diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 202fdc7ff..4bf90d0ff 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -59,6 +59,7 @@ class ObjectHydrator extends AbstractHydrator $this->_resultPointers = $this->_idTemplate = array(); $this->_resultCounter = 0; + $this->_hints['deferEagerLoad'] = true; foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { $this->_identifierMap[$dqlAlias] = array(); @@ -132,6 +133,8 @@ class ObjectHydrator extends AbstractHydrator $coll->takeSnapshot(); } + $this->_em->getUnitOfWork()->triggerEagerLoads(); + return $result; } diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index c9ae5ddd9..57090ba83 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -560,7 +560,10 @@ class BasicEntityPersister $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); - return $this->_createEntity($result, $entity, $hints); + $hints['deferEagerLoad'] = true; + $entity = $this->_createEntity($result, $entity, $hints); + $this->_em->getUnitOfWork()->triggerEagerLoads(); + return $entity; } /** @@ -577,6 +580,10 @@ class BasicEntityPersister */ public function loadOneToOneEntity(array $assoc, $sourceEntity, $targetEntity, array $identifier = array()) { + if ($foundEntity = $this->_em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) { + return $foundEntity; + } + $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); if ($assoc['isOwningSide']) { @@ -739,10 +746,13 @@ class BasicEntityPersister $result = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); + $hints = array('deferEagerLoads' => true); foreach ($result as $row) { - $entities[] = $this->_createEntity($row); + $entities[] = $this->_createEntity($row, null, $hints); } + $this->_em->getUnitOfWork()->triggerEagerLoads(); + return $entities; } @@ -870,7 +880,14 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); - return $this->_conn->executeQuery($sql, $params, $types); + $stmt = $this->_conn->executeQuery($sql, $params, $types); + $hints = array('deferEagerLoads' => true); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $coll->hydrateAdd($this->_createEntity($result, null, $hints)); + } + $stmt->closeCursor(); + + $this->_em->getUnitOfWork()->triggerEagerLoads(); } /** @@ -1330,7 +1347,16 @@ class BasicEntityPersister $sql = $this->_getSelectEntitiesSQL($criteria, $assoc, 0, $limit, $offset); list($params, $types) = $this->expandParameters($criteria); - return $this->_conn->executeQuery($sql, $params, $types); + + $stmt = $this->_conn->executeQuery($sql, $params, $types); + $hints = array('deferEagerLoads' => true); + while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) { + $coll->hydrateAdd($this->_createEntity($result, null, $hints)); + } + $stmt->closeCursor(); + + $this->_em->getUnitOfWork()->triggerEagerLoads(); + return $stmt; } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d0f5bc022..1cc0c47dc 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1928,10 +1928,18 @@ class UnitOfWork implements PropertyChangedListener $newValue = $this->getEntityPersister($assoc['targetEntity']) ->loadOneToOneEntity($assoc, $entity, null, $associatedId); } else { - if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) { - // TODO: Maybe it could be optimized to do an eager fetch with a JOIN inside - // the persister instead of this rather unperformant approach. - $newValue = $this->em->find($assoc['targetEntity'], $associatedId); + if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER && isset($hints['deferEagerLoad'])) { + if (!isset($this->eagerLoadingEntities[$assoc['targetEntity']])) { + $this->eagerLoadingEntities[$assoc['targetEntity']] = array(); + } + + // TODO: Is there a faster approach? + $this->eagerLoadingEntities[$assoc['targetEntity']] = array_merge_recursive( + $this->eagerLoadingEntities[$assoc['targetEntity']], + array_map(function($id) { + return array($id); + }, $associatedId) + ); } else { $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId); } @@ -1983,6 +1991,26 @@ class UnitOfWork implements PropertyChangedListener } /** +<<<<<<< HEAD +======= + * @return void + */ + public function triggerEagerLoads() + { + if (!$this->eagerLoadingEntities) { + return; + } + + $eagerLoadingEntities = $this->eagerLoadingEntities; + $this->eagerLoadingEntities = array(); + + foreach ($eagerLoadingEntities AS $entityName => $ids) { + $this->getEntityPersister($entityName)->loadAll($ids); + } + } + + /** +>>>>>>> DDC-952 - Implemented first approach for batching eager loads of ToOne associations. * Initializes (loads) an uninitialized persistent collection of an entity. * * @param PeristentCollection $collection The collection to initialize. diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC633Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC633Test.php index d375f408c..d4b093da9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC633Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC633Test.php @@ -21,6 +21,10 @@ class DDC633Test extends \Doctrine\Tests\OrmFunctionalTestCase } } + /** + * @group DDC-633 + * @group DDC-952 + */ public function testOneToOneEager() { $app = new DDC633Appointment(); @@ -35,7 +39,35 @@ class DDC633Test extends \Doctrine\Tests\OrmFunctionalTestCase $eagerAppointment = $this->_em->find(__NAMESPACE__ . '\DDC633Appointment', $app->id); - $this->assertNotType('Doctrine\ORM\Proxy\Proxy', $eagerAppointment->patient); + // Eager loading still produces proxies + $this->assertType('Doctrine\ORM\Proxy\Proxy', $eagerAppointment->patient); + $this->assertTrue($eagerAppointment->patient->__isInitialized__, "Proxy should already be initialized due to eager loading!"); + } + + /** + * @group DDC-633 + * @group DDC-952 + */ + public function testDQLDeferredEagerLoad() + { + for ($i = 0; $i < 10; $i++) { + $app = new DDC633Appointment(); + $pat = new DDC633Patient(); + $app->patient = $pat; + $pat->appointment = $app; + + $this->_em->persist($app); + $this->_em->persist($pat); + } + $this->_em->flush(); + $this->_em->clear(); + + $appointments = $this->_em->createQuery("SELECT a FROM " . __NAMESPACE__ . "\DDC633Appointment a")->getResult(); + + foreach ($appointments AS $eagerAppointment) { + $this->assertType('Doctrine\ORM\Proxy\Proxy', $eagerAppointment->patient); + $this->assertTrue($eagerAppointment->patient->__isInitialized__, "Proxy should already be initialized due to eager loading!"); + } } }