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 diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index ecbf9dcc4..ab6ac59cf 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; @@ -522,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(); + } } } diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index af732384a..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 { @@ -150,6 +151,116 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($e2->prePersistCallbackInvoked); } + /** + * @group DDC-54 + * @group DDC-3005 + */ + 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(); + + $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); + $this->assertTrue(current($entities)->cascader->postLoadCallbackInvoked); + $this->assertEquals(current($entities)->cascader->postLoadEntitiesCount, 2); + } + + /** + * @group DDC-54 + * @group DDC-3005 + */ + 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(); + + $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]->postLoadCascaderNotNull); + + 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() { $childMeta = $this->_em->getClassMetadata(__NAMESPACE__ . '\LifecycleCallbackChildEntity'); @@ -282,7 +393,7 @@ class LifecycleCallbackTestEntity public $prePersistCallbackInvoked = false; public $postPersistCallbackInvoked = false; public $postLoadCallbackInvoked = false; - + public $postLoadCascaderNotNull = false; public $preFlushCallbackInvoked = false; /** @@ -322,6 +433,7 @@ class LifecycleCallbackTestEntity /** @PostLoad */ public function doStuffOnPostLoad() { $this->postLoadCallbackInvoked = true; + $this->postLoadCascaderNotNull = isset($this->cascader); } /** @PreUpdate */ @@ -336,11 +448,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 +472,12 @@ class LifecycleCallbackCascader { $this->entities = new \Doctrine\Common\Collections\ArrayCollection(); } + + /** @PostLoad */ + public function doStuffOnPostLoad() { + $this->postLoadCallbackInvoked = true; + $this->postLoadEntitiesCount = count($this->entities); + } } /** @MappedSuperclass @HasLifecycleCallbacks */