Doctrine: many-to-many get removed associated object inside an event listener

蓝咒 提交于 2020-01-04 02:39:10

问题


I have many to many association like this

Entity Car
{
   /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="User", inversedBy="cars", fetch="EXTRA_LAZY")
     * @ORM\JoinTable(
     *  name="cars_users",
     *  joinColumns={
     *      @ORM\JoinColumn(name="car_id", referencedColumnName="id")
     *  },
     *  inverseJoinColumns={
     *      @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     *  }
     * )
     */
    protected $users; 

    /**
     * @param $user
      */
    public function removeUser(User $user)
    {
        if (!$this->users->contains($user)) {
            return;
        }

        $this->users->removeElement($user);
        $user->removeCar($this);

        return $this;
    }
}

Entity User 
{
/**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="Car", mappedBy="users")
     */
    protected $cars;
}

in my controller i have

$carA = getRandomCar();
$userB = getAUserThatBelongToCarA();

//remove association between Car A and User B
$carA->removesUser($userB);
doctrineUpdateCar($carA);

I have a Doctrine Listener CarListener i'm trying to find out if a User has been removed inside that listener and tell which User is that.

i tried preUpdate postUpdate postFlush but couldn't figure out a way to get $userB object inside the CarListener


回答1:


Event

I'd suggest you use the onflush event to have the listener/subscriber listen to, as it is dispatched after the changes are computed, but before any operations towards the database have been performed. This makes it ideal as you have potential access to all changes that are about to be made, but non of them have actually been written to the database yet (so you have the option to abort).

The listener/subscriber gets passed an OnFlushEventArgs object, from which you have access to the UnitOfWork. The Unit of work has 5 methods to gather the changes about to be made:

  • getScheduledEntityInsertions()
  • getScheduledEntityUpdates()
  • getScheduledEntityDeletions()
  • getScheduledCollectionUpdates()
  • getScheduledCollectionDeletions()

Collections

What you are doing is removing a User from the collection Car::$users and removing a Car from the collection User::cars.

So no entities are actually deleted, no fields are being changed. In stead you're changing collections. In other words, it's getScheduledCollectionUpdates() you want to be looking at.

The collection(s) returned by getScheduledCollectionUpdates() are an instance of PersistentCollection, which has these methods to tell you what has changed:

  • getInsertDiff()
  • getDeleteDiff()

In your case you can use getDeleteDiff() to find out which entity has been removed from the collection.




回答2:


Your preUpdate listener will receive a PreUpdateEventArgs object which you can use to get a lot of information about the changes to be persisted with methods like hasChangedField(). More info here:

http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/events.html#preupdate




回答3:


Doctrine really behaves strangely... Had the same problem in a more complex environment. So I decided to replicate the car and user class from your example. Used exactly the same entities except the removeUser function in the car entity. Think this would be much shorter:

class Car
{
    public function removeUser(User $user)
    {
        $this->users->removeElement($user);
    }
}

In the controller I got this:

$em = $this->getDoctrine()->getManager();

$cars = $this->getDoctrine()->getRepository('AppBundle:Car');
$users = $this->getDoctrine()->getRepository('AppBundle:User');

$car = $cars->findOneById(2);
$removeUser = $users->findOneById(3);

$car->removeUser($removeUser);
// $em->flush();

The user with the ID #3 is always removed from the car #2. The DELETE-statement is executed whether the entry in the car_user table exists or not. If I comment in the flush method on the EntityManager ($em) the onFlush event is called. The functions getScheduledEntityInsertions(), getScheduledEntityUpdates(), getScheduledEntityDeletions(), getScheduledCollectionDeletions() and getScheduledCollectionUpdates() on the UnitsOfWork object just returning empty arrays...

I think the simplest way would be to notify yourself about the changes. Found a simple way to track them by hand over an annotation. Here are the links:

  • Doctrine: Change Tracking Policies - Notify
  • Implementing the Notify ChangeTracking Policy
  • Doctrine2 - Trigger event on property change (PropertyChangeListener)


来源:https://stackoverflow.com/questions/33370173/doctrine-many-to-many-get-removed-associated-object-inside-an-event-listener

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!