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\Mapping\ClassMetadata;
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
use Doctrine\ORM\Query;
|
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\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\ORM\Proxy\Proxy;
|
use Doctrine\ORM\Proxy\Proxy;
|
||||||
|
|
||||||
|
@ -74,11 +77,18 @@ class ObjectHydrator extends AbstractHydrator
|
||||||
*/
|
*/
|
||||||
private $existingCollections = array();
|
private $existingCollections = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Doctrine\ORM\Event\PostLoadEventDispatcher
|
||||||
|
*/
|
||||||
|
private $postLoadEventDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected function prepare()
|
protected function prepare()
|
||||||
{
|
{
|
||||||
|
$this->postLoadEventDispatcher = new PostLoadEventDispatcher($this->_em, $this->_hints);
|
||||||
|
|
||||||
if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
|
if ( ! isset($this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
|
||||||
$this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true;
|
$this->_hints[UnitOfWork::HINT_DEFEREAGERLOAD] = true;
|
||||||
}
|
}
|
||||||
|
@ -144,6 +154,8 @@ class ObjectHydrator extends AbstractHydrator
|
||||||
$this->existingCollections =
|
$this->existingCollections =
|
||||||
$this->resultPointers = array();
|
$this->resultPointers = array();
|
||||||
|
|
||||||
|
unset($this->postLoadEventDispatcher);
|
||||||
|
|
||||||
if ($eagerLoad) {
|
if ($eagerLoad) {
|
||||||
$this->_uow->triggerEagerLoads();
|
$this->_uow->triggerEagerLoads();
|
||||||
}
|
}
|
||||||
|
@ -167,6 +179,8 @@ class ObjectHydrator extends AbstractHydrator
|
||||||
$coll->takeSnapshot();
|
$coll->takeSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->postLoadEventDispatcher->dispatchEnqueuedPostLoadEvents();
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +281,13 @@ class ObjectHydrator extends AbstractHydrator
|
||||||
|
|
||||||
$this->_hints['fetchAlias'] = $dqlAlias;
|
$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\DBAL\Types\Type;
|
||||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
use Doctrine\ORM\Query;
|
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
|
class SimpleObjectHydrator extends AbstractHydrator
|
||||||
{
|
{
|
||||||
|
@ -31,11 +35,23 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||||
*/
|
*/
|
||||||
private $class;
|
private $class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Doctrine\ORM\Event\PostLoadEventDispatcher
|
||||||
|
*/
|
||||||
|
private $postLoadEventDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $hydratedObjects = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected function prepare()
|
protected function prepare()
|
||||||
{
|
{
|
||||||
|
$this->postLoadEventDispatcher = new PostLoadEventDispatcher($this->_em, $this->_hints);
|
||||||
|
|
||||||
if (count($this->_rsm->aliasMap) !== 1) {
|
if (count($this->_rsm->aliasMap) !== 1) {
|
||||||
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result.");
|
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->_em->getUnitOfWork()->triggerEagerLoads();
|
||||||
|
|
||||||
|
$this->postLoadEventDispatcher->dispatchEnqueuedPostLoadEvents();
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function cleanup()
|
||||||
|
{
|
||||||
|
parent::cleanup();
|
||||||
|
|
||||||
|
unset($this->postLoadEventDispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -141,7 +169,11 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||||
}
|
}
|
||||||
|
|
||||||
$uow = $this->_em->getUnitOfWork();
|
$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;
|
$result[] = $entity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,65 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||||
$this->assertTrue($e2->prePersistCallbackInvoked);
|
$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()
|
public function testLifecycleCallbacksGetInherited()
|
||||||
{
|
{
|
||||||
$childMeta = $this->_em->getClassMetadata(__NAMESPACE__ . '\LifecycleCallbackChildEntity');
|
$childMeta = $this->_em->getClassMetadata(__NAMESPACE__ . '\LifecycleCallbackChildEntity');
|
||||||
|
@ -282,7 +341,7 @@ class LifecycleCallbackTestEntity
|
||||||
public $prePersistCallbackInvoked = false;
|
public $prePersistCallbackInvoked = false;
|
||||||
public $postPersistCallbackInvoked = false;
|
public $postPersistCallbackInvoked = false;
|
||||||
public $postLoadCallbackInvoked = false;
|
public $postLoadCallbackInvoked = false;
|
||||||
|
public $postLoadCascaderNotNull = false;
|
||||||
public $preFlushCallbackInvoked = false;
|
public $preFlushCallbackInvoked = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -322,6 +381,7 @@ class LifecycleCallbackTestEntity
|
||||||
/** @PostLoad */
|
/** @PostLoad */
|
||||||
public function doStuffOnPostLoad() {
|
public function doStuffOnPostLoad() {
|
||||||
$this->postLoadCallbackInvoked = true;
|
$this->postLoadCallbackInvoked = true;
|
||||||
|
$this->postLoadCascaderNotNull = isset($this->cascader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @PreUpdate */
|
/** @PreUpdate */
|
||||||
|
@ -336,11 +396,15 @@ class LifecycleCallbackTestEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Entity
|
* @Entity @HasLifecycleCallbacks
|
||||||
* @Table(name="lc_cb_test_cascade")
|
* @Table(name="lc_cb_test_cascade")
|
||||||
*/
|
*/
|
||||||
class LifecycleCallbackCascader
|
class LifecycleCallbackCascader
|
||||||
{
|
{
|
||||||
|
/* test stuff */
|
||||||
|
public $postLoadCallbackInvoked = false;
|
||||||
|
public $postLoadEntitiesCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Id @Column(type="integer")
|
* @Id @Column(type="integer")
|
||||||
* @GeneratedValue(strategy="AUTO")
|
* @GeneratedValue(strategy="AUTO")
|
||||||
|
@ -356,6 +420,12 @@ class LifecycleCallbackCascader
|
||||||
{
|
{
|
||||||
$this->entities = new \Doctrine\Common\Collections\ArrayCollection();
|
$this->entities = new \Doctrine\Common\Collections\ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @PostLoad */
|
||||||
|
public function doStuffOnPostLoad() {
|
||||||
|
$this->postLoadCallbackInvoked = true;
|
||||||
|
$this->postLoadEntitiesCount = count($this->entities);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @MappedSuperclass @HasLifecycleCallbacks */
|
/** @MappedSuperclass @HasLifecycleCallbacks */
|
||||||
|
|
Loading…
Add table
Reference in a new issue