From aa4796cd0dd5fabce2bf94bdbc8cbf8ba062f702 Mon Sep 17 00:00:00 2001 From: Lukasz Cybula Date: Wed, 13 Mar 2013 09:27:43 +0100 Subject: [PATCH] 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 */