Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scheduled entity in onFlush is different instance

I have a strange problem with \Doctrine\ORM\UnitOfWork::getScheduledEntityDeletions used inside onFlush event

foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
    if ($entity instanceof PollVote) {
        $arr = $entity->getAnswer()->getVotes()->toArray();

        dump($arr);
        dump($entity);

        dump(in_array($entity, $arr, true));
        dump(in_array($entity, $arr));
    }
}

And here is the result:

dump() output

So we see that the object is pointing to a different instance than the original, therefore in_array no longer yields expected results when used with stick comparison (AKA ===). Furthermore, the \DateTime object is pointing to a different instance.

The only possible explanation I found is the following (source):

Whenever you fetch an object from the database Doctrine will keep a copy of all the properties and associations inside the UnitOfWork. Because variables in the PHP language are subject to “copy-on-write” the memory usage of a PHP request that only reads objects from the database is the same as if Doctrine did not keep this variable copy. Only if you start changing variables PHP will create new variables internally that consume new memory.

However, I did not change anything (even the created field is kept as it is). The only operations that were preformed on entity are:

  1. \Doctrine\ORM\EntityRepository::findBy (fetching from DB)
  2. \Doctrine\Common\Persistence\ObjectManager::remove (scheduling for removal)
  3. $em->flush(); (triggering synchronization with DB)

Which leads me to think (I might be wrong) that the Doctrine's change tracking method has nothing to do with the issue that I'm experiencing. Which leads me to following questions:

  • What causes this?
  • How to reliably check if an entity scheduled for deletion is inside a collection (\Doctrine\Common\Collections\Collection::contains uses in_array with strict comparison) or which items in a collection are scheduled for deletion?
like image 888
Xymanek Avatar asked Dec 12 '16 13:12

Xymanek


1 Answers

The problem is that when you tell doctrine to remove entity, it is removed from identity map (here):

<?php

public function scheduleForDelete($entity)
{
    $oid = spl_object_hash($entity);

    // ....

    $this->removeFromIdentityMap($entity);

    // ...

    if ( ! isset($this->entityDeletions[$oid])) {
        $this->entityDeletions[$oid] = $entity;
        $this->entityStates[$oid]    = self::STATE_REMOVED;
    }
}

And when you do $entity->getAnswer()->getVotes(), it does the following:

  1. Load all votes from database
  2. For every vote, checks if it is in identity map, use old one
  3. If it is not in identity map, create new object

Try to call $entity->getAnswer()->getVotes() before you delete entity. If the problem disappears, then I am right. Of cause, I would not suggest this hack as a solution, just to make sure we understand what is going on under the hood.

UPD instead of $entity->getAnswer()->getVotes() you should probably do foreach for all votes, because of lazy loading. If you just call $entity->getAnswer()->getVotes(), Doctrine probably wouldn't do anytning, and will load them only when you start to iterate through them.

like image 100
Anton Serdyuk Avatar answered Oct 18 '22 18:10

Anton Serdyuk