From aa4796cd0dd5fabce2bf94bdbc8cbf8ba062f702 Mon Sep 17 00:00:00 2001 From: Lukasz Cybula Date: Wed, 13 Mar 2013 09:27:43 +0100 Subject: [PATCH 1/8] Moved postLoad dispatching from UnitOfWork to object hydrators --- .../ORM/Event/PostLoadEventDispatcher.php | 123 ++++++++++++++++++ .../ORM/Internal/Hydration/ObjectHydrator.php | 22 +++- .../Hydration/SimpleObjectHydrator.php | 34 ++++- .../ORM/Functional/LifecycleCallbackTest.php | 74 ++++++++++- 4 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php diff --git a/lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php b/lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php new file mode 100644 index 000000000..ea5e10869 --- /dev/null +++ b/lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php @@ -0,0 +1,123 @@ +. + */ + +namespace Doctrine\ORM\Event; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Events; +use Doctrine\ORM\Query; + +/** + * Dispatcher for postLoad event on entities used during object hydration. + * + * @author Lukasz Cybula + * @since 2.4 + */ +class PostLoadEventDispatcher +{ + /** + * Entity Manager + * + * @var \Doctrine\ORM\EntityManager + */ + private $entityManager; + + /** + * Listeners Invoker + * + * @var \Doctrine\ORM\Event\ListenersInvoker + */ + private $invoker; + + /** + * Metadata Factory + * + * @var \Doctrine\ORM\Mapping\ClassMetadataFactory + */ + private $metadataFactory; + + /** + * The query hints + * + * @var array + */ + private $hints = array(); + + /** + * Entities enqueued for postLoad dispatching + * + * @var array + */ + private $entities = array(); + + /** + * Constructs a new dispatcher instance + * + * @param EntityManager $em + * @param array $hints + */ + public function __construct(EntityManager $em, array $hints = array()) + { + $this->entityManager = $em; + $this->metadataFactory = $em->getMetadataFactory(); + $this->invoker = $this->entityManager->getUnitOfWork()->getListenersInvoker(); + $this->hints = $hints; + } + + /** + * Dispatches postLoad event for specified entity or enqueues it for later dispatching + * + * @param object $entity + */ + public function dispatchPostLoad($entity) + { + $className = get_class($entity); + $meta = $this->metadataFactory->getMetadataFor($className); + $invoke = $this->invoker->getSubscribedSystems($meta, Events::postLoad); + + if ($invoke === ListenersInvoker::INVOKE_NONE) { + return; + } + + if (isset($this->hints[Query::HINT_INTERNAL_ITERATION])) { + $this->invoker->invoke($meta, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->entityManager), $invoke); + } else { + if ( ! isset($this->entities[$className])) { + $this->entities[$className] = array(); + } + + $this->entities[$className][] = $entity; + } + } + + /** + * Dispatches all enqueued postLoad events + */ + public function dispatchEnqueuedPostLoadEvents() + { + foreach ($this->entities as $class => $entities) { + $meta = $this->metadataFactory->getMetadataFor($class); + $invoke = $this->invoker->getSubscribedSystems($meta, Events::postLoad); + + foreach ($entities as $entity) { + $this->invoker->invoke($meta, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->entityManager), $invoke); + } + } + } +} diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index ecbf9dcc4..e2d119f66 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -24,6 +24,9 @@ use PDO; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Query; +use Doctrine\ORM\Events; +use Doctrine\ORM\Event\LifecycleEventArgs; +use Doctrine\ORM\Event\PostLoadEventDispatcher; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Proxy\Proxy; @@ -74,11 +77,18 @@ class ObjectHydrator extends AbstractHydrator */ private $existingCollections = array(); + /** + * @var \Doctrine\ORM\Event\PostLoadEventDispatcher + */ + private $postLoadEventDispatcher; + /** * {@inheritdoc} */ protected function prepare() { + $this->postLoadEventDispatcher = new PostLoadEventDispatcher($this->_em, $this->_hints); + if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) { $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true; } @@ -144,6 +154,8 @@ class ObjectHydrator extends AbstractHydrator $this->existingCollections = $this->resultPointers = array(); + unset($this->postLoadEventDispatcher); + if ($eagerLoad) { $this->_uow->triggerEagerLoads(); } @@ -167,6 +179,8 @@ class ObjectHydrator extends AbstractHydrator $coll->takeSnapshot(); } + $this->postLoadEventDispatcher->dispatchEnqueuedPostLoadEvents(); + return $result; } @@ -267,7 +281,13 @@ class ObjectHydrator extends AbstractHydrator $this->_hints['fetchAlias'] = $dqlAlias; - return $this->_uow->createEntity($className, $data, $this->_hints); + $created = false; + $entity = $this->_uow->createEntity($className, $data, $this->_hints, $created); + if ($created) { + $this->postLoadEventDispatcher->dispatchPostLoad($entity); + } + + return $entity; } /** diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 20c75ad30..6254d35b4 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -23,6 +23,10 @@ use PDO; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; +use Doctrine\ORM\Events; +use Doctrine\ORM\Event\LifecycleEventArgs; +use Doctrine\ORM\Event\ListenersInvoker; +use Doctrine\ORM\Event\PostLoadEventDispatcher; class SimpleObjectHydrator extends AbstractHydrator { @@ -31,11 +35,23 @@ class SimpleObjectHydrator extends AbstractHydrator */ private $class; + /** + * @var \Doctrine\ORM\Event\PostLoadEventDispatcher + */ + private $postLoadEventDispatcher; + + /** + * @var array + */ + private $hydratedObjects = array(); + /** * {@inheritdoc} */ protected function prepare() { + $this->postLoadEventDispatcher = new PostLoadEventDispatcher($this->_em, $this->_hints); + if (count($this->_rsm->aliasMap) !== 1) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result."); } @@ -71,9 +87,21 @@ class SimpleObjectHydrator extends AbstractHydrator $this->_em->getUnitOfWork()->triggerEagerLoads(); + $this->postLoadEventDispatcher->dispatchEnqueuedPostLoadEvents(); + return $result; } + /** + * {@inheritdoc} + */ + protected function cleanup() + { + parent::cleanup(); + + unset($this->postLoadEventDispatcher); + } + /** * {@inheritdoc} */ @@ -141,7 +169,11 @@ class SimpleObjectHydrator extends AbstractHydrator } $uow = $this->_em->getUnitOfWork(); - $entity = $uow->createEntity($entityName, $data, $this->_hints); + $created = false; + $entity = $uow->createEntity($entityName, $data, $this->_hints, $created); + if ($created) { + $this->postLoadEventDispatcher->dispatchPostLoad($entity); + } $result[] = $entity; } diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index af732384a..f1c84aa94 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -150,6 +150,65 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($e2->prePersistCallbackInvoked); } + public function testCascadedEntitiesLoadedInPostLoad() + { + $e1 = new LifecycleCallbackTestEntity(); + $e2 = new LifecycleCallbackTestEntity(); + + $c = new LifecycleCallbackCascader(); + $this->_em->persist($c); + + $c->entities[] = $e1; + $c->entities[] = $e2; + $e1->cascader = $c; + $e2->cascader = $c; + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery(' +SELECT e, c +FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e + LEFT JOIN e.cascader AS c +WHERE e.id IN ('.$e1->getId().', '.$e2->getId().')'); + $entities = $query->execute(null, \Doctrine\ORM\Query::HYDRATE_OBJECT); + + $this->assertTrue(current($entities)->postLoadCallbackInvoked); + $this->assertTrue(current($entities)->postLoadCascaderNotNull); + $this->assertTrue(current($entities)->cascader->postLoadCallbackInvoked); + $this->assertEquals(current($entities)->cascader->postLoadEntitiesCount, 2); + } + + public function testCascadedEntitiesNotLoadedInPostLoadDuringIteration() + { + $e1 = new LifecycleCallbackTestEntity(); + $e2 = new LifecycleCallbackTestEntity(); + + $c = new LifecycleCallbackCascader(); + $this->_em->persist($c); + + $c->entities[] = $e1; + $c->entities[] = $e2; + $e1->cascader = $c; + $e2->cascader = $c; + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery(' +SELECT e, c +FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e +LEFT JOIN e.cascader AS c +WHERE e.id IN ('.$e1->getId().', '.$e2->getId().')'); + $result = $query->iterate(); + + foreach ($result as $entity) { + $this->assertTrue($entity[0]->postLoadCallbackInvoked); + $this->assertFalse($entity[0]->postLoadCascaderNotNull); + break; + } + } + public function testLifecycleCallbacksGetInherited() { $childMeta = $this->_em->getClassMetadata(__NAMESPACE__ . '\LifecycleCallbackChildEntity'); @@ -282,7 +341,7 @@ class LifecycleCallbackTestEntity public $prePersistCallbackInvoked = false; public $postPersistCallbackInvoked = false; public $postLoadCallbackInvoked = false; - + public $postLoadCascaderNotNull = false; public $preFlushCallbackInvoked = false; /** @@ -322,6 +381,7 @@ class LifecycleCallbackTestEntity /** @PostLoad */ public function doStuffOnPostLoad() { $this->postLoadCallbackInvoked = true; + $this->postLoadCascaderNotNull = isset($this->cascader); } /** @PreUpdate */ @@ -336,11 +396,15 @@ class LifecycleCallbackTestEntity } /** - * @Entity + * @Entity @HasLifecycleCallbacks * @Table(name="lc_cb_test_cascade") */ class LifecycleCallbackCascader { + /* test stuff */ + public $postLoadCallbackInvoked = false; + public $postLoadEntitiesCount = 0; + /** * @Id @Column(type="integer") * @GeneratedValue(strategy="AUTO") @@ -356,6 +420,12 @@ class LifecycleCallbackCascader { $this->entities = new \Doctrine\Common\Collections\ArrayCollection(); } + + /** @PostLoad */ + public function doStuffOnPostLoad() { + $this->postLoadCallbackInvoked = true; + $this->postLoadEntitiesCount = count($this->entities); + } } /** @MappedSuperclass @HasLifecycleCallbacks */ From b1144e74ea1e22f583a15081f9f38f6c7e1f48a1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 01:09:23 +0100 Subject: [PATCH 2/8] #470 DDC-54 DDC-3005 - reverting hydrator changes, as patch DDC-3005 already deals with the issue --- .../ORM/Internal/Hydration/ObjectHydrator.php | 19 +---------- .../Hydration/SimpleObjectHydrator.php | 34 +------------------ 2 files changed, 2 insertions(+), 51 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index e2d119f66..2d225c3d1 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -77,18 +77,11 @@ class ObjectHydrator extends AbstractHydrator */ private $existingCollections = array(); - /** - * @var \Doctrine\ORM\Event\PostLoadEventDispatcher - */ - private $postLoadEventDispatcher; - /** * {@inheritdoc} */ protected function prepare() { - $this->postLoadEventDispatcher = new PostLoadEventDispatcher($this->_em, $this->_hints); - if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) { $this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true; } @@ -154,8 +147,6 @@ class ObjectHydrator extends AbstractHydrator $this->existingCollections = $this->resultPointers = array(); - unset($this->postLoadEventDispatcher); - if ($eagerLoad) { $this->_uow->triggerEagerLoads(); } @@ -179,8 +170,6 @@ class ObjectHydrator extends AbstractHydrator $coll->takeSnapshot(); } - $this->postLoadEventDispatcher->dispatchEnqueuedPostLoadEvents(); - return $result; } @@ -281,13 +270,7 @@ class ObjectHydrator extends AbstractHydrator $this->_hints['fetchAlias'] = $dqlAlias; - $created = false; - $entity = $this->_uow->createEntity($className, $data, $this->_hints, $created); - if ($created) { - $this->postLoadEventDispatcher->dispatchPostLoad($entity); - } - - return $entity; + return $this->_uow->createEntity($className, $data, $this->_hints); } /** diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 6254d35b4..20c75ad30 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -23,10 +23,6 @@ use PDO; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query; -use Doctrine\ORM\Events; -use Doctrine\ORM\Event\LifecycleEventArgs; -use Doctrine\ORM\Event\ListenersInvoker; -use Doctrine\ORM\Event\PostLoadEventDispatcher; class SimpleObjectHydrator extends AbstractHydrator { @@ -35,23 +31,11 @@ class SimpleObjectHydrator extends AbstractHydrator */ private $class; - /** - * @var \Doctrine\ORM\Event\PostLoadEventDispatcher - */ - private $postLoadEventDispatcher; - - /** - * @var array - */ - private $hydratedObjects = array(); - /** * {@inheritdoc} */ protected function prepare() { - $this->postLoadEventDispatcher = new PostLoadEventDispatcher($this->_em, $this->_hints); - if (count($this->_rsm->aliasMap) !== 1) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result."); } @@ -87,21 +71,9 @@ class SimpleObjectHydrator extends AbstractHydrator $this->_em->getUnitOfWork()->triggerEagerLoads(); - $this->postLoadEventDispatcher->dispatchEnqueuedPostLoadEvents(); - return $result; } - /** - * {@inheritdoc} - */ - protected function cleanup() - { - parent::cleanup(); - - unset($this->postLoadEventDispatcher); - } - /** * {@inheritdoc} */ @@ -169,11 +141,7 @@ class SimpleObjectHydrator extends AbstractHydrator } $uow = $this->_em->getUnitOfWork(); - $created = false; - $entity = $uow->createEntity($entityName, $data, $this->_hints, $created); - if ($created) { - $this->postLoadEventDispatcher->dispatchPostLoad($entity); - } + $entity = $uow->createEntity($entityName, $data, $this->_hints); $result[] = $entity; } From a884452ffce78445cbcf850efc9d97e77d29f5cf Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 01:16:46 +0100 Subject: [PATCH 3/8] #470 DDC-54 DDC-3005 - removing unused `PostLoadEventDispatcher` --- .../ORM/Event/PostLoadEventDispatcher.php | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php diff --git a/lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php b/lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php deleted file mode 100644 index ea5e10869..000000000 --- a/lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php +++ /dev/null @@ -1,123 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Event; - -use Doctrine\ORM\EntityManager; -use Doctrine\ORM\Events; -use Doctrine\ORM\Query; - -/** - * Dispatcher for postLoad event on entities used during object hydration. - * - * @author Lukasz Cybula - * @since 2.4 - */ -class PostLoadEventDispatcher -{ - /** - * Entity Manager - * - * @var \Doctrine\ORM\EntityManager - */ - private $entityManager; - - /** - * Listeners Invoker - * - * @var \Doctrine\ORM\Event\ListenersInvoker - */ - private $invoker; - - /** - * Metadata Factory - * - * @var \Doctrine\ORM\Mapping\ClassMetadataFactory - */ - private $metadataFactory; - - /** - * The query hints - * - * @var array - */ - private $hints = array(); - - /** - * Entities enqueued for postLoad dispatching - * - * @var array - */ - private $entities = array(); - - /** - * Constructs a new dispatcher instance - * - * @param EntityManager $em - * @param array $hints - */ - public function __construct(EntityManager $em, array $hints = array()) - { - $this->entityManager = $em; - $this->metadataFactory = $em->getMetadataFactory(); - $this->invoker = $this->entityManager->getUnitOfWork()->getListenersInvoker(); - $this->hints = $hints; - } - - /** - * Dispatches postLoad event for specified entity or enqueues it for later dispatching - * - * @param object $entity - */ - public function dispatchPostLoad($entity) - { - $className = get_class($entity); - $meta = $this->metadataFactory->getMetadataFor($className); - $invoke = $this->invoker->getSubscribedSystems($meta, Events::postLoad); - - if ($invoke === ListenersInvoker::INVOKE_NONE) { - return; - } - - if (isset($this->hints[Query::HINT_INTERNAL_ITERATION])) { - $this->invoker->invoke($meta, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->entityManager), $invoke); - } else { - if ( ! isset($this->entities[$className])) { - $this->entities[$className] = array(); - } - - $this->entities[$className][] = $entity; - } - } - - /** - * Dispatches all enqueued postLoad events - */ - public function dispatchEnqueuedPostLoadEvents() - { - foreach ($this->entities as $class => $entities) { - $meta = $this->metadataFactory->getMetadataFor($class); - $invoke = $this->invoker->getSubscribedSystems($meta, Events::postLoad); - - foreach ($entities as $entity) { - $this->invoker->invoke($meta, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->entityManager), $invoke); - } - } - } -} From 0a19fbb376ff128711edbe605b8af3429f91ed07 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 01:26:04 +0100 Subject: [PATCH 4/8] #470 DDC-54 DDC-3005 - minor test cleanups, changing test according to current limitation to document the actual expected behavior --- .../ORM/Functional/LifecycleCallbackTest.php | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index f1c84aa94..29566d8cc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -150,6 +150,10 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($e2->prePersistCallbackInvoked); } + /** + * @group DDC-54 + * @group DDC-3005 + */ public function testCascadedEntitiesLoadedInPostLoad() { $e1 = new LifecycleCallbackTestEntity(); @@ -166,12 +170,21 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); $this->_em->clear(); - $query = $this->_em->createQuery(' -SELECT e, c -FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e - LEFT JOIN e.cascader AS c -WHERE e.id IN ('.$e1->getId().', '.$e2->getId().')'); - $entities = $query->execute(null, \Doctrine\ORM\Query::HYDRATE_OBJECT); + $dql = <<<'DQL' +SELECT + e, c +FROM + Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e +LEFT JOIN + e.cascader AS c +WHERE + e.id IN (%s, %s) +DQL; + + $entities = $this + ->_em + ->createQuery(sprintf($dql, $e1->getId(), $e2->getId())) + ->getResult(); $this->assertTrue(current($entities)->postLoadCallbackInvoked); $this->assertTrue(current($entities)->postLoadCascaderNotNull); @@ -179,6 +192,10 @@ WHERE e.id IN ('.$e1->getId().', '.$e2->getId().')'); $this->assertEquals(current($entities)->cascader->postLoadEntitiesCount, 2); } + /** + * @group DDC-54 + * @group DDC-3005 + */ public function testCascadedEntitiesNotLoadedInPostLoadDuringIteration() { $e1 = new LifecycleCallbackTestEntity(); @@ -195,16 +212,29 @@ WHERE e.id IN ('.$e1->getId().', '.$e2->getId().')'); $this->_em->flush(); $this->_em->clear(); - $query = $this->_em->createQuery(' -SELECT e, c -FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e -LEFT JOIN e.cascader AS c -WHERE e.id IN ('.$e1->getId().', '.$e2->getId().')'); - $result = $query->iterate(); + $dql = <<<'DQL' +SELECT + e, c +FROM + Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e +LEFT JOIN + e.cascader AS c +WHERE + e.id IN (%s, %s) +DQL; + + $result = $this + ->_em + ->createQuery(sprintf($dql, $e1->getId(), $e2->getId())) + ->iterate(); foreach ($result as $entity) { - $this->assertTrue($entity[0]->postLoadCallbackInvoked); + $this->assertFalse( + $entity[0]->postLoadCallbackInvoked, + 'During iteration, postLoad callbacks are not triggered until the end of the resultset is reached' + ); $this->assertFalse($entity[0]->postLoadCascaderNotNull); + break; } } From 5cd73f0d12526d6091947e468c5f51eb803fd45a Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 01:36:13 +0100 Subject: [PATCH 5/8] #470 DDC-54 DDC-3005 - reverting assertion `postLoad` should be triggered eagerly when using `iterate()`, as worse problems may be experienced with missed initialization via listeners. --- .../Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index 29566d8cc..d1b020e03 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -229,10 +229,7 @@ DQL; ->iterate(); foreach ($result as $entity) { - $this->assertFalse( - $entity[0]->postLoadCallbackInvoked, - 'During iteration, postLoad callbacks are not triggered until the end of the resultset is reached' - ); + $this->assertTrue($entity[0]->postLoadCallbackInvoked); $this->assertFalse($entity[0]->postLoadCascaderNotNull); break; From 0ffc752f6f85dbf72a426b3527e90ab8e4d9aabe Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 01:42:03 +0100 Subject: [PATCH 6/8] #470 DDC-54 DDC-3005 - simple-object hydration should also trigger `postLoad` events when iterating over single results --- .../ORM/Functional/LifecycleCallbackTest.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index d1b020e03..9b60c43b2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -2,6 +2,7 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\ORM\Event\PreUpdateEventArgs; +use Doctrine\ORM\Query; class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase { @@ -235,6 +236,30 @@ DQL; break; } } + /** + * @group DDC-54 + * @group DDC-3005 + */ + public function testCascadedEntitiesNotLoadedInPostLoadDuringIterationWithSimpleObjectHydrator() + { + $this->_em->persist(new LifecycleCallbackTestEntity()); + $this->_em->persist(new LifecycleCallbackTestEntity()); + + $this->_em->flush(); + $this->_em->clear(); + + $result = $this + ->_em + ->createQuery('SELECT e FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e') + ->iterate(null, Query::HYDRATE_SIMPLEOBJECT); + + foreach ($result as $entity) { + $this->assertTrue($entity[0]->postLoadCallbackInvoked); + $this->assertFalse($entity[0]->postLoadCascaderNotNull); + + break; + } + } public function testLifecycleCallbacksGetInherited() { From f571a9ef88b9103f3d55901d71aadb4d7a7f53a2 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 01:42:38 +0100 Subject: [PATCH 7/8] #470 DDC-54 DDC-3005 - query iteration must cause eager `hydrationComplete` logic to be fired --- lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php | 4 ++++ lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 2d225c3d1..ab6ac59cf 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -525,6 +525,10 @@ class ObjectHydrator extends AbstractHydrator $resultKey = $index; } } + + if (isset($this->_hints[Query::HINT_INTERNAL_ITERATION]) && $this->_hints[Query::HINT_INTERNAL_ITERATION]) { + $this->_uow->hydrationComplete(); + } } if ( ! isset($resultKey) ) { diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 20c75ad30..1c21369e3 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -144,5 +144,9 @@ class SimpleObjectHydrator extends AbstractHydrator $entity = $uow->createEntity($entityName, $data, $this->_hints); $result[] = $entity; + + if (isset($this->_hints[Query::HINT_INTERNAL_ITERATION]) && $this->_hints[Query::HINT_INTERNAL_ITERATION]) { + $this->_uow->hydrationComplete(); + } } } From b81209c2780724f0ec65c04b6115e2b552dd6ee4 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 13 Jan 2015 01:43:12 +0100 Subject: [PATCH 8/8] #470 DDC-54 DDC-3005 - documenting `postLoad` and `Doctrine\ORM\AbstractQuery#iterate()` partial incompatibility --- docs/en/reference/events.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst index 2762e5fc7..65acd2466 100644 --- a/docs/en/reference/events.rst +++ b/docs/en/reference/events.rst @@ -184,6 +184,14 @@ 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, when using ``Doctrine\ORM\AbstractQuery#iterate()``, ``postLoad`` + events will be executed immediately after objects are being hydrated, and therefore + associations are not guaranteed to be initialized. It is not safe to combine + usage of ``Doctrine\ORM\AbstractQuery#iterate()`` and ``postLoad`` event + handlers. + .. warning:: Note that the postRemove event or any events triggered after an entity removal