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

Regression in Events::postRemove event #11098

Open
onEXHovia opened this issue Nov 30, 2023 · 9 comments
Open

Regression in Events::postRemove event #11098

onEXHovia opened this issue Nov 30, 2023 · 9 comments
Assignees

Comments

@onEXHovia
Copy link

onEXHovia commented Nov 30, 2023

Bug Report

Q A
BC Break yes
Version 2.17.1

Summary

When updating orm with version 2.14.1 to 2.17.1, some events stopped working. These are quite old tests and they still worked.
The idea was that after deleting an entity, check if there are more related entities, if no, then delete the parent(owner).

Current behavior

Parent(owner) entity stopped being deleted.

How to reproduce

namespace App\EventListener;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use App\Entity\PullRequest\Comment;

final class NoteAutoRemoveListener implements EventSubscriber
{
    public function getSubscribedEvents(): array
    {
        return [
            Events::postRemove,
        ];
    }

    public function postRemove(LifecycleEventArgs $args): void
    {
        $entity = $args->getObject();
        $entityManager = $args->getObjectManager();

        if ($entity instanceof Comment) {
            $this->removeDiscussionNote($entity, $entityManager);
        }
    }

    private function removeDiscussionNote(Comment $comment, EntityManagerInterface $entityManager): void
    {
        $discussion = $comment->getNote();

        if (null !== $discussion && 0 === $discussion->getComments()->count()) {
            $entityManager->remove($discussion);
        }
    }
}

and test

namespace App\Tests\EventListener;

use Doctrine\ORM\EntityManagerInterface;
use App\Entity\PullRequest\Comment;
use App\Entity\PullRequest\DiscussionNote;
use App\Entity\PullRequest\Note;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

final class NoteAutoRemoveListenerTest extends KernelTestCase
{
    private EntityManagerInterface $em;

    protected function setUp(): void
    {
        parent::bootKernel();

        $this->em = self::getContainer()->get(EntityManagerInterface::class);
    }

    public function testRemoveDiscussionNoteAfterRemoveComment(): void
    {
        $pullRequest = $this->createPullRequest(); // method create entity
        $this->em->persist($pullRequest);
        $this->em->flush();

        $comment = (new Comment())
            ->setBody('text')
            ->setUser($this->em->getRepository(User::class)->findOneBy([]))
            ->setPullRequest($pullRequest);

        $note = (new DiscussionNote())
            ->setUser($comment->getUser())
            ->setPullRequest($pullRequest)
            ->addComment($comment);

        $this->em->persist($note);
        $this->em->flush();
        $this->em->refresh($note);

        $note = $this->em->getRepository(Note::class)->find($note->getId());
        $this->assertSame(1, $note->getComments()->count());
        $this->assertNotNull($note->getId());

        $this->em->remove($comment);
        $this->em->flush();

        $this->assertNull($note->getId());
    }
}
@onEXHovia
Copy link
Author

I looked at the dates, current behavior worked from version ^2.5 to 2.14.1, it may work in 2.15 or 2.16, I haven’t checked.

@beberlei
Copy link
Member

@onEXHovia can you please check if it works on 2.16?

@beberlei
Copy link
Member

Ah, I am pretty sure this is related to the commit order change, @mpdude could you chime in?

@onEXHovia
Copy link
Author

@onEXHovia can you please check if it works on 2.16?

Last work version 2.15.5, bug occurs in the version 2.16.0 and next releases

@mpdude
Copy link
Contributor

mpdude commented Dec 1, 2023

Will do

@mpdude
Copy link
Contributor

mpdude commented Dec 21, 2023

It may have to do with #10915, which moved the postRemove event dispatch to the point where all pending deletions have been executed. Maybe before that, the event was dispatched earlier so that the additional remove() call would update the list of pending entity removals and would happen within the currently running commit phase, without requiring an extra flush() call.

But, I am pretty sure #10763 plays a role as well:

In 2.16.0, the DiscussionNote::$comments collection still contains the already-removed Comment instance at the point where the postRemove event is dispatched. #10763 moved the clean-up for in-memory collections to a later stage for reasons I will have to recap. #11132 tries to address that.

mpdude added a commit that referenced this issue Dec 22, 2023
Make sure in-memory collections have removed entities unset before the `postRemove` event is dispatched. This is related to #11098, although by itself probably not going to fix the regression.

 #### Background

When an entity is removed from the database, the UoW also takes care of removing that entity from all in-memory collections. #10763 moved this cleanup and taking snapshots of updated collections to a very late stage during the commit phase, in order to avoid other side effects.

Now, part of the issue in #11098 is that `postRemove` event listeners will be called at a point where the database-level `DELETE` has happened (although the transaction has not yet been committed), but the in-memory collections have not yet been updated.

 #### Suggested change

This PR splits taking the new collection snapshots and updating in-memory collections. The in-memory update happens along with the database-level execution, collection snapshots still happen after transaction commit.
mpdude added a commit that referenced this issue Dec 22, 2023
Make sure in-memory collections have removed entities unset before the `postRemove` event is dispatched. This is related to #11098, although by itself probably not going to fix the regression.

 #### Background

When an entity is removed from the database, the UoW also takes care of removing that entity from all in-memory collections. #10763 moved this cleanup and taking snapshots of updated collections to a very late stage during the commit phase, in order to avoid other side effects.

Now, part of the issue in #11098 is that `postRemove` event listeners will be called at a point where the database-level `DELETE` has happened (although the transaction has not yet been committed), but the in-memory collections have not yet been updated.

 #### Suggested change

This PR splits taking the new collection snapshots and updating in-memory collections. The in-memory update happens along with the database-level execution, collection snapshots still happen after transaction commit.
@mpdude
Copy link
Contributor

mpdude commented Dec 22, 2023

@beberlei would you say this kind of event listener should work without an extra flush() call, even if that was the case previously?

@beberlei
Copy link
Member

The post* events are documented to be used only for non persistence related things, see https://www.doctrine-project.org/projects/doctrine-orm/en/2.17/reference/events.html#postupdate-postremove-postpersist

That this worked before was pure luck.

Could you not trigger this logic in preRemove? Instead of calling EntityManager::remove you could use entityManager->getUnitOfWork()->scheduleForDeletion to break/control the recursion

mpdude added a commit that referenced this issue Jan 2, 2024
Make sure in-memory collections have removed entities unset before the `postRemove` event is dispatched. This is related to #11098, although by itself probably not going to fix the regression.

 #### Background

When an entity is removed from the database, the UoW also takes care of removing that entity from all in-memory collections. #10763 moved this cleanup and taking snapshots of updated collections to a very late stage during the commit phase, in order to avoid other side effects.

Now, part of the issue in #11098 is that `postRemove` event listeners will be called at a point where the database-level `DELETE` has happened (although the transaction has not yet been committed), but the in-memory collections have not yet been updated.

 #### Suggested change

This PR splits taking the new collection snapshots and updating in-memory collections. The in-memory update happens along with the database-level execution, collection snapshots still happen after transaction commit.
mpdude added a commit that referenced this issue Jan 2, 2024
…e `postRemove` event is dispatched. This is related to #11098, although by itself probably not going to fix the regression.

#### Background 

When an entity is removed from the database, the UoW also takes care of removing that entity from all in-memory collections. #10763 moved this cleanup and taking snapshots of updated collections to a very late stage during the commit phase, in order to avoid other side effects.

Now, part of the issue in #11098 is that `postRemove` event listeners will be called at a point where the database-level `DELETE` has happened (although the transaction has not yet been committed), but the in-memory collections have not yet been updated.

#### Suggested change

This PR moves the code part that unsets removed entities from in-memory collections and takes collection snapshots (makes collections "clean") from after transaction commit to before the `postRemove` event. That brings the in-memory update closer to the database-level execution.

In the case of a transaction failure/abort, this leaves us with updated and snapshotted in-memory collections, but no database-level updates. But, at this point, probably things got inconsistent anyways, the EM will be closed and we need not be too worried about the state.
mpdude added a commit that referenced this issue Jan 2, 2024
…e `postRemove` event is dispatched. This is related to #11098, although by itself probably not going to fix the regression.

#### Background 

When an entity is removed from the database, the UoW also takes care of removing that entity from all in-memory collections. #10763 moved this cleanup and taking snapshots of updated collections to a very late stage during the commit phase, in order to avoid other side effects.

Now, part of the issue in #11098 is that `postRemove` event listeners will be called at a point where the database-level `DELETE` has happened (although the transaction has not yet been committed), but the in-memory collections have not yet been updated.

#### Suggested change

This PR moves the code part that unsets removed entities from in-memory collections and takes collection snapshots (makes collections "clean") from after transaction commit to before the `postRemove` event. That brings the in-memory update closer to the database-level execution.

In the case of a transaction failure/abort, this leaves us with updated and snapshotted in-memory collections, but no database-level updates. But, at this point, probably things got inconsistent anyways, the EM will be closed and we need not be too worried about the state.
@mpdude
Copy link
Contributor

mpdude commented Jan 2, 2024

#11146 improves the documentation a bit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants