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);
|
$collection->hydrateSet($index, $entity);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->uow->hydrationComplete();
|
||||||
|
|
||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,9 @@ class DefaultEntityHydrator implements EntityHydrator
|
|||||||
$this->uow->registerManaged($entity, $key->identifier, $data);
|
$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) {
|
if ($this->cacheLogger !== null) {
|
||||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||||
}
|
}
|
||||||
|
$this->uow->hydrationComplete();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -175,6 +176,7 @@ class DefaultQueryCache implements QueryCache
|
|||||||
if ($this->cacheLogger !== null) {
|
if ($this->cacheLogger !== null) {
|
||||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||||
}
|
}
|
||||||
|
$this->uow->hydrationComplete();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -196,6 +198,8 @@ class DefaultQueryCache implements QueryCache
|
|||||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
|
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->uow->hydrationComplete();
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,8 +145,10 @@ class ObjectHydrator extends AbstractHydrator
|
|||||||
$this->resultPointers = array();
|
$this->resultPointers = array();
|
||||||
|
|
||||||
if ($eagerLoad) {
|
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));
|
$this->class = $this->getClassMetadata(reset($this->_rsm->aliasMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function cleanup()
|
||||||
|
{
|
||||||
|
parent::cleanup();
|
||||||
|
|
||||||
|
$this->_uow->triggerEagerLoads();
|
||||||
|
$this->_uow->hydrationComplete();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -276,6 +276,15 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
*/
|
*/
|
||||||
protected $hasCache = false;
|
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.
|
* Initializes a new UnitOfWork instance, bound to the given EntityManager.
|
||||||
*
|
*
|
||||||
@ -2801,11 +2810,8 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($overrideLocalValues) {
|
if ($overrideLocalValues) {
|
||||||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad);
|
// defer invoking of postLoad event to hydration complete step
|
||||||
|
$this->deferredToInvokeLoadEventEntities[] = array($class, $entity);
|
||||||
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
|
|
||||||
$this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $entity;
|
return $entity;
|
||||||
@ -3379,4 +3385,23 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
|
|
||||||
return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
|
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();
|
$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()
|
private function loadFixture()
|
||||||
{
|
{
|
||||||
$user = new CmsUser;
|
$user = new CmsUser;
|
||||||
@ -248,3 +262,22 @@ class PostLoadListener
|
|||||||
echo 'Should never be called!';
|
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