Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gedmo soft-delete #3

Merged
merged 4 commits into from
Feb 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 30 additions & 28 deletions .php_cs.dist
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
<?php

$finder = PhpCsFixer\Finder::create()
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests')
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests')
;

return PhpCsFixer\Config::create()
->setUsingCache(FALSE)
->setIndent("\t")
->setRules([
'@PSR2' => TRUE,
'array_syntax' => ['syntax' => 'short'],
'trailing_comma_in_multiline_array' => true,
'lowercase_constants' => FALSE,
'declare_strict_types' => TRUE,
'phpdoc_align' => TRUE,
'blank_line_after_opening_tag' => TRUE,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return'],
],
'blank_line_after_namespace' => TRUE,
'single_blank_line_before_namespace' => TRUE,
'return_type_declaration' => [
'space_before' => 'none',
],
'ordered_imports' => [
'sort_algorithm' => 'length',
],
'no_unused_imports' => TRUE,
'single_line_after_imports' => TRUE,
'no_leading_import_slash' => TRUE,
])
->setRiskyAllowed(TRUE)
->setFinder($finder)
->setIndent("\t")
->setRules([
'@PSR2' => TRUE,
'array_syntax' => ['syntax' => 'short'],
'trailing_comma_in_multiline_array' => true,
'constant_case' => [
'case' => 'upper',
],
'declare_strict_types' => TRUE,
'phpdoc_align' => TRUE,
'blank_line_after_opening_tag' => TRUE,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return'],
],
'blank_line_after_namespace' => TRUE,
'single_blank_line_before_namespace' => TRUE,
'return_type_declaration' => [
'space_before' => 'none',
],
'ordered_imports' => [
'sort_algorithm' => 'length',
],
'no_unused_imports' => TRUE,
'single_line_after_imports' => TRUE,
'no_leading_import_slash' => TRUE,
])
->setRiskyAllowed(TRUE)
->setFinder($finder)
;
16 changes: 9 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,27 @@
}
],
"require": {
"php": "~7.1",
"nette/di": "~2.4",
"nette/utils": "~2.4",
"nette/application": "~2.4",
"nette/forms": "~2.4",
"php": "^7.1",
"nette/di": "^2.4",
"nette/utils": "^2.4",
"nette/application": "^2.4",
"nette/forms": "^2.4",
"ramsey/uuid": "^3.8",
"68publishers/smart-nette-component": "^0.1",
"68publishers/doctrine-persistence": "^0.1",
"68publishers/image-storage": "^0.2",
"symfony/event-dispatcher": "^4.0",
"kdyby/doctrine": "^3.0",
"kdyby/translation": "~2.4"
"kdyby/translation": "^2.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.0",
"nette/tester": "^1.7",
"68publishers/notification-bundle": "^0.1"
"68publishers/notification-bundle": "^0.1",
"gedmo/doctrine-extensions": "^2.4"
},
"suggest": {
"gedmo/doctrine-extensions": "If you want to use soft-deletable images.",
"68publishers/notification-bundle": "For automatic notifications (flash message, toastr) on image upload, deletion etc."
},
"autoload": {
Expand Down
86 changes: 86 additions & 0 deletions src/DI/ImageBundleExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@

namespace SixtyEightPublishers\ImageBundle\DI;

use Gedmo;
use Kdyby;
use Nette;
use Symfony;
use Doctrine;
use SixtyEightPublishers;

final class ImageBundleExtension extends Nette\DI\CompilerExtension implements
Kdyby\Doctrine\DI\IEntityProvider,
Kdyby\Doctrine\DI\ITargetEntityProvider,
Kdyby\Translation\DI\ITranslationProvider
{
public const SOFT_DELETE_FILTER_NAME = 'soft-delete';

/** @var array */
private $defaults = [
'image_entity' => SixtyEightPublishers\ImageBundle\DoctrineEntity\Basic\Image::class, # or array [entity => class name, mapping: array]
Expand All @@ -25,6 +29,10 @@ final class ImageBundleExtension extends Nette\DI\CompilerExtension implements
'image_manager_control' => NULL,
'dropzone_control' => NULL,
],
'soft_delete' => [
'register_listener' => FALSE,
'register_filter' => FALSE,
],
];

/** @var array|NULL */
Expand Down Expand Up @@ -74,6 +82,43 @@ public function loadConfiguration(): void
'configuration' => $configurationStatementFactory->create((string) $name, $options),
]);
}

$softDeleteConfig = $this->getSoftDeleteConfig();

if (TRUE === $softDeleteConfig['register_listener']) {
$reader = $builder->getByType(Doctrine\Common\Annotations\Reader::class, FALSE);

$softDeleteListener = $builder->addDefinition($this->prefix('event_subscriber.gedmo.soft_delete_listener'))
->setType(Gedmo\SoftDeleteable\SoftDeleteableListener::class);

if (NULL !== $reader) {
$softDeleteListener->addSetup('setAnnotationReader', [
'reader' => '@' . $reader,
]);
}
}
}

/**
* {@inheritdoc}
*
* @throws \Nette\Utils\AssertionException
*/
public function beforeCompile(): void
{
$builder = $this->getContainerBuilder();
$softDeleteConfig = $this->getSoftDeleteConfig();

if (TRUE === $softDeleteConfig['register_filter']) {
$builder->getDefinitionByType(Doctrine\ORM\EntityManagerInterface::class)
->addSetup('$service->getConfiguration()->addFilter(?, ?)', [
self::SOFT_DELETE_FILTER_NAME,
Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter::class,
])
->addSetup('$service->getFilters()->enable(?)', [
self::SOFT_DELETE_FILTER_NAME,
]);
}
}

/**
Expand Down Expand Up @@ -217,6 +262,47 @@ private function getImageEntityDefinition(): array
return $this->imageEntityDefinition = $imageEntity;
}

/**
* @return array
* @throws \Nette\Utils\AssertionException
*/
private function getSoftDeleteConfig(): array
{
$config = $this->getConfig($this->defaults);

Nette\Utils\Validators::assertField($config, 'soft_delete', 'array');
Nette\Utils\Validators::assertField($config['soft_delete'], 'register_listener', 'bool');
Nette\Utils\Validators::assertField($config['soft_delete'], 'register_filter', 'bool');

$entity = $this->getImageEntityDefinition()['entity'];
$softDelete = $config['soft_delete'];

$enabledOptions = array_map(function (string $v) {
return $this->name . '.soft_delete' . $v;
}, array_keys(array_filter([$softDelete['register_listener'], $softDelete['register_filter']])));

if ((0 < count($enabledOptions)) && !is_subclass_of($entity, SixtyEightPublishers\ImageBundle\DoctrineEntity\ISoftDeletableImage::class, TRUE)) {
throw new Nette\Utils\AssertionException(sprintf(
'Logical mismatch. A soft delete dependencies should by register according to the configuration (%s %s) but an Image entity is not implementor of an interface %s.',
1 < count($enabledOptions) ? 'options' : 'an option',
implode(', ', $enabledOptions),
SixtyEightPublishers\ImageBundle\DoctrineEntity\ISoftDeletableImage::class
));
}

/** @noinspection ClassConstantCanBeUsedInspection */
if (TRUE === $softDelete['register_listener'] && !class_exists('Gedmo\SoftDeleteable\SoftDeleteableListener')) {
throw new Nette\Utils\AssertionException('Class Gedmo\SoftDeleteable\SoftDeleteableListener not found. Please add a dependency on a package gedmo/doctrine-extensions.');
}

/** @noinspection ClassConstantCanBeUsedInspection */
if (TRUE === $softDelete['register_filter'] && !class_exists('Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter')) {
throw new Nette\Utils\AssertionException('Class Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter not found. Please add a dependency on a package gedmo/doctrine-extensions.');
}

return $softDelete;
}

/**************** interface \Kdyby\Doctrine\DI\IEntityProvider ****************/

/**
Expand Down
8 changes: 4 additions & 4 deletions src/DoctrineEntity/ISoftDeletableImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
interface ISoftDeletableImage extends IImage
{
/**
* @return void
* @param \DateTime|NULL $deletedAt
*/
public function delete(): void;
public function setDeletedAt(?\DateTime $deletedAt): void;

/**
* @return void
* @return \DateTime|NULL
*/
public function restore(): void;
public function getDeletedAt(): ?\DateTime;

/**
* @return bool
Expand Down
2 changes: 2 additions & 0 deletions src/DoctrineEntity/SoftDeletable/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

use SixtyEightPublishers;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
* @ORM\Entity
* @ORM\Table(indexes={
* @ORM\Index(name="IDX_IMAGE_CREATED", columns={"created"})
* })
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
*/
final class Image extends SixtyEightPublishers\ImageBundle\DoctrineEntity\AbstractImage implements SixtyEightPublishers\ImageBundle\DoctrineEntity\ISoftDeletableImage
{
Expand Down
28 changes: 19 additions & 9 deletions src/DoctrineEntity/TSoftDeletableImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,46 @@

namespace SixtyEightPublishers\ImageBundle\DoctrineEntity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
* (!) ADD THIS ANNOTATION INTO YOUR ENTITY:
*
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
*/
trait TSoftDeletableImage
{
/**
* @ORM\Column(type="boolean")
* @var \DateTime|NULL
*
* @var bool
* @ORM\Column(type="datetime", nullable=true)
*/
protected $deleted = FALSE;
protected $deletedAt;

/**
* @param \DateTime|NULL $deletedAt
*
* @return void
*/
public function delete(): void
public function setDeletedAt(?\DateTime $deletedAt): void
{
$this->deleted = TRUE;
$this->deletedAt = $deletedAt;
}

/**
* @return void
* @return \DateTime|NULL
*/
public function restore(): void
public function getDeletedAt(): ?\DateTime
{
$this->deleted = FALSE;
return $this->deletedAt;
}

/**
* @return bool
*/
public function isDeleted(): bool
{
return $this->deleted;
return NULL !== $this->deletedAt;
}
}
1 change: 0 additions & 1 deletion src/Event/UploadCompletedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Nette;
use Symfony;
use SixtyEightPublishers;

final class UploadCompletedEvent extends Symfony\Contracts\EventDispatcher\Event
{
Expand Down
4 changes: 1 addition & 3 deletions src/Storage/ArrayDataStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ public function addImage(SixtyEightPublishers\ImageBundle\DoctrineEntity\IImage
*/
public function getImages(): Doctrine\Common\Collections\Collection
{
return (new Doctrine\Common\Collections\ArrayCollection($this->images))->filter(static function (SixtyEightPublishers\ImageBundle\DoctrineEntity\IImage $image) {
return !$image instanceof SixtyEightPublishers\ImageBundle\DoctrineEntity\ISoftDeletableImage || !$image->isDeleted();
});
return new Doctrine\Common\Collections\ArrayCollection($this->images);
}
}
3 changes: 1 addition & 2 deletions src/Storage/CallbackDataStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Nette;
use Doctrine;
use SixtyEightPublishers;

final class CallbackDataStorage implements IDataStorage
{
Expand All @@ -33,6 +32,6 @@ public function getImages(): Doctrine\Common\Collections\Collection
{
$cb = $this->callback;

return (new ArrayDataStorage((array) $cb()))->getImages();
return new Doctrine\Common\Collections\ArrayCollection((array) $cb());
}
}
21 changes: 1 addition & 20 deletions src/Storage/DoctrineDataStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,16 @@ public function __construct(Doctrine\ORM\QueryBuilder $qb)
$this->qb = $qb;
}

/**
* @return \Doctrine\ORM\Query\Expr\From|NULL
*/
private function getFrom(): ?Doctrine\ORM\Query\Expr\From
{
$from = $this->qb->getDQLPart('from');

return isset($from[0]) && $from[0] instanceof Doctrine\ORM\Query\Expr\From ? $from[0] : NULL;
}

/*************** interface \SixtyEightPublishers\ImageBundle\Storage\IDataStorage ***************/

/**
* {@inheritdoc}
*/
public function getImages(): Doctrine\Common\Collections\Collection
{
$qb = clone $this->qb;
$from = $this->getFrom();

# remove soft-deleted images
if (NULL !== $from && (is_a($from->getFrom(), SixtyEightPublishers\ImageBundle\DoctrineEntity\ISoftDeletableImage::class, TRUE) || is_subclass_of($from->getFrom(), SixtyEightPublishers\ImageBundle\DoctrineEntity\ISoftDeletableImage::class, TRUE))) {
$qb->andWhere($from->getAlias() . '.deleted = :__deleted')
->setParameter('__deleted', FALSE);
}

$collection = new Doctrine\Common\Collections\ArrayCollection();

foreach (new Doctrine\ORM\Tools\Pagination\Paginator($qb->getQuery(), FALSE) as $image) {
foreach (new Doctrine\ORM\Tools\Pagination\Paginator($this->qb->getQuery(), FALSE) as $image) {
if (!$image instanceof SixtyEightPublishers\ImageBundle\DoctrineEntity\IImage) {
throw new SixtyEightPublishers\ImageBundle\Exception\InvalidStateException(sprintf(
'Invalid entities returned from passed Query. Entities must be instances of interface %s, %s given.',
Expand Down
Loading