问题
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