Moved postLoad dispatching from UnitOfWork to object hydrators
This commit is contained in:
parent
a906295c65
commit
aa4796cd0d
4 changed files with 249 additions and 4 deletions
123
lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php
Normal file
123
lib/Doctrine/ORM/Event/PostLoadEventDispatcher.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Add table
Reference in a new issue