symfonydoctrine-ormdoctrinesubscribe

Doctrine trigger event on add and delete in many to many


In docs: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html

There are defined events. Among them:

preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed. It is not called for a DQL DELETE statement.

postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations. It is not called for a DQL DELETE statement.

But these events are not called on DQL DELETE statement.


I remove elements from many to many table. When In collection there are two elements and I remove one, then event postUpdate is called, but when i remove last element from collection postUpdate is not called. postRemove and preRemove are not called never.

I saw in _profiler that this query calls postUpdate:

DELETE FROM instance_user WHERE instance_id = ? AND user_id = ?

but this not:

DELETE FROM instance_user WHERE instance_id = ?

How can I call action on deletion and addition in of many to many table? I can trigger any addition and these deletions that are not deletions all of elements in collection. To fully working code I need trigger deletions without all parameters specified.


I found some related questions:


Solution

  • As doctrine 2.18, Events::preUpdate can be used.
    BUT:

    Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to correctly handle referential integrity at this point of the flush operation.

    That means, if you want to update $tag values, you CAN'T from there. I recommand to use Events::preFlush instead.

    Getting tags from this event, will return a PersistentCollection, e.g $post->getTags().

    BE CAREFUL, when new Post entity, and your collection is empty, getTags() will return a Doctrine\Common\Collections\ArrayCollection instead (which is empty).

    Finally, you can use getDeleteDiff() or getInsertDiff() to get your informations.

    <?php
    
    namespace App\EventListener;
    
    use App\Entity\Post;
    use App\Entity\Tag;
    use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
    use Doctrine\ORM\Event\PreFlushEventArgs;
    use Doctrine\ORM\Events;
    use Doctrine\ORM\PersistentCollection;
    
    #[AsEntityListener(event: Events::preFlush, entity: Post::class)]
    class PostListener
    {
        public function preFlush(Post $post, PreFlushEventArgs $event): void
        {
            /** @var ArrayCollection|PersistentCollection<int, Tag> $tags */
            $tags = $post->getTags();
            
            if ($tags instanceof PersistentCollection) {
               dump($tags->getDeleteDiff());
               dd($tags->getInsertDiff());
            }
        }
    }
    
    
    namespace App\Entity;
    
    use App\Repository\PostRepository;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\Common\Collections\Collection;
    use Doctrine\DBAL\Types\Types;
    use Doctrine\ORM\Mapping as ORM;
    
    #[ORM\Entity(repositoryClass: PostRepository::class)]
    class Post
    {
        #[ORM\Id]
        #[ORM\GeneratedValue]
        #[ORM\Column(type: Types::INTEGER)]
        private ?int $id = null;
    
        /**
         * @var Collection<int, Tag>
         */
        #[ORM\ManyToMany(targetEntity: Tag::class, cascade: ['persist'])]
        #[ORM\OrderBy(['name' => 'ASC'])]
        private Collection $tags;
    
        public function __construct()
        {
            $this->tags = new ArrayCollection();
        }
    
        public function getId(): ?int
        {
            return $this->id;
        }
    
        public function addTag(Tag ...$tags): void
        {
            foreach ($tags as $tag) {
                if (!$this->tags->contains($tag)) {
                    $this->tags->add($tag);
                }
            }
        }
    
        public function removeTag(Tag $tag): void
        {
            $this->tags->removeElement($tag);
        }
    
        /**
         * @return Collection<int, Tag>
         */
        public function getTags(): Collection
        {
            return $this->tags;
        }
    }