1
0
Fork 0
mirror of synced 2025-04-01 12:26:11 +03:00

Moved postLoad dispatching from UnitOfWork to object hydrators

This commit is contained in:
Lukasz Cybula 2013-03-13 09:27:43 +01:00 committed by Marco Pivetta
parent a906295c65
commit aa4796cd0d
4 changed files with 249 additions and 4 deletions

View file

@ -0,0 +1,123 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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 <lukasz@fsi.pl>
* @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);
}
}
}
}

View file

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

View file

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

View file

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