diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 61a0f48fa..493b1461c 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -22,15 +22,17 @@ namespace Doctrine\ORM\Internal\Hydration; use PDO, Doctrine\DBAL\Connection, Doctrine\DBAL\Types\Type, - Doctrine\ORM\EntityManager; + Doctrine\ORM\EntityManager, + Doctrine\ORM\Mapping\ClassMetadata; /** * Base class for all hydrators. A hydrator is a class that provides some form * of transformation of an SQL result set into another structure. * - * @since 2.0 - * @author Konsta Vesterinen - * @author Roman Borschel + * @since 2.0 + * @author Konsta Vesterinen + * @author Roman Borschel + * @author Guilherme Blanco */ abstract class AbstractHydrator { @@ -62,9 +64,9 @@ abstract class AbstractHydrator */ public function __construct(EntityManager $em) { - $this->_em = $em; + $this->_em = $em; $this->_platform = $em->getConnection()->getDatabasePlatform(); - $this->_uow = $em->getUnitOfWork(); + $this->_uow = $em->getUnitOfWork(); } /** @@ -72,14 +74,17 @@ abstract class AbstractHydrator * * @param object $stmt * @param object $resultSetMapping + * * @return IterableResult */ public function iterate($stmt, $resultSetMapping, array $hints = array()) { - $this->_stmt = $stmt; - $this->_rsm = $resultSetMapping; + $this->_stmt = $stmt; + $this->_rsm = $resultSetMapping; $this->_hints = $hints; - $this->_prepare(); + + $this->prepare(); + return new IterableResult($this); } @@ -92,12 +97,16 @@ abstract class AbstractHydrator */ public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) { - $this->_stmt = $stmt; - $this->_rsm = $resultSetMapping; + $this->_stmt = $stmt; + $this->_rsm = $resultSetMapping; $this->_hints = $hints; - $this->_prepare(); - $result = $this->_hydrateAll(); - $this->_cleanup(); + + $this->prepare(); + + $result = $this->hydrateAllData(); + + $this->cleanup(); + return $result; } @@ -110,12 +119,17 @@ abstract class AbstractHydrator public function hydrateRow() { $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); + if ( ! $row) { - $this->_cleanup(); + $this->cleanup(); + return false; } + $result = array(); - $this->_hydrateRow($row, $this->_cache, $result); + + $this->hydrateRowData($row, $this->_cache, $result); + return $result; } @@ -123,16 +137,17 @@ abstract class AbstractHydrator * Excutes one-time preparation tasks, once each time hydration is started * through {@link hydrateAll} or {@link iterate()}. */ - protected function _prepare() + protected function prepare() {} /** * Excutes one-time cleanup tasks at the end of a hydration that was initiated * through {@link hydrateAll} or {@link iterate()}. */ - protected function _cleanup() + protected function cleanup() { $this->_rsm = null; + $this->_stmt->closeCursor(); $this->_stmt = null; } @@ -146,15 +161,15 @@ abstract class AbstractHydrator * @param array $cache The cache to use. * @param mixed $result The result to fill. */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + protected function hydrateRowData(array $data, array &$cache, array &$result) { - throw new HydrationException("_hydrateRow() not implemented by this hydrator."); + throw new HydrationException("hydrateRowData() not implemented by this hydrator."); } /** * Hydrates all rows from the current statement instance at once. */ - abstract protected function _hydrateAll(); + abstract protected function hydrateAllData(); /** * Processes a row of the result set. @@ -173,7 +188,7 @@ abstract class AbstractHydrator * @return array An array with all the fields (name => value) of the data row, * grouped by their component alias. */ - protected function _gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) + protected function gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) { $rowData = array(); @@ -207,6 +222,7 @@ abstract class AbstractHydrator if (isset($cache[$key]['isScalar'])) { $rowData['scalars'][$cache[$key]['fieldName']] = $value; + continue; } @@ -220,10 +236,11 @@ abstract class AbstractHydrator if (!isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; } + continue; } - // in an inheritance hierachy the same field could be defined several times. + // in an inheritance hierarchy the same field could be defined several times. // We overwrite this value so long we dont have a non-null value, that value we keep. // Per definition it cannot be that a field is defined several times and has several values. if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) { @@ -250,9 +267,10 @@ abstract class AbstractHydrator * * @param array $data * @param array $cache + * * @return array The processed row. */ - protected function _gatherScalarRowData(&$data, &$cache) + protected function gatherScalarRowData(&$data, &$cache) { $rowData = array(); @@ -294,8 +312,15 @@ abstract class AbstractHydrator return $rowData; } - - protected function registerManaged($class, $entity, $data) + + /** + * Register entity as managed in UnitOfWork. + * + * @param Doctrine\ORM\Mapping\ClassMetadata $class + * @param object $entity + * @param array $data + */ + protected function registerManaged(ClassMetadata $class, $entity, array $data) { if ($class->isIdentifierComposite) { $id = array(); @@ -313,6 +338,7 @@ abstract class AbstractHydrator $id = array($class->identifier[0] => $data[$class->identifier[0]]); } } + $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index cbc77d2d2..e25df2fea 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -25,8 +25,9 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata; * The ArrayHydrator produces a nested array "graph" that is often (not always) * interchangeable with the corresponding object graph for read-only access. * + * @since 2.0 * @author Roman Borschel - * @since 1.0 + * @author Guilherme Blanco */ class ArrayHydrator extends AbstractHydrator { @@ -38,45 +39,54 @@ class ArrayHydrator extends AbstractHydrator private $_idTemplate = array(); private $_resultCounter = 0; - /** @override */ - protected function _prepare() + /** + * {@inheritdoc} + */ + protected function prepare() { - $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; - $this->_identifierMap = array(); + $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; + $this->_identifierMap = array(); $this->_resultPointers = array(); - $this->_idTemplate = array(); - $this->_resultCounter = 0; + $this->_idTemplate = array(); + $this->_resultCounter = 0; + foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { - $this->_identifierMap[$dqlAlias] = array(); + $this->_identifierMap[$dqlAlias] = array(); $this->_resultPointers[$dqlAlias] = array(); - $this->_idTemplate[$dqlAlias] = ''; + $this->_idTemplate[$dqlAlias] = ''; } } - /** @override */ - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { $result = array(); - $cache = array(); + $cache = array(); + while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { - $this->_hydrateRow($data, $cache, $result); + $this->hydrateRowData($data, $cache, $result); } return $result; } - /** @override */ - protected function _hydrateRow(array $row, array &$cache, array &$result) + /** + * {@inheritdoc} + */ + protected function hydrateRowData(array $row, array &$cache, array &$result) { // 1) Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); - $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); + $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { $scalars = $rowData['scalars']; unset($rowData['scalars']); + if (empty($rowData)) { ++$this->_resultCounter; } @@ -111,7 +121,7 @@ class ArrayHydrator extends AbstractHydrator } $relationAlias = $this->_rsm->relationMap[$dqlAlias]; - $relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; + $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias]; // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { @@ -230,28 +240,45 @@ class ArrayHydrator extends AbstractHydrator { if ($coll === null) { unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 + return; } + if ($index !== false) { $this->_resultPointers[$dqlAlias] =& $coll[$index]; + + return; + } + + if ( ! $coll) { return; - } else { - if ($coll) { - if ($oneToOne) { - $this->_resultPointers[$dqlAlias] =& $coll; - } else { - end($coll); - $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; - } - } } + + if ($oneToOne) { + $this->_resultPointers[$dqlAlias] =& $coll; + + return; + } + + end($coll); + $this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; + + return; } - private function _getClassMetadata($className) + /** + * Retrieve ClassMetadata associated to entity class name. + * + * @param string $className + * + * @return Doctrine\ORM\Mapping\ClassMetadata + */ + private function getClassMetadata($className) { if ( ! isset($this->_ce[$className])) { $this->_ce[$className] = $this->_em->getClassMetadata($className); } + return $this->_ce[$className]; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 3f787694b..d7d0281c4 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -29,8 +29,10 @@ use PDO, /** * The ObjectHydrator constructs an object graph out of an SQL result set. * + * @since 2.0 * @author Roman Borschel - * @since 2.0 + * @author Guilherme Blanco + * * @internal Highly performance-sensitive code. */ class ObjectHydrator extends AbstractHydrator @@ -53,7 +55,7 @@ class ObjectHydrator extends AbstractHydrator /** @override */ - protected function _prepare() + protected function prepare() { $this->_identifierMap = $this->_resultPointers = @@ -113,11 +115,12 @@ class ObjectHydrator extends AbstractHydrator /** * {@inheritdoc} */ - protected function _cleanup() + protected function cleanup() { $eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true; - parent::_cleanup(); + parent::cleanup(); + $this->_identifierMap = $this->_initializedCollections = $this->_existingCollections = @@ -131,13 +134,13 @@ class ObjectHydrator extends AbstractHydrator /** * {@inheritdoc} */ - protected function _hydrateAll() + protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { - $this->_hydrateRow($row, $cache, $result); + $this->hydrateRowData($row, $cache, $result); } // Take snapshots from all newly initialized collections @@ -278,13 +281,13 @@ class ObjectHydrator extends AbstractHydrator * @param array $cache The cache to use. * @param array $result The result array to fill. */ - protected function _hydrateRow(array $row, array &$cache, array &$result) + protected function hydrateRowData(array $row, array &$cache, array &$result) { // Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); // Split the row data into chunks of class data. - $rowData = $this->_gatherRowData($row, $cache, $id, $nonemptyComponents); + $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents); // Extract scalar values. They're appended at the end. if (isset($rowData['scalars'])) { diff --git a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php index f15307310..29c0d12e5 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ScalarHydrator.php @@ -26,25 +26,32 @@ use Doctrine\DBAL\Connection; * The created result is almost the same as a regular SQL result set, except * that column names are mapped to field names and data type conversions take place. * + * @since 2.0 * @author Roman Borschel - * @since 2.0 + * @author Guilherme Blanco */ class ScalarHydrator extends AbstractHydrator { - /** @override */ - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { $result = array(); - $cache = array(); + $cache = array(); + while ($data = $this->_stmt->fetch(\PDO::FETCH_ASSOC)) { - $result[] = $this->_gatherScalarRowData($data, $cache); + $this->hydrateRowData($data, $cache, $result); } + return $result; } - /** @override */ - protected function _hydrateRow(array $data, array &$cache, array &$result) + /** + * {@inheritdoc} + */ + protected function hydrateRowData(array $data, array &$cache, array &$result) { - $result[] = $this->_gatherScalarRowData($data, $cache); + $result[] = $this->gatherScalarRowData($data, $cache); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index e9f6395f9..c045c1edf 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -17,7 +17,6 @@ * . */ - namespace Doctrine\ORM\Internal\Hydration; use \PDO; @@ -32,15 +31,21 @@ class SimpleObjectHydrator extends AbstractHydrator */ private $class; + /** + * @var array + */ private $declaringClasses = array(); - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { $result = array(); $cache = array(); while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) { - $this->_hydrateRow($row, $cache, $result); + $this->hydrateRowData($row, $cache, $result); } $this->_em->getUnitOfWork()->triggerEagerLoads(); @@ -48,82 +53,71 @@ class SimpleObjectHydrator extends AbstractHydrator return $result; } - protected function _prepare() + /** + * {@inheritdoc} + */ + protected function prepare() { - if (count($this->_rsm->aliasMap) == 1) { - $this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap)); - if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { - foreach ($this->_rsm->declaringClasses AS $column => $class) { - $this->declaringClasses[$column] = $this->_em->getClassMetadata($class); - } - } - } else { - throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping not containing exactly one object result."); + if (count($this->_rsm->aliasMap) !== 1) { + throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains more than one object result."); } + if ($this->_rsm->scalarMappings) { throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings."); } + + $this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap)); + + // We only need to add declaring classes if we have inheritance. + if ($this->class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_NONE) { + return; + } + + foreach ($this->_rsm->declaringClasses AS $column => $class) { + $this->declaringClasses[$column] = $this->_em->getClassMetadata($class); + } } - protected function _hydrateRow(array $sqlResult, array &$cache, array &$result) + /** + * {@inheritdoc} + */ + protected function hydrateRowData(array $sqlResult, array &$cache, array &$result) { - $data = array(); - if ($this->class->inheritanceType == ClassMetadata::INHERITANCE_TYPE_NONE) { - foreach ($sqlResult as $column => $value) { - - if (!isset($cache[$column])) { - if (isset($this->_rsm->fieldMappings[$column])) { - $cache[$column]['name'] = $this->_rsm->fieldMappings[$column]; - $cache[$column]['field'] = true; - } else { - $cache[$column]['name'] = $this->_rsm->metaMappings[$column]; - } - } - - if (isset($cache[$column]['field'])) { - $value = Type::getType($this->class->fieldMappings[$cache[$column]['name']]['type']) - ->convertToPHPValue($value, $this->_platform); - } - $data[$cache[$column]['name']] = $value; - } - $entityName = $this->class->name; - } else { + $entityName = $this->class->name; + $data = array(); + + // We need to find the correct entity class name if we have inheritance in resultset + if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { $discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']); - if ($sqlResult[$discrColumnName] === "") { + + if ($sqlResult[$discrColumnName] === '') { throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap)); } $entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]]; + unset($sqlResult[$discrColumnName]); - - foreach ($sqlResult as $column => $value) { - if (!isset($cache[$column])) { - if (isset($this->_rsm->fieldMappings[$column])) { - $field = $this->_rsm->fieldMappings[$column]; - $class = $this->declaringClasses[$column]; - if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) { - $cache[$column]['name'] = $field; - $cache[$column]['class'] = $class; - } - } else if (isset($this->_rsm->relationMap[$column])) { - if ($this->_rsm->relationMap[$column] == $entityName || is_subclass_of($entityName, $this->_rsm->relationMap[$column])) { - $cache[$column]['name'] = $field; - } - } else { - $cache[$column]['name'] = $this->_rsm->metaMappings[$column]; - } + } + + foreach ($sqlResult as $column => $value) { + // Hydrate column information if not yet present + if ( ! isset($cache[$column])) { + if (($info = $this->hydrateColumnInfo($entityName, $column)) === null) { + continue; } + + $cache[$column] = $info; + } - if (isset($cache[$column]['class'])) { - $value = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']) - ->convertToPHPValue($value, $this->_platform); - } - - // the second and part is to prevent overwrites in case of multiple - // inheritance classes using the same property name (See AbstractHydrator) - if (isset($cache[$column]) && (!isset($data[$cache[$column]['name']]) || $value !== null)) { - $data[$cache[$column]['name']] = $value; - } + // Convert field to a valid PHP value + if (isset($cache[$column]['field'])) { + $type = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']); + $value = $type->convertToPHPValue($value, $this->_platform); + } + + // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator) + if (isset($cache[$column]) && ( ! isset($data[$cache[$column]['name']]) || $value !== null)) { + $data[$cache[$column]['name']] = $value; } } @@ -133,4 +127,52 @@ class SimpleObjectHydrator extends AbstractHydrator $result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints); } + + /** + * Retrieve column information form ResultSetMapping. + * + * @param string $entityName + * @param string $column + * + * @return array + */ + protected function hydrateColumnInfo($entityName, $column) + { + switch (true) { + case (isset($this->_rsm->fieldMappings[$column])): + $class = isset($this->declaringClasses[$column]) + ? $this->declaringClasses[$column] + : $this->class; + + // If class is not part of the inheritance, ignore + if ( ! ($class->name === $entityName || is_subclass_of($entityName, $class->name))) { + return null; + } + + return array( + 'class' => $class, + 'name' => $this->_rsm->fieldMappings[$column], + 'field' => true, + ); + + case (isset($this->_rsm->relationMap[$column])): + $class = isset($this->_rsm->relationMap[$column]) + ? $this->_rsm->relationMap[$column] + : $this->class; + + // If class is not self referencing, ignore + if ( ! ($class === $entityName || is_subclass_of($entityName, $class))) { + return null; + } + + // TODO: Decide what to do with associations. It seems original code is incomplete. + // One solution is to load the association, but it might require extra efforts. + return array('name' => $column); + + default: + return array( + 'name' => $this->_rsm->metaMappings[$column] + ); + } + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php index 466815521..98a45960e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SingleScalarHydrator.php @@ -19,30 +19,37 @@ namespace Doctrine\ORM\Internal\Hydration; -use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Connection, + Doctrine\ORM\NoResultException, + Doctrine\ORM\NonUniqueResultException; /** * Hydrator that hydrates a single scalar value from the result set. * + * @since 2.0 * @author Roman Borschel - * @since 2.0 + * @author Guilherme Blanco */ class SingleScalarHydrator extends AbstractHydrator { - /** @override */ - protected function _hydrateAll() + /** + * {@inheritdoc} + */ + protected function hydrateAllData() { - $cache = array(); - $result = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); - $num = count($result); - - if ($num == 0) { - throw new \Doctrine\ORM\NoResultException; - } else if ($num > 1 || count($result[key($result)]) > 1) { - throw new \Doctrine\ORM\NonUniqueResultException; + $data = $this->_stmt->fetchAll(\PDO::FETCH_ASSOC); + $numRows = count($data); + + if ($numRows === 0) { + throw new NoResultException(); } - $result = $this->_gatherScalarRowData($result[key($result)], $cache); + if ($numRows > 1 || count($data[key($data)]) > 1) { + throw new NonUniqueResultException(); + } + + $cache = array(); + $result = $this->gatherScalarRowData($data[key($data)], $cache); return array_shift($result); }