From f3b31c2807aa9bec30872c7a7604a52da149bda9 Mon Sep 17 00:00:00 2001 From: Strate Date: Fri, 4 Apr 2014 21:30:13 +0400 Subject: [PATCH 01/19] 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 --- .../ORM/Cache/DefaultCollectionHydrator.php | 2 ++ .../ORM/Cache/DefaultEntityHydrator.php | 5 ++- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 4 +++ .../ORM/Internal/Hydration/ObjectHydrator.php | 4 ++- .../Hydration/SimpleObjectHydrator.php | 11 ++++++ lib/Doctrine/ORM/UnitOfWork.php | 35 ++++++++++++++++--- .../ORM/Functional/PostLoadEventTest.php | 33 +++++++++++++++++ 7 files changed, 87 insertions(+), 7 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php b/lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php index a74b88dfb..cedd891da 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php +++ b/lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php @@ -96,6 +96,8 @@ class DefaultCollectionHydrator implements CollectionHydrator $collection->hydrateSet($index, $entity); }); + $this->uow->hydrationComplete(); + return $list; } } diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php index 6f22900e1..b040dfa92 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php @@ -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; } } diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 927d46ebe..6d0c35492 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -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; } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index d4e84da92..ecbf9dcc4 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -145,8 +145,10 @@ class ObjectHydrator extends AbstractHydrator $this->resultPointers = array(); if ($eagerLoad) { - $this->_em->getUnitOfWork()->triggerEagerLoads(); + $this->_uow->triggerEagerLoads(); } + + $this->_uow->hydrationComplete(); } /** diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index ea2664291..69fd60ca4 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -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} */ diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index c0e681bce..fcb3cf652 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -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(); + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php index 6cc464bab..d041faafc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php @@ -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(); + } + } +} From 35ea399d3346ee0da483b72fa54b773953187150 Mon Sep 17 00:00:00 2001 From: Strate Date: Sat, 5 Apr 2014 10:45:00 +0400 Subject: [PATCH 02/19] DDC-3005 Defer invoking of postLoad event to the end of hydration cycle. 1. Refactor handling of hydration complete: delegate this task to special object 2. Write test case for situation, when inside postLoad listener other entity is loading. 3. Make test, written on second step, be able to pass :) --- .../ORM/Internal/HydrationCompleteHandler.php | 108 ++++++++++++++++++ lib/Doctrine/ORM/UnitOfWork.php | 32 ++---- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 5 +- .../ORM/Functional/PostLoadEventTest.php | 36 ++++++ 4 files changed, 160 insertions(+), 21 deletions(-) create mode 100644 lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php new file mode 100644 index 000000000..09ea8f9cd --- /dev/null +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -0,0 +1,108 @@ +. + */ + +namespace Doctrine\ORM\Internal; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Event\LifecycleEventArgs; +use Doctrine\ORM\Event\ListenersInvoker; +use Doctrine\ORM\Events; +use Doctrine\ORM\UnitOfWork; + +/** + * Class, which can handle completion of hydration cycle and produce some of tasks. + * In current implementation triggers deferred postLoad event. + * + * TODO Move deferred eager loading here + * + * @author Artur Eshenbrener + * @since 2.5 + */ +class HydrationCompleteHandler +{ + /** @var \Doctrine\ORM\UnitOfWork */ + private $uow; + + /** @var \Doctrine\ORM\Event\ListenersInvoker */ + private $listenersInvoker; + + /** @var \Doctrine\ORM\EntityManager */ + private $em; + + /** @var array */ + private $deferredPostLoadInvocations = array(); + + /** + * Constructor for this object + * + * @param UnitOfWork $uow + * @param \Doctrine\ORM\Event\ListenersInvoker $listenersInvoker + * @param \Doctrine\ORM\EntityManager $em + * + * @since 2.5 + */ + public function __construct(UnitOfWork $uow, ListenersInvoker $listenersInvoker, EntityManager $em) + { + $this->uow = $uow; + $this->listenersInvoker = $listenersInvoker; + $this->em = $em; + } + + /** + * Method schedules invoking of postLoad entity to the very end of current hydration cycle. + * + * @since 2.5 + * + * @param ClassMetadata $class + * @param object $entity + */ + public function deferPostLoadInvoking(ClassMetadata $class, $entity) + { + $this->deferredPostLoadInvocations[] = array($class, $entity); + } + + /** + * This method should me called after any hydration cycle completed. + * @since 2.5 + */ + public function hydrationComplete() + { + $this->invokeAllDeferredPostLoadEvents(); + } + + /** + * Method fires all deferred invocations of postLoad events + * @since 2.5 + */ + private function invokeAllDeferredPostLoadEvents() + { + $toInvoke = $this->deferredPostLoadInvocations; + $this->deferredPostLoadInvocations = array(); + foreach ($toInvoke as $classAndEntity) { + list($class, $entity) = $classAndEntity; + + $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); + } + } + } +} diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index fcb3cf652..0d126c84e 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM; use Doctrine\DBAL\LockMode; +use Doctrine\ORM\Internal\HydrationCompleteHandler; use Exception; use InvalidArgumentException; use UnexpectedValueException; @@ -277,13 +278,11 @@ 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 + * Helper for handling completion of hydration * - * @var array + * @var HydrationCompleteHandler */ - private $deferredToInvokeLoadEventEntities = array(); + private $hydrationCompleteHandler; /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. @@ -292,11 +291,12 @@ class UnitOfWork implements PropertyChangedListener */ public function __construct(EntityManager $em) { - $this->em = $em; - $this->evm = $em->getEventManager(); - $this->listenersInvoker = new ListenersInvoker($em); - $this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled(); - $this->identifierFlattener = new IdentifierFlattener($this, $em->getMetadataFactory()); + $this->em = $em; + $this->evm = $em->getEventManager(); + $this->listenersInvoker = new ListenersInvoker($em); + $this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled(); + $this->identifierFlattener = new IdentifierFlattener($this, $em->getMetadataFactory()); + $this->hydrationCompleteHandler = new HydrationCompleteHandler($this, $this->listenersInvoker, $em); } /** @@ -2811,7 +2811,7 @@ class UnitOfWork implements PropertyChangedListener if ($overrideLocalValues) { // defer invoking of postLoad event to hydration complete step - $this->deferredToInvokeLoadEventEntities[] = array($class, $entity); + $this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity); } return $entity; @@ -3394,14 +3394,6 @@ class UnitOfWork implements PropertyChangedListener */ 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(); + $this->hydrationCompleteHandler->hydrationComplete(); } } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index 2bae6ed4f..c95cce4ea 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -236,6 +236,9 @@ class CmsUser } } + /** + * @return CmsEmail + */ public function getEmail() { return $this->email; } public function setEmail(CmsEmail $email = null) { @@ -387,7 +390,7 @@ class CmsUser ) ) )); - + $metadata->addSqlResultSetMapping(array( 'name' => 'mappingMultipleJoinsEntityResults', 'entities' => array(array( diff --git a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php index d041faafc..302b3b129 100644 --- a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php @@ -1,6 +1,7 @@ assertTrue($checkerListener->populated, 'Association of email is not populated in postLoad event'); } + public function testEventRaisedCorrectTimesWhenOtherEntityLoadedInEventHandler() + { + $eventManager = $this->_em->getEventManager(); + $listener = new PostLoadListener_LoadEntityInEventHandler(); + $eventManager->addEventListener(array(Events::postLoad), $listener); + + $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); + $this->assertSame(1, $listener->countHandledEvents('Doctrine\Tests\Models\CMS\CmsUser'), 'Doctrine\Tests\Models\CMS\CmsUser should be handled once!'); + $this->assertSame(1, $listener->countHandledEvents('Doctrine\Tests\Models\CMS\CmsEmail'), '\Doctrine\Tests\Models\CMS\CmsEmail should be handled once!'); + } + private function loadFixture() { $user = new CmsUser; @@ -281,3 +293,27 @@ class PostLoadListener_CheckAssociationsArePopulated } } } + +class PostLoadListener_LoadEntityInEventHandler +{ + private $firedByClasses = array(); + + public function postLoad(LifecycleEventArgs $event) + { + $object = $event->getObject(); + $class = ClassUtils::getClass($object); + if (!isset($this->firedByClasses[$class])) { + $this->firedByClasses[$class] = 1; + } else { + $this->firedByClasses[$class]++; + } + if ($object instanceof CmsUser) { + $object->getEmail()->getEmail(); + } + } + + public function countHandledEvents($className) + { + return isset($this->firedByClasses[$className]) ? $this->firedByClasses[$className] : 0; + } +} From 367a79206d7d4b2a79e3397f06f1a03b1fc405e9 Mon Sep 17 00:00:00 2001 From: Strate Date: Sun, 21 Sep 2014 21:51:18 +0400 Subject: [PATCH 03/19] DDC-3005. Renamed class by removing underscore --- tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php index 302b3b129..30bd8edc2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php @@ -206,7 +206,7 @@ class PostLoadEventTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testAssociationsArePopulatedWhenEventIsFired() { - $checkerListener = new PostLoadListener_CheckAssociationsArePopulated(); + $checkerListener = new PostLoadListenerCheckAssociationsArePopulated(); $this->_em->getEventManager()->addEventListener(array(Events::postLoad), $checkerListener); $qb = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser')->createQueryBuilder('u'); @@ -221,7 +221,7 @@ class PostLoadEventTest extends \Doctrine\Tests\OrmFunctionalTestCase public function testEventRaisedCorrectTimesWhenOtherEntityLoadedInEventHandler() { $eventManager = $this->_em->getEventManager(); - $listener = new PostLoadListener_LoadEntityInEventHandler(); + $listener = new PostLoadListenerLoadEntityInEventHandler(); $eventManager->addEventListener(array(Events::postLoad), $listener); $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); @@ -275,7 +275,7 @@ class PostLoadListener } } -class PostLoadListener_CheckAssociationsArePopulated +class PostLoadListenerCheckAssociationsArePopulated { public $checked = false; @@ -294,7 +294,7 @@ class PostLoadListener_CheckAssociationsArePopulated } } -class PostLoadListener_LoadEntityInEventHandler +class PostLoadListenerLoadEntityInEventHandler { private $firedByClasses = array(); From 96184b95419e5cac443ad6c3e25f1d83b494994d Mon Sep 17 00:00:00 2001 From: Strate Date: Sun, 21 Sep 2014 22:36:12 +0400 Subject: [PATCH 04/19] DDC-3005.Removed warning in documentation --- docs/en/reference/events.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 1a1728392..2762e5fc7 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -184,13 +184,6 @@ the life-time of their registered entities. invoked, after all references to entities have been removed from the unit of work. This event is not a lifecycle callback. -.. warning:: - - Note that the postLoad event occurs for an entity - before any associations have been initialized. Therefore it is not - safe to access associations in a postLoad callback or event - handler. - .. warning:: Note that the postRemove event or any events triggered after an entity removal From 0c8a31bf2b4b5fe43fc1b38f4ed66637cd1a20cb Mon Sep 17 00:00:00 2001 From: Strate Date: Sun, 5 Oct 2014 11:04:44 +0400 Subject: [PATCH 05/19] DDC-3005. Revert empty line. Removed @since annotation --- .../ORM/Internal/Hydration/SimpleObjectHydrator.php | 2 +- lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 69fd60ca4..20c75ad30 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -90,7 +90,7 @@ class SimpleObjectHydrator extends AbstractHydrator if ($metaMappingDiscrColumnName = array_search($discrColumnName, $this->_rsm->metaMappings)) { $discrColumnName = $metaMappingDiscrColumnName; } - + if ( ! isset($sqlResult[$discrColumnName])) { throw HydrationException::missingDiscriminatorColumn($entityName, $discrColumnName, key($this->_rsm->aliasMap)); } diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index 09ea8f9cd..0137c5ba0 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -55,8 +55,6 @@ class HydrationCompleteHandler * @param UnitOfWork $uow * @param \Doctrine\ORM\Event\ListenersInvoker $listenersInvoker * @param \Doctrine\ORM\EntityManager $em - * - * @since 2.5 */ public function __construct(UnitOfWork $uow, ListenersInvoker $listenersInvoker, EntityManager $em) { @@ -68,8 +66,6 @@ class HydrationCompleteHandler /** * Method schedules invoking of postLoad entity to the very end of current hydration cycle. * - * @since 2.5 - * * @param ClassMetadata $class * @param object $entity */ @@ -80,7 +76,6 @@ class HydrationCompleteHandler /** * This method should me called after any hydration cycle completed. - * @since 2.5 */ public function hydrationComplete() { @@ -89,7 +84,6 @@ class HydrationCompleteHandler /** * Method fires all deferred invocations of postLoad events - * @since 2.5 */ private function invokeAllDeferredPostLoadEvents() { From 22e12e00439ac0f422e96a4e16648d6abe9e89dd Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 12 Jan 2015 23:50:16 +0100 Subject: [PATCH 06/19] #1001 DDC-3005 - coverage annotations, minor CS fixes --- lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php | 1 + lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 2 ++ tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php index b040dfa92..1190b38fd 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php @@ -152,6 +152,7 @@ class DefaultEntityHydrator implements EntityHydrator } $result = $this->uow->createEntity($entry->class, $data, $hints); + $this->uow->hydrationComplete(); return $result; diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 6d0c35492..87bb13663 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -148,6 +148,7 @@ class DefaultQueryCache implements QueryCache if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey); } + $this->uow->hydrationComplete(); return null; @@ -176,6 +177,7 @@ class DefaultQueryCache implements QueryCache if ($this->cacheLogger !== null) { $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey); } + $this->uow->hydrationComplete(); return null; diff --git a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php index 30bd8edc2..9dcff9cb6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/PostLoadEventTest.php @@ -204,6 +204,9 @@ class PostLoadEventTest extends \Doctrine\Tests\OrmFunctionalTestCase $phonenumbersCol->first(); } + /** + * @group DDC-3005 + */ public function testAssociationsArePopulatedWhenEventIsFired() { $checkerListener = new PostLoadListenerCheckAssociationsArePopulated(); @@ -218,6 +221,9 @@ class PostLoadEventTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($checkerListener->populated, 'Association of email is not populated in postLoad event'); } + /** + * @group DDC-3005 + */ public function testEventRaisedCorrectTimesWhenOtherEntityLoadedInEventHandler() { $eventManager = $this->_em->getEventManager(); From af9353957602266e0b1a5afabef1128d88ac137e Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:06:07 +0100 Subject: [PATCH 07/19] #1001 DDC-3005 - `HydrationCompleteHandler` should accept `EntityManagerInterface` instances --- lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index 0137c5ba0..1f4c4c08c 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM\Internal; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\ListenersInvoker; use Doctrine\ORM\Events; @@ -35,7 +36,7 @@ use Doctrine\ORM\UnitOfWork; * @author Artur Eshenbrener * @since 2.5 */ -class HydrationCompleteHandler +final class HydrationCompleteHandler { /** @var \Doctrine\ORM\UnitOfWork */ private $uow; @@ -43,7 +44,7 @@ class HydrationCompleteHandler /** @var \Doctrine\ORM\Event\ListenersInvoker */ private $listenersInvoker; - /** @var \Doctrine\ORM\EntityManager */ + /** @var \Doctrine\ORM\EntityManagerInterface */ private $em; /** @var array */ @@ -54,9 +55,9 @@ class HydrationCompleteHandler * * @param UnitOfWork $uow * @param \Doctrine\ORM\Event\ListenersInvoker $listenersInvoker - * @param \Doctrine\ORM\EntityManager $em + * @param \Doctrine\ORM\EntityManagerInterface $em */ - public function __construct(UnitOfWork $uow, ListenersInvoker $listenersInvoker, EntityManager $em) + public function __construct(UnitOfWork $uow, ListenersInvoker $listenersInvoker, EntityManagerInterface $em) { $this->uow = $uow; $this->listenersInvoker = $listenersInvoker; From fccd08afa5015b18f010b6a6c43d38c6cd446606 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:29:50 +0100 Subject: [PATCH 08/19] #1001 DDC-3005 - Basic coverage for the `HydrationCompleteHandler` --- .../Internal/HydrationCompleteHandlerTest.php | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php diff --git a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php new file mode 100644 index 000000000..60ad85116 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php @@ -0,0 +1,103 @@ +. + */ + +namespace Doctrine\Tests\ORM\Internal; + +use Doctrine\Common\Persistence\Event\LifecycleEventArgs; +use Doctrine\ORM\Event\ListenersInvoker; +use Doctrine\ORM\Events; +use Doctrine\ORM\Internal\HydrationCompleteHandler; +use PHPUnit_Framework_TestCase; +use stdClass; + +/** + * Tests for {@see \Doctrine\ORM\Internal\HydrationCompleteHandler} + * + * @covers \Doctrine\ORM\Internal\HydrationCompleteHandler + */ +class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \Doctrine\ORM\UnitOfWork|\PHPUnit_Framework_MockObject_MockObject + */ + private $unitOfWork; + + /** + * @var \Doctrine\ORM\Event\ListenersInvoker|\PHPUnit_Framework_MockObject_MockObject + */ + private $listenersInvoker; + + /** + * @var \Doctrine\ORM\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $entityManager; + + /** + * @var HydrationCompleteHandler + */ + private $handler; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->unitOfWork = $this->getMock('Doctrine\ORM\UnitOfWork', array(), array(), '', false); + $this->listenersInvoker = $this->getMock('Doctrine\ORM\Event\ListenersInvoker', array(), array(), '', false); + $this->entityManager = $this->getMock('Doctrine\ORM\EntityManagerInterface'); + $this->handler = new HydrationCompleteHandler( + $this->unitOfWork, + $this->listenersInvoker, + $this->entityManager + ); + } + + public function testDefersPostLoadOfEntity() + { + /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ + $metadata = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); + $entity = new stdClass(); + $entityManager = $this->entityManager; + + $this + ->listenersInvoker + ->expects($this->any()) + ->method('getSubscribedSystems') + ->with($metadata) + ->will($this->returnValue(ListenersInvoker::INVOKE_LISTENERS)); + + $this->handler->deferPostLoadInvoking($metadata, $entity); + + $this + ->listenersInvoker + ->expects($this->once()) + ->method('invoke') + ->with( + $metadata, + Events::postLoad, + $entity, + $this->callback(function (LifecycleEventArgs $args) use ($entityManager, $entity) { + return $entity === $args->getEntity() && $entityManager === $args->getObjectManager(); + }), + ListenersInvoker::INVOKE_LISTENERS + ); + + $this->handler->hydrationComplete(); + } +} From 516d04c3911a5b18dca1d435506d5c3eb1ebd8a6 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:31:32 +0100 Subject: [PATCH 09/19] #1001 DDC-3005 - Removing useless dependency from the `HydrationCompleteHandler` to the `UnitOfWork` --- .../ORM/Internal/HydrationCompleteHandler.php | 8 +------- lib/Doctrine/ORM/UnitOfWork.php | 2 +- .../ORM/Internal/HydrationCompleteHandlerTest.php | 12 +----------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index 1f4c4c08c..4d6851f92 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -20,12 +20,10 @@ namespace Doctrine\ORM\Internal; use Doctrine\Common\Persistence\Mapping\ClassMetadata; -use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\ListenersInvoker; use Doctrine\ORM\Events; -use Doctrine\ORM\UnitOfWork; /** * Class, which can handle completion of hydration cycle and produce some of tasks. @@ -38,9 +36,6 @@ use Doctrine\ORM\UnitOfWork; */ final class HydrationCompleteHandler { - /** @var \Doctrine\ORM\UnitOfWork */ - private $uow; - /** @var \Doctrine\ORM\Event\ListenersInvoker */ private $listenersInvoker; @@ -53,11 +48,10 @@ final class HydrationCompleteHandler /** * Constructor for this object * - * @param UnitOfWork $uow * @param \Doctrine\ORM\Event\ListenersInvoker $listenersInvoker * @param \Doctrine\ORM\EntityManagerInterface $em */ - public function __construct(UnitOfWork $uow, ListenersInvoker $listenersInvoker, EntityManagerInterface $em) + public function __construct(ListenersInvoker $listenersInvoker, EntityManagerInterface $em) { $this->uow = $uow; $this->listenersInvoker = $listenersInvoker; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 0d126c84e..758754202 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -296,7 +296,7 @@ class UnitOfWork implements PropertyChangedListener $this->listenersInvoker = new ListenersInvoker($em); $this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled(); $this->identifierFlattener = new IdentifierFlattener($this, $em->getMetadataFactory()); - $this->hydrationCompleteHandler = new HydrationCompleteHandler($this, $this->listenersInvoker, $em); + $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em); } /** diff --git a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php index 60ad85116..14b930db2 100644 --- a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php +++ b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php @@ -33,11 +33,6 @@ use stdClass; */ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase { - /** - * @var \Doctrine\ORM\UnitOfWork|\PHPUnit_Framework_MockObject_MockObject - */ - private $unitOfWork; - /** * @var \Doctrine\ORM\Event\ListenersInvoker|\PHPUnit_Framework_MockObject_MockObject */ @@ -58,14 +53,9 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase */ protected function setUp() { - $this->unitOfWork = $this->getMock('Doctrine\ORM\UnitOfWork', array(), array(), '', false); $this->listenersInvoker = $this->getMock('Doctrine\ORM\Event\ListenersInvoker', array(), array(), '', false); $this->entityManager = $this->getMock('Doctrine\ORM\EntityManagerInterface'); - $this->handler = new HydrationCompleteHandler( - $this->unitOfWork, - $this->listenersInvoker, - $this->entityManager - ); + $this->handler = new HydrationCompleteHandler($this->listenersInvoker, $this->entityManager); } public function testDefersPostLoadOfEntity() From 89530f88f742d5554a87c6117b824c8fc102c944 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:32:10 +0100 Subject: [PATCH 10/19] #1001 DDC-3005 - Removing dead assignment `HydrationCompleteHandler#__construct()` --- lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index 4d6851f92..9fb963a39 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -53,9 +53,8 @@ final class HydrationCompleteHandler */ public function __construct(ListenersInvoker $listenersInvoker, EntityManagerInterface $em) { - $this->uow = $uow; $this->listenersInvoker = $listenersInvoker; - $this->em = $em; + $this->em = $em; } /** From 7b81cfb6eafc1b66bc6753af6808bc59ae7f5a36 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:33:15 +0100 Subject: [PATCH 11/19] #1001 DDC-3005 - Minor CS fixes in `HydrationCompleteHandler` --- .../ORM/Internal/HydrationCompleteHandler.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index 9fb963a39..d6227e126 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -36,20 +36,26 @@ use Doctrine\ORM\Events; */ final class HydrationCompleteHandler { - /** @var \Doctrine\ORM\Event\ListenersInvoker */ + /** + * @var ListenersInvoker + */ private $listenersInvoker; - /** @var \Doctrine\ORM\EntityManagerInterface */ + /** + * @var EntityManagerInterface + */ private $em; - /** @var array */ + /** + * @var array[] + */ private $deferredPostLoadInvocations = array(); /** * Constructor for this object * - * @param \Doctrine\ORM\Event\ListenersInvoker $listenersInvoker - * @param \Doctrine\ORM\EntityManagerInterface $em + * @param ListenersInvoker $listenersInvoker + * @param EntityManagerInterface $em */ public function __construct(ListenersInvoker $listenersInvoker, EntityManagerInterface $em) { @@ -61,7 +67,7 @@ final class HydrationCompleteHandler * Method schedules invoking of postLoad entity to the very end of current hydration cycle. * * @param ClassMetadata $class - * @param object $entity + * @param object $entity */ public function deferPostLoadInvoking(ClassMetadata $class, $entity) { From 833058fd2e48bc78ec87fdbc0e8da1c61a8bb1f1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:36:17 +0100 Subject: [PATCH 12/19] #1001 DDC-3005 - Testing `HydrationCompleteHandler` when no events should be triggered --- .../Internal/HydrationCompleteHandlerTest.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php index 14b930db2..10c2fdf76 100644 --- a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php +++ b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php @@ -90,4 +90,24 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase $this->handler->hydrationComplete(); } + + public function testSkipsDeferredPostLoadOfMetadataWithNoInvokedListeners() + { + /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ + $metadata = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); + $entity = new stdClass(); + + $this + ->listenersInvoker + ->expects($this->any()) + ->method('getSubscribedSystems') + ->with($metadata) + ->will($this->returnValue(ListenersInvoker::INVOKE_NONE)); + + $this->handler->deferPostLoadInvoking($metadata, $entity); + + $this->listenersInvoker->expects($this->never())->method('invoke'); + + $this->handler->hydrationComplete(); + } } From 948d6c2b9f86ba580cb73d23bdb57386389e076d Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:39:28 +0100 Subject: [PATCH 13/19] #1001 DDC-3005 - Testing `HydrationCompleteHandler` against all possible `ListenersInvoker` flags --- .../Internal/HydrationCompleteHandlerTest.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php index 10c2fdf76..f5ee96f0e 100644 --- a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php +++ b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php @@ -58,7 +58,10 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase $this->handler = new HydrationCompleteHandler($this->listenersInvoker, $this->entityManager); } - public function testDefersPostLoadOfEntity() + /** + * @dataProvider testGetValidListenerInvocationFlags + */ + public function testDefersPostLoadOfEntity($listenersFlag) { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ $metadata = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); @@ -70,7 +73,7 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase ->expects($this->any()) ->method('getSubscribedSystems') ->with($metadata) - ->will($this->returnValue(ListenersInvoker::INVOKE_LISTENERS)); + ->will($this->returnValue($listenersFlag)); $this->handler->deferPostLoadInvoking($metadata, $entity); @@ -85,7 +88,7 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase $this->callback(function (LifecycleEventArgs $args) use ($entityManager, $entity) { return $entity === $args->getEntity() && $entityManager === $args->getObjectManager(); }), - ListenersInvoker::INVOKE_LISTENERS + $listenersFlag ); $this->handler->hydrationComplete(); @@ -110,4 +113,16 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase $this->handler->hydrationComplete(); } + + public function testGetValidListenerInvocationFlags() + { + return array( + array(ListenersInvoker::INVOKE_LISTENERS), + array(ListenersInvoker::INVOKE_CALLBACKS), + array(ListenersInvoker::INVOKE_MANAGER), + array(ListenersInvoker::INVOKE_LISTENERS | ListenersInvoker::INVOKE_CALLBACKS), + array(ListenersInvoker::INVOKE_LISTENERS | ListenersInvoker::INVOKE_MANAGER), + array(ListenersInvoker::INVOKE_LISTENERS | ListenersInvoker::INVOKE_CALLBACKS | ListenersInvoker::INVOKE_MANAGER), + ); + } } From 29d4d342bd0e28eb99e7c4197ab292109b1066e4 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:45:07 +0100 Subject: [PATCH 14/19] #1001 DDC-3005 - Testing `HydrationCompleteHandler` with multiple deferred entities `postLoad`s --- .../Internal/HydrationCompleteHandlerTest.php | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php index f5ee96f0e..bb039eb66 100644 --- a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php +++ b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php @@ -60,6 +60,8 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase /** * @dataProvider testGetValidListenerInvocationFlags + * + * @param int $listenersFlag */ public function testDefersPostLoadOfEntity($listenersFlag) { @@ -93,6 +95,48 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase $this->handler->hydrationComplete(); } + /** + * @dataProvider testGetValidListenerInvocationFlags + * + * @param int $listenersFlag + */ + public function testDefersMultiplePostLoadOfEntity($listenersFlag) + { + /* @var $metadata1 \Doctrine\ORM\Mapping\ClassMetadata */ + /* @var $metadata2 \Doctrine\ORM\Mapping\ClassMetadata */ + $metadata1 = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); + $metadata2 = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); + $entity1 = new stdClass(); + $entity2 = new stdClass(); + $entityManager = $this->entityManager; + + $this + ->listenersInvoker + ->expects($this->any()) + ->method('getSubscribedSystems') + ->with($this->logicalOr($metadata1, $metadata2)) + ->will($this->returnValue($listenersFlag)); + + $this->handler->deferPostLoadInvoking($metadata1, $entity1); + $this->handler->deferPostLoadInvoking($metadata2, $entity2); + + $this + ->listenersInvoker + ->expects($this->exactly(2)) + ->method('invoke') + ->with( + $this->logicalOr($metadata1, $metadata2), + Events::postLoad, + $this->logicalOr($entity1, $entity2), + $this->callback(function (LifecycleEventArgs $args) use ($entityManager, $entity1, $entity2) { + return in_array($args->getEntity(), array($entity1, $entity2), true) + && $entityManager === $args->getObjectManager(); + }), + $listenersFlag + ); + + $this->handler->hydrationComplete(); + } public function testSkipsDeferredPostLoadOfMetadataWithNoInvokedListeners() { From 8c54a65aa5f50eff52db88fec961fc953fd87d8a Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:48:53 +0100 Subject: [PATCH 15/19] #1001 DDC-3005 - Verifying `HydrationCompleteHandler` skips over registered deferred loads that were already handled by `hydrationComplete` --- .../Internal/HydrationCompleteHandlerTest.php | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php index bb039eb66..b767bd225 100644 --- a/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php +++ b/tests/Doctrine/Tests/ORM/Internal/HydrationCompleteHandlerTest.php @@ -95,6 +95,33 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase $this->handler->hydrationComplete(); } + + /** + * @dataProvider testGetValidListenerInvocationFlags + * + * @param int $listenersFlag + */ + public function testDefersPostLoadOfEntityOnlyOnce($listenersFlag) + { + /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ + $metadata = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); + $entity = new stdClass(); + + $this + ->listenersInvoker + ->expects($this->any()) + ->method('getSubscribedSystems') + ->with($metadata) + ->will($this->returnValue($listenersFlag)); + + $this->handler->deferPostLoadInvoking($metadata, $entity); + + $this->listenersInvoker->expects($this->once())->method('invoke'); + + $this->handler->hydrationComplete(); + $this->handler->hydrationComplete(); + } + /** * @dataProvider testGetValidListenerInvocationFlags * @@ -141,8 +168,8 @@ class HydrationCompleteHandlerTest extends PHPUnit_Framework_TestCase public function testSkipsDeferredPostLoadOfMetadataWithNoInvokedListeners() { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ - $metadata = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); - $entity = new stdClass(); + $metadata = $this->getMock('Doctrine\ORM\Mapping\ClassMetadata', array(), array(), '', false); + $entity = new stdClass(); $this ->listenersInvoker From 730c2a81f766a5447b13c43515ccda6da784921b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:50:05 +0100 Subject: [PATCH 16/19] #1001 DDC-3005 - `HydrationCompleteHandler` static introspection cleanups, as well as memory and performance improvements --- .../ORM/Internal/HydrationCompleteHandler.php | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index d6227e126..ad80f7112 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -19,7 +19,7 @@ namespace Doctrine\ORM\Internal; -use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\ListenersInvoker; @@ -29,8 +29,6 @@ use Doctrine\ORM\Events; * Class, which can handle completion of hydration cycle and produce some of tasks. * In current implementation triggers deferred postLoad event. * - * TODO Move deferred eager loading here - * * @author Artur Eshenbrener * @since 2.5 */ @@ -71,7 +69,13 @@ final class HydrationCompleteHandler */ public function deferPostLoadInvoking(ClassMetadata $class, $entity) { - $this->deferredPostLoadInvocations[] = array($class, $entity); + $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad); + + if ($invoke === ListenersInvoker::INVOKE_NONE) { + return; + } + + $this->deferredPostLoadInvocations[] = array($class, $invoke, $entity); } /** @@ -87,16 +91,13 @@ final class HydrationCompleteHandler */ private function invokeAllDeferredPostLoadEvents() { - $toInvoke = $this->deferredPostLoadInvocations; + $toInvoke = $this->deferredPostLoadInvocations; $this->deferredPostLoadInvocations = array(); + foreach ($toInvoke as $classAndEntity) { - list($class, $entity) = $classAndEntity; + list($class, $invoke, $entity) = $classAndEntity; - $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->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } } } From c9ccd91421717c689a834dad46fec9bfaab343e0 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:50:19 +0100 Subject: [PATCH 17/19] #1001 DDC-3005 - optimized `HydrationCompleteHandler` imports --- lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index ad80f7112..077615f29 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -19,11 +19,11 @@ namespace Doctrine\ORM\Internal; -use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\ListenersInvoker; use Doctrine\ORM\Events; +use Doctrine\ORM\Mapping\ClassMetadata; /** * Class, which can handle completion of hydration cycle and produce some of tasks. From 45221b195191379d755c17f1b46ee025c93d294d Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:51:44 +0100 Subject: [PATCH 18/19] #1001 DDC-3005 - removed `HydrationCompleteHandler#invokeAllDeferredPostLoadEvents()` (useless method call indirection) --- lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index 077615f29..1f0155123 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -80,16 +80,10 @@ final class HydrationCompleteHandler /** * This method should me called after any hydration cycle completed. - */ - public function hydrationComplete() - { - $this->invokeAllDeferredPostLoadEvents(); - } - - /** + * * Method fires all deferred invocations of postLoad events */ - private function invokeAllDeferredPostLoadEvents() + public function hydrationComplete() { $toInvoke = $this->deferredPostLoadInvocations; $this->deferredPostLoadInvocations = array(); From d09280a1dcc6e0542866ee385b4dc8da0ebdcdb1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 00:52:12 +0100 Subject: [PATCH 19/19] #1001 DDC-3005 - `HydrationCompleteHandler` cs fixes (line-wrap) --- lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php index 1f0155123..4da71cefa 100644 --- a/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php +++ b/lib/Doctrine/ORM/Internal/HydrationCompleteHandler.php @@ -91,7 +91,13 @@ final class HydrationCompleteHandler foreach ($toInvoke as $classAndEntity) { list($class, $invoke, $entity) = $classAndEntity; - $this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); + $this->listenersInvoker->invoke( + $class, + Events::postLoad, + $entity, + new LifecycleEventArgs($entity, $this->em), + $invoke + ); } } }