What's the proper use of $unitOfWork->getSched

2019-03-31 03:47发布

问题:

I'm trying to detect changes in a many-to-many relation in an onFlush event.

If new entities are added to the relation or the relation is updated (always keeping an element), I can detect changes using $unitOfWork->getScheduledCollectionUpdates() and then check for getInsertDiff() or getDeleteDiff(). So far so good.

The problem comes when I take all the entities out of the relation: "There were two related entities before but there are NO related entities now."

When the relation is left empty I can access $unitOfWork->getScheduledCollectionDeletions(), but there is no way of knowing which entities were deleted:

  • getDeleteDiff() for this collections doesn't tell anything.
  • getSnapshot() doesn't tell me which entities were there before

How should I know which entities were taken out of the many-to-many relation?


I've added a Gist with the full implementation: everything works ok (it may need some optimization) except $uow->getScheduledCollectionDeletions() (line 101)

https://gist.github.com/eillarra/5127606

回答1:

The cause of this problem is twofold:

1) When the method clear() is called on a Doctrine\ORM\PersistentCollection, it will:

  1. clear its internal collection of entities.
  2. call scheduleCollectionDeletion() on the Doctrine\ORM\UnitOfWork.
  3. take a new snapshot of itself.

Number 2 is the reason your collection shows up in $uow->getScheduledCollectionDeletions() (and not in $uow->getScheduledCollectionUpdates()). Number 3 is the reason why you cannot determine what was in the collection before it was cleared.

2) When using the Symfony2 Form component, specifically the ChoiceType or CollectionType types in combination with the option multiple, that clear() method will get called when all entities should be removed from the collection.

This is due to the MergeDoctrineCollectionListener which is added here: https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php#L55

This is done as optimization: It's faster to clear a collection this way, in stead of checking which entities should be removed from it.

I can think of two possible solutions:

1) Create a fork symfony/symfony and implement an option in order to not add the MergeDoctrineCollectionListener. Maybe something like no_clear to prevent the listener from being added. This won't introduce a BC break and would solve your problem because the clear() method of a collection won't get called when all entities should be removed.

2) Redesign your counter: Maybe also listen to the OnLoad event which can count the amount of entities in the collection at the time it's fetched from the db. That way your OnFlush listener can use that number to know how many entities where removed from the collection when it was cleared.