1
0
mirror of synced 2025-01-31 04:21:44 +03:00

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:
Strate 2014-04-04 21:30:13 +04:00 committed by Marco Pivetta
parent b9c0868f08
commit f3b31c2807
7 changed files with 87 additions and 7 deletions

View File

@ -96,6 +96,8 @@ class DefaultCollectionHydrator implements CollectionHydrator
$collection->hydrateSet($index, $entity);
});
$this->uow->hydrationComplete();
return $list;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -145,8 +145,10 @@ class ObjectHydrator extends AbstractHydrator
$this->resultPointers = array();
if ($eagerLoad) {
$this->_em->getUnitOfWork()->triggerEagerLoads();
$this->_uow->triggerEagerLoads();
}
$this->_uow->hydrationComplete();
}
/**

View File

@ -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}
*/

View File

@ -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();
}
}

View File

@ -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();
}
}
}