diff --git a/CHANGELOG.md b/CHANGELOG.md index 3059036a10..e681dd33ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ a release. --- ## [Unreleased] +### Fixed +- SoftDeleteable: Resolved a bug where a soft-deleted object isn't remove from the ObjectManager (#2930) + ### Added - IP address provider for use with extensions with IP address references (#2928) @@ -105,7 +108,7 @@ a release. - Dropped support for doctrine/dbal < 3.2 ### 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\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. ## [3.13.0] - 2023-09-06 diff --git a/src/SoftDeleteable/SoftDeleteableListener.php b/src/SoftDeleteable/SoftDeleteableListener.php index 7157e11380..133aeb1927 100644 --- a/src/SoftDeleteable/SoftDeleteableListener.php +++ b/src/SoftDeleteable/SoftDeleteableListener.php @@ -51,6 +51,13 @@ class SoftDeleteableListener extends MappedEventSubscriber */ public const POST_SOFT_DELETE = 'postSoftDelete'; + /** + * Objects soft-deleted on flush. + * + * @var array + */ + private array $softDeletedObjects = []; + /** * @return string[] */ @@ -59,6 +66,7 @@ public function getSubscribedEvents() return [ 'loadClassMetadata', 'onFlush', + 'postFlush', ]; } @@ -102,7 +110,7 @@ public function onFlush(EventArgs $args) $evm->dispatchEvent( self::PRE_SOFT_DELETE, - $preSoftDeleteEventArgs + $preSoftDeleteEventArgs, ); } @@ -129,10 +137,27 @@ public function onFlush(EventArgs $args) $postSoftDeleteEventArgs ); } + + $this->softDeletedObjects[] = $object; } } } + /** + * Detach soft-deleted objects from object manager. + * + * @return void + */ + public function postFlush(EventArgs $args) + { + $ea = $this->getEventAdapter($args); + $om = $ea->getObjectManager(); + foreach ($this->softDeletedObjects as $index => $object) { + $om->detach($object); + unset($this->softDeletedObjects[$index]); + } + } + /** * Maps additional metadata * diff --git a/tests/Gedmo/Blameable/Fixture/Entity/Article.php b/tests/Gedmo/Blameable/Fixture/Entity/Article.php index f780508a6c..0ecb4263d5 100644 --- a/tests/Gedmo/Blameable/Fixture/Entity/Article.php +++ b/tests/Gedmo/Blameable/Fixture/Entity/Article.php @@ -48,7 +48,7 @@ class Article implements Blameable * @ORM\OneToMany(targetEntity="Gedmo\Tests\Blameable\Fixture\Entity\Comment", mappedBy="article") */ #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'article')] - private $comments; + private Collection $comments; /** * @Gedmo\Blameable(on="create") diff --git a/tests/Gedmo/SoftDeleteable/Fixture/Entity/Article.php b/tests/Gedmo/SoftDeleteable/Fixture/Entity/Article.php index 5c6c1e0a35..bb467ef452 100644 --- a/tests/Gedmo/SoftDeleteable/Fixture/Entity/Article.php +++ b/tests/Gedmo/SoftDeleteable/Fixture/Entity/Article.php @@ -92,4 +92,12 @@ public function addComment(Comment $comment): void { $this->comments[] = $comment; } + + /** + * @return Collection + */ + public function getComments(): Collection + { + return $this->comments; + } } diff --git a/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php b/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php index daddb99c0f..d377e68e56 100644 --- a/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php +++ b/tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php @@ -522,6 +522,42 @@ public function testShouldFilterBeQueryCachedCorrectlyWhenToggledForEntity(): vo static::assertCount(0, $data); } + public function testSoftDeletedObjectIsRemovedPostFlush(): void + { + $repo = $this->em->getRepository(Article::class); + $commentRepo = $this->em->getRepository(Comment::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()); + static::assertCount(1, $art->getComments()); + + $this->em->remove($comment); + + // The Comment has been marked for removal, but not yet flushed. This means the + // Comment should still be available. + static::assertInstanceOf(Comment::class, $commentRepo->find($comment->getId())); + + $this->em->flush(); + + // Now that we've flushed, the Comment should no longer be available and should return null. + static::assertNull($commentRepo->find($comment->getId())); + } + public function testPostSoftDeleteEventIsDispatched(): void { $this->em->getEventManager()->addEventSubscriber(new WithPreAndPostSoftDeleteEventArgsTypeListener());