DDC-3005 Defer invoking of postLoad event to the end of hydration cycle.
This feature makes guarantee, that postLoad event fires after all associations are populated
This commit is contained in:
parent
b9c0868f08
commit
f3b31c2807
@ -96,6 +96,8 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
$collection->hydrateSet($index, $entity);
|
||||
});
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +151,9 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
$this->uow->registerManaged($entity, $key->identifier, $data);
|
||||
}
|
||||
|
||||
return $this->uow->createEntity($entry->class, $data, $hints);
|
||||
$result = $this->uow->createEntity($entry->class, $data, $hints);
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +148,7 @@ class DefaultQueryCache implements QueryCache
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -175,6 +176,7 @@ class DefaultQueryCache implements QueryCache
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -196,6 +198,8 @@ class DefaultQueryCache implements QueryCache
|
||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
|
||||
}
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -145,8 +145,10 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$this->resultPointers = array();
|
||||
|
||||
if ($eagerLoad) {
|
||||
$this->_em->getUnitOfWork()->triggerEagerLoads();
|
||||
$this->_uow->triggerEagerLoads();
|
||||
}
|
||||
|
||||
$this->_uow->hydrationComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,6 +47,17 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
$this->class = $this->getClassMetadata(reset($this->_rsm->aliasMap));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function cleanup()
|
||||
{
|
||||
parent::cleanup();
|
||||
|
||||
$this->_uow->triggerEagerLoads();
|
||||
$this->_uow->hydrationComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -276,6 +276,15 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
protected $hasCache = false;
|
||||
|
||||
/**
|
||||
* Map of entities, loaded in current hydration cycle.
|
||||
* After hydration cycle is finished, some of events should be fired for this entities.
|
||||
* Array contains arrays of two values. First value is ClassMetadata object, second is entity object
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $deferredToInvokeLoadEventEntities = array();
|
||||
|
||||
/**
|
||||
* Initializes a new UnitOfWork instance, bound to the given EntityManager.
|
||||
*
|
||||
@ -2801,11 +2810,8 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
if ($overrideLocalValues) {
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad);
|
||||
|
||||
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
||||
$this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
|
||||
}
|
||||
// defer invoking of postLoad event to hydration complete step
|
||||
$this->deferredToInvokeLoadEventEntities[] = array($class, $entity);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
@ -3379,4 +3385,23 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
|
||||
* Unit of work able to fire deferred events, related to loading events here.
|
||||
*
|
||||
* @internal should be called internally from object hydrators
|
||||
*/
|
||||
public function hydrationComplete()
|
||||
{
|
||||
foreach ($this->deferredToInvokeLoadEventEntities as $metaAndEntity) {
|
||||
list($class, $entity) = $metaAndEntity;
|
||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad);
|
||||
|
||||
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
||||
$this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
|
||||
}
|
||||
}
|
||||
$this->deferredToInvokeLoadEventEntities = array();
|
||||
}
|
||||
}
|
||||
|
@ -203,6 +203,20 @@ class PostLoadEventTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$phonenumbersCol->first();
|
||||
}
|
||||
|
||||
public function testAssociationsArePopulatedWhenEventIsFired()
|
||||
{
|
||||
$checkerListener = new PostLoadListener_CheckAssociationsArePopulated();
|
||||
$this->_em->getEventManager()->addEventListener(array(Events::postLoad), $checkerListener);
|
||||
|
||||
$qb = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')->createQueryBuilder('u');
|
||||
$qb->leftJoin('u.email', 'email');
|
||||
$qb->addSelect('email');
|
||||
$qb->getQuery()->getSingleResult();
|
||||
|
||||
$this->assertTrue($checkerListener->checked, 'postLoad event is not invoked');
|
||||
$this->assertTrue($checkerListener->populated, 'Association of email is not populated in postLoad event');
|
||||
}
|
||||
|
||||
private function loadFixture()
|
||||
{
|
||||
$user = new CmsUser;
|
||||
@ -248,3 +262,22 @@ class PostLoadListener
|
||||
echo 'Should never be called!';
|
||||
}
|
||||
}
|
||||
|
||||
class PostLoadListener_CheckAssociationsArePopulated
|
||||
{
|
||||
public $checked = false;
|
||||
|
||||
public $populated = false;
|
||||
|
||||
public function postLoad(LifecycleEventArgs $event)
|
||||
{
|
||||
$object = $event->getObject();
|
||||
if ($object instanceof CmsUser) {
|
||||
if ($this->checked) {
|
||||
throw new \RuntimeException('Expected to be one user!');
|
||||
}
|
||||
$this->checked = true;
|
||||
$this->populated = null !== $object->getEmail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user