Search code examples
phpsymfonydoctrine-ormbehat

Doctrine - Proxy entity is not properly updated


Scenario

  • I have an entity Device which has a bidirectional OneToMany relation with Attribute entity
  • I have a CLI process which listen for incoming requests and eventually does something sending back a response
  • I have a Behat test which checks if a Device attribute has been properly changed sending a request to said listener

Behat test

This is what happens:

  1. I get a Device object from DB through Doctrine repository - the device has an Attribute collection not yet initialized (lazy loaded)
  2. Since I need to know a property of a specific attribute, this specific attribute gets loaded from Doctrine and thus initialized - it's still a Proxy but has __isInitialized__ set as true
  3. I send a request to the listener asking a change on this specific device attribute
  4. The listener receives the request and change the attribute value as requested (e.g. attribute->setValue(true))
  5. The listener sends back a response to confirm the change
  6. Now the test fetches the attribute for given device through Attribute entity repository in order to check if its value has been properly changed in DB from the listener

The problem comes at point 6: attribute's value is wrong.

Now someone might think it's normal since the object is already loaded and marked as initialized in Behat test so Doctrine is not aware of changes done by concurrent processes and this would fine.

However what is really strange is that Doctrine is executing anyway a query at point 6 (checked with Doctrine SQL logging) and this query is returning a result set which contains different values for that device attribute (ie. a different value property) but the attribute variable is not automatically updated!

In fact in order to update it I have to call $em->refresh() on the attribute or clear the cache using $em->clear() before fetching attribute at point 6.

Is this a bug in Doctrine or what? Any thoughts?


Solution

  • I think I found the answer I was looking for.

    Debugging through Doctrine classes (specifically in UnitOfWork::createEntity()) I found out that Doctrine won't overwrite existing entity data unless the query explicitly specifies a Query::HINT_REFRESH hint:

    $result = $this->getEntityManager()->createQueryBuilder()
        ->select('e')
        ->from(MyEntity::class, 'e')
        ->getQuery()->setHint(Query::HINT_REFRESH, true)->getResult();
    

    This way Doctrine will overwrite entity properties if there's new DB data available for this entity even if the entity was already loaded in memory. Moreover this doesn't fire a new query like $em->refresh() does.

    In fact this is also confirmed by official documentation:

    Query::HINT_REFRESH - This query is used internally by EntityManager::refresh() and can be used in userland as well. If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the fields of the existing entity will be refreshed. In normal operation a result-set that loads data of an already existing entity is discarded in favor of the already existing entity.