diff --git a/CHANGELOG.md b/CHANGELOG.md index 334d691212..2a9702e5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,15 @@ a release. --- ## [Unreleased] +### Added +- SoftDeleteable: `Gedmo\SoftDeleteable\Event\PreSoftDeleteEventArgs` and + `Gedmo\SoftDeleteable\Event\PostSoftDeleteEventArgs` classes. + +### Deprecated +- Do not add type-hinted parameters `Gedmo\SoftDeleteable\Event\PreSoftDeleteEventArgs` and + `Gedmo\SoftDeleteable\Event\PostSoftDeleteEventArgs` classes to `preSoftDelete` and `postSoftDelete` events. +- The `createLifecycleEventArgsInstance()` method on `Gedmo\Mapping\Event\AdapterInterface` + implementations is deprecated, use your own subclass of `Doctrine\Persistence\Event\LifecycleEventArgs` as needed. ## [3.14.0] ### Added @@ -31,7 +40,7 @@ a release. ### Deprecated - Calling `Gedmo\Mapping\Event\Adapter\ORM::getObjectManager()` and `getObject()` on EventArgs that do not implement `getObjectManager()` and `getObject()` (such as old EventArgs implementing `getEntityManager()` and `getEntity()`) -- Calling `Gedmo\Uploadable\Event\UploadableBaseEventArgs::getEntityManager()` and `getEntity()`. Call `getObjectManager()` and `getObject()` instead. +- Calling `Gedmo\Uploadable\Event\UploadableBaseEventArgs::getEntityManager()` and `getEntity()`. Call `getObjectManager()` and `getObject()` instead. ## [3.13.0] - 2023-09-06 ### Fixed diff --git a/rector.php b/rector.php index aad132431a..2803e9f705 100644 --- a/rector.php +++ b/rector.php @@ -10,7 +10,6 @@ */ use Rector\Config\RectorConfig; -use Rector\Php71\Rector\FuncCall\CountOnNullRector; use Rector\Set\ValueObject\LevelSetList; use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector; @@ -33,7 +32,4 @@ $rectorConfig->importNames(); $rectorConfig->importShortClasses(false); - $rectorConfig->skip([ - CountOnNullRector::class, - ]); }; diff --git a/src/Mapping/Event/Adapter/ODM.php b/src/Mapping/Event/Adapter/ODM.php index f90ee5417b..b333b6f06f 100644 --- a/src/Mapping/Event/Adapter/ODM.php +++ b/src/Mapping/Event/Adapter/ODM.php @@ -150,6 +150,8 @@ public function clearObjectChangeSet($uow, $object) } /** + * @deprecated to be removed in 4.0, use custom lifecycle event classes instead. + * * Creates a ODM specific LifecycleEventArgs. * * @param object $document @@ -159,6 +161,11 @@ public function clearObjectChangeSet($uow, $object) */ public function createLifecycleEventArgsInstance($document, $documentManager) { + @trigger_error(sprintf( + 'Using "%s()" method is deprecated since gedmo/doctrine-extensions 3.15 and will be removed in version 4.0.', + __METHOD__ + ), E_USER_DEPRECATED); + return new LifecycleEventArgs($document, $documentManager); } } diff --git a/src/Mapping/Event/Adapter/ORM.php b/src/Mapping/Event/Adapter/ORM.php index 55c834167a..1c1ddadd02 100644 --- a/src/Mapping/Event/Adapter/ORM.php +++ b/src/Mapping/Event/Adapter/ORM.php @@ -180,15 +180,26 @@ public function clearObjectChangeSet($uow, $object) } /** - * Creates a ORM specific LifecycleEventArgs. + * @deprecated use custom lifecycle event classes instead * - * @param object $document + * Creates an ORM specific LifecycleEventArgs + * + * @param object $object * @param EntityManagerInterface $entityManager * * @return LifecycleEventArgs */ - public function createLifecycleEventArgsInstance($document, $entityManager) + public function createLifecycleEventArgsInstance($object, $entityManager) { - return new LifecycleEventArgs($document, $entityManager); + @trigger_error(sprintf( + 'Using "%s()" method is deprecated since gedmo/doctrine-extensions 3.15 and will be removed in version 4.0.', + __METHOD__ + ), E_USER_DEPRECATED); + + if (!class_exists(LifecycleEventArgs::class)) { + throw new \RuntimeException(sprintf('Cannot call %s() when using doctrine/orm >=3.0, use a custom lifecycle event class instead.', __METHOD__)); + } + + return new LifecycleEventArgs($object, $entityManager); } } diff --git a/src/Mapping/Event/AdapterInterface.php b/src/Mapping/Event/AdapterInterface.php index 0fc8bc6fa3..339a54f3ea 100644 --- a/src/Mapping/Event/AdapterInterface.php +++ b/src/Mapping/Event/AdapterInterface.php @@ -21,7 +21,7 @@ * * @author Gediminas Morkevicius * - * @method LifecycleEventArgs createLifecycleEventArgsInstance(object $object, ObjectManager $manager) + * @method LifecycleEventArgs createLifecycleEventArgsInstance(object $object, ObjectManager $manager) @deprecated * @method object getObject() */ interface AdapterInterface diff --git a/src/SoftDeleteable/Event/PostSoftDeleteEventArgs.php b/src/SoftDeleteable/Event/PostSoftDeleteEventArgs.php new file mode 100644 index 0000000000..f8a41338b2 --- /dev/null +++ b/src/SoftDeleteable/Event/PostSoftDeleteEventArgs.php @@ -0,0 +1,24 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\SoftDeleteable\Event; + +use Doctrine\Persistence\Event\LifecycleEventArgs; +use Doctrine\Persistence\ObjectManager; + +/** + * @template TObjectManager of ObjectManager + * + * @template-extends LifecycleEventArgs + */ +final class PostSoftDeleteEventArgs extends LifecycleEventArgs +{ +} diff --git a/src/SoftDeleteable/Event/PreSoftDeleteEventArgs.php b/src/SoftDeleteable/Event/PreSoftDeleteEventArgs.php new file mode 100644 index 0000000000..e01cd73d86 --- /dev/null +++ b/src/SoftDeleteable/Event/PreSoftDeleteEventArgs.php @@ -0,0 +1,24 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\SoftDeleteable\Event; + +use Doctrine\Persistence\Event\LifecycleEventArgs; +use Doctrine\Persistence\ObjectManager; + +/** + * @template TObjectManager of ObjectManager + * + * @template-extends LifecycleEventArgs + */ +final class PreSoftDeleteEventArgs extends LifecycleEventArgs +{ +} diff --git a/src/SoftDeleteable/SoftDeleteableListener.php b/src/SoftDeleteable/SoftDeleteableListener.php index 6bf17e9ccb..eae0260eb9 100644 --- a/src/SoftDeleteable/SoftDeleteableListener.php +++ b/src/SoftDeleteable/SoftDeleteableListener.php @@ -10,6 +10,7 @@ namespace Gedmo\SoftDeleteable; use Doctrine\Common\EventArgs; +use Doctrine\Common\EventManager; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\UnitOfWork as MongoDBUnitOfWork; use Doctrine\ORM\EntityManagerInterface; @@ -17,6 +18,8 @@ use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\ObjectManager; use Gedmo\Mapping\MappedEventSubscriber; +use Gedmo\SoftDeleteable\Event\PostSoftDeleteEventArgs; +use Gedmo\SoftDeleteable\Event\PreSoftDeleteEventArgs; /** * SoftDeleteable listener @@ -81,10 +84,17 @@ public function onFlush(EventArgs $args) continue; // want to hard delete } - $evm->dispatchEvent( - self::PRE_SOFT_DELETE, - $ea->createLifecycleEventArgsInstance($object, $om) - ); + if ($evm->hasListeners(self::PRE_SOFT_DELETE)) { + // @todo: in the next major remove check and only instantiate the event + $preSoftDeleteEventArgs = $this->hasToDispatchNewEvent($evm, self::PRE_SOFT_DELETE, PreSoftDeleteEventArgs::class) + ? new PreSoftDeleteEventArgs($object, $om) + : $ea->createLifecycleEventArgsInstance($object, $om); + + $evm->dispatchEvent( + self::PRE_SOFT_DELETE, + $preSoftDeleteEventArgs + ); + } $reflProp->setValue($object, $date); @@ -98,10 +108,17 @@ public function onFlush(EventArgs $args) ]); } - $evm->dispatchEvent( - self::POST_SOFT_DELETE, - $ea->createLifecycleEventArgsInstance($object, $om) - ); + if ($evm->hasListeners(self::POST_SOFT_DELETE)) { + // @todo: in the next major remove check and only instantiate the event + $postSoftDeleteEventArgs = $this->hasToDispatchNewEvent($evm, self::POST_SOFT_DELETE, PostSoftDeleteEventArgs::class) + ? new PostSoftDeleteEventArgs($object, $om) + : $ea->createLifecycleEventArgsInstance($object, $om); + + $evm->dispatchEvent( + self::POST_SOFT_DELETE, + $postSoftDeleteEventArgs + ); + } } } } @@ -124,4 +141,32 @@ protected function getNamespace() { return __NAMESPACE__; } + + /** @param class-string $eventClass */ + private function hasToDispatchNewEvent(EventManager $eventManager, string $eventName, string $eventClass): bool + { + foreach ($eventManager->getListeners($eventName) as $listener) { + $reflMethod = new \ReflectionMethod($listener, $eventName); + + $parameters = $reflMethod->getParameters(); + + if ( + 1 !== count($parameters) + || !$parameters[0]->hasType() + || !$parameters[0]->getType() instanceof \ReflectionNamedType + || $eventClass !== $parameters[0]->getType()->getName() + ) { + @trigger_error(sprintf( + 'Type-hinting to something different than "%s" in "%s::%s()" is deprecated.', + $eventClass, + get_class($listener), + $reflMethod->getName() + ), E_USER_DEPRECATED); + + return false; + } + } + + return true; + } } diff --git a/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithLifecycleEventArgsFromODMTypeListener.php b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithLifecycleEventArgsFromODMTypeListener.php new file mode 100644 index 0000000000..fdc592d810 --- /dev/null +++ b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithLifecycleEventArgsFromODMTypeListener.php @@ -0,0 +1,35 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\SoftDeleteable\Fixture\Listener; + +use Doctrine\Common\EventSubscriber; +use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs; +use Gedmo\SoftDeleteable\SoftDeleteableListener; + +final class WithLifecycleEventArgsFromODMTypeListener implements EventSubscriber +{ + public function preSoftDelete(LifecycleEventArgs $args): void + { + } + + public function postSoftDelete(LifecycleEventArgs $args): void + { + } + + public function getSubscribedEvents(): array + { + return [ + SoftDeleteableListener::PRE_SOFT_DELETE, + SoftDeleteableListener::POST_SOFT_DELETE, + ]; + } +} diff --git a/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithLifecycleEventArgsFromORMTypeListener.php b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithLifecycleEventArgsFromORMTypeListener.php new file mode 100644 index 0000000000..5d936b7d5f --- /dev/null +++ b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithLifecycleEventArgsFromORMTypeListener.php @@ -0,0 +1,35 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\SoftDeleteable\Fixture\Listener; + +use Doctrine\Common\EventSubscriber; +use Doctrine\ORM\Event\LifecycleEventArgs; +use Gedmo\SoftDeleteable\SoftDeleteableListener; + +final class WithLifecycleEventArgsFromORMTypeListener implements EventSubscriber +{ + public function preSoftDelete(LifecycleEventArgs $args): void + { + } + + public function postSoftDelete(LifecycleEventArgs $args): void + { + } + + public function getSubscribedEvents(): array + { + return [ + SoftDeleteableListener::PRE_SOFT_DELETE, + SoftDeleteableListener::POST_SOFT_DELETE, + ]; + } +} diff --git a/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithPreAndPostSoftDeleteEventArgsTypeListener.php b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithPreAndPostSoftDeleteEventArgsTypeListener.php new file mode 100644 index 0000000000..b24c51f6d7 --- /dev/null +++ b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithPreAndPostSoftDeleteEventArgsTypeListener.php @@ -0,0 +1,39 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\SoftDeleteable\Fixture\Listener; + +use Doctrine\Common\EventSubscriber; +use Doctrine\Persistence\ObjectManager; +use Gedmo\SoftDeleteable\Event\PostSoftDeleteEventArgs; +use Gedmo\SoftDeleteable\Event\PreSoftDeleteEventArgs; +use Gedmo\SoftDeleteable\SoftDeleteableListener; + +final class WithPreAndPostSoftDeleteEventArgsTypeListener implements EventSubscriber +{ + /** @param PreSoftDeleteEventArgs $args */ + public function preSoftDelete(PreSoftDeleteEventArgs $args): void + { + } + + /** @param PostSoftDeleteEventArgs $args */ + public function postSoftDelete(PostSoftDeleteEventArgs $args): void + { + } + + public function getSubscribedEvents(): array + { + return [ + SoftDeleteableListener::PRE_SOFT_DELETE, + SoftDeleteableListener::POST_SOFT_DELETE, + ]; + } +} diff --git a/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithoutTypeListener.php b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithoutTypeListener.php new file mode 100644 index 0000000000..9ab5cf3a8e --- /dev/null +++ b/tests/Gedmo/SoftDeleteable/Fixture/Listener/WithoutTypeListener.php @@ -0,0 +1,39 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\SoftDeleteable\Fixture\Listener; + +use Doctrine\Common\EventSubscriber; +use Doctrine\Persistence\ObjectManager; +use Gedmo\SoftDeleteable\Event\PostSoftDeleteEventArgs; +use Gedmo\SoftDeleteable\Event\PreSoftDeleteEventArgs; +use Gedmo\SoftDeleteable\SoftDeleteableListener; + +final class WithoutTypeListener implements EventSubscriber +{ + /** @param PreSoftDeleteEventArgs $args */ + public function preSoftDelete($args): void + { + } + + /** @param PostSoftDeleteEventArgs $args */ + public function postSoftDelete($args): void + { + } + + public function getSubscribedEvents(): array + { + return [ + SoftDeleteableListener::PRE_SOFT_DELETE, + SoftDeleteableListener::POST_SOFT_DELETE, + ]; + } +} diff --git a/tests/Gedmo/SoftDeleteable/SoftDeleteableDocumentTest.php b/tests/Gedmo/SoftDeleteable/SoftDeleteableDocumentTest.php index 34dfbb1129..fcf8261f96 100644 --- a/tests/Gedmo/SoftDeleteable/SoftDeleteableDocumentTest.php +++ b/tests/Gedmo/SoftDeleteable/SoftDeleteableDocumentTest.php @@ -12,11 +12,13 @@ namespace Gedmo\Tests\SoftDeleteable; use Doctrine\Common\EventManager; -use Doctrine\Common\EventSubscriber; use Gedmo\SoftDeleteable\Filter\ODM\SoftDeleteableFilter; use Gedmo\SoftDeleteable\SoftDeleteableListener; use Gedmo\Tests\SoftDeleteable\Fixture\Document\User; use Gedmo\Tests\SoftDeleteable\Fixture\Document\UserTimeAware; +use Gedmo\Tests\SoftDeleteable\Fixture\Listener\WithLifecycleEventArgsFromODMTypeListener; +use Gedmo\Tests\SoftDeleteable\Fixture\Listener\WithoutTypeListener; +use Gedmo\Tests\SoftDeleteable\Fixture\Listener\WithPreAndPostSoftDeleteEventArgsTypeListener; use Gedmo\Tests\Tool\BaseTestCaseMongoODM; /** @@ -150,28 +152,22 @@ public function shouldSupportSoftDeleteableFilterTimeAware(): void public function testPostSoftDeleteEventIsDispatched(): void { - $subscriber = $this->getMockBuilder(EventSubscriber::class) - ->setMethods([ - 'getSubscribedEvents', - 'preSoftDelete', - 'postSoftDelete', - ]) - ->getMock(); + $this->dm->getEventManager()->addEventSubscriber(new WithPreAndPostSoftDeleteEventArgsTypeListener()); - $subscriber->expects(static::once()) - ->method('getSubscribedEvents') - ->willReturn([SoftDeleteableListener::PRE_SOFT_DELETE, SoftDeleteableListener::POST_SOFT_DELETE]); - - $subscriber->expects(static::once()) - ->method('preSoftDelete') - ->with(static::anything()); + $this->doTestPostSoftDeleteEventIsDispatched(); + } - $subscriber->expects(static::once()) - ->method('postSoftDelete') - ->with(static::anything()); + /** @group legacy */ + public function testPostSoftDeleteEventIsDispatchedWithDeprecatedListeners(): void + { + $this->dm->getEventManager()->addEventSubscriber(new WithoutTypeListener()); + $this->dm->getEventManager()->addEventSubscriber(new WithLifecycleEventArgsFromODMTypeListener()); - $this->dm->getEventManager()->addEventSubscriber($subscriber); + $this->doTestPostSoftDeleteEventIsDispatched(); + } + private function doTestPostSoftDeleteEventIsDispatched(): void + { $repo = $this->dm->getRepository(self::USER_CLASS); $newUser = new User(); diff --git a/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php b/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php index 19067a7ef1..fcff3c745b 100644 --- a/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php +++ b/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php @@ -12,7 +12,7 @@ namespace Gedmo\Tests\SoftDeleteable; use Doctrine\Common\EventManager; -use Doctrine\Common\EventSubscriber; +use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Query; use Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter; use Gedmo\SoftDeleteable\Query\TreeWalker\SoftDeleteableWalker; @@ -27,6 +27,9 @@ use Gedmo\Tests\SoftDeleteable\Fixture\Entity\Page; use Gedmo\Tests\SoftDeleteable\Fixture\Entity\User; use Gedmo\Tests\SoftDeleteable\Fixture\Entity\UserNoHardDelete; +use Gedmo\Tests\SoftDeleteable\Fixture\Listener\WithLifecycleEventArgsFromORMTypeListener; +use Gedmo\Tests\SoftDeleteable\Fixture\Listener\WithoutTypeListener; +use Gedmo\Tests\SoftDeleteable\Fixture\Listener\WithPreAndPostSoftDeleteEventArgsTypeListener; use Gedmo\Tests\Tool\BaseTestCaseORM; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -529,52 +532,21 @@ public function testShouldFilterBeQueryCachedCorrectlyWhenToggledForEntity(): vo public function testPostSoftDeleteEventIsDispatched(): void { - $subscriber = $this->getMockBuilder(EventSubscriber::class) - ->setMethods([ - 'getSubscribedEvents', - 'preSoftDelete', - 'postSoftDelete', - ]) - ->getMock(); - - $subscriber->expects(static::once()) - ->method('getSubscribedEvents') - ->willReturn([ - SoftDeleteableListener::PRE_SOFT_DELETE, - SoftDeleteableListener::POST_SOFT_DELETE, - ]); - - $subscriber->expects(static::exactly(2)) - ->method('preSoftDelete') - ->with(static::anything()); - - $subscriber->expects(static::exactly(2)) - ->method('postSoftDelete') - ->with(static::anything()); - - $this->em->getEventManager()->addEventSubscriber($subscriber); + $this->em->getEventManager()->addEventSubscriber(new WithPreAndPostSoftDeleteEventArgsTypeListener()); - $repo = $this->em->getRepository(self::ARTICLE_CLASS); - - $comment = new Comment(); - $commentValue = 'Comment 1'; - $comment->setComment($commentValue); - $art0 = new Article(); - $field = 'title'; - $value = 'Title 1'; - $art0->setTitle($value); - $art0->addComment($comment); - - $this->em->persist($art0); - $this->em->flush(); + $this->doTestPostSoftDeleteEventIsDispatched(); + } - $art = $repo->findOneBy([$field => $value]); + /** @group legacy */ + public function testPostSoftDeleteEventIsDispatchedWithDeprecatedListeners(): void + { + $this->em->getEventManager()->addEventSubscriber(new WithoutTypeListener()); - static::assertNull($art->getDeletedAt()); - static::assertNull($comment->getDeletedAt()); + if (class_exists(LifecycleEventArgs::class)) { + $this->em->getEventManager()->addEventSubscriber(new WithLifecycleEventArgsFromORMTypeListener()); + } - $this->em->remove($art); - $this->em->flush(); + $this->doTestPostSoftDeleteEventIsDispatched(); } public function testShouldNotDeleteIfColumnNameDifferFromPropertyName(): void @@ -625,4 +597,29 @@ protected function getUsedEntityFixtures(): array self::USER_NO_HARD_DELETE_CLASS, ]; } + + private function doTestPostSoftDeleteEventIsDispatched(): void + { + $repo = $this->em->getRepository(self::ARTICLE_CLASS); + + $comment = new Comment(); + $commentValue = 'Comment 1'; + $comment->setComment($commentValue); + $art0 = new Article(); + $field = 'title'; + $value = 'Title 1'; + $art0->setTitle($value); + $art0->addComment($comment); + + $this->em->persist($art0); + $this->em->flush(); + + $art = $repo->findOneBy([$field => $value]); + + static::assertNull($art->getDeletedAt()); + static::assertNull($comment->getDeletedAt()); + + $this->em->remove($art); + $this->em->flush(); + } }