diff --git a/COPYRIGHT b/COPYRIGHT index 8c445d0db..dfa08dabc 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -19,15 +19,6 @@ Url: http://www.symfony-project.org Copyright: Fabien Potencier License: MIT - see LICENSE file -Spyc ----- - -Doctrine contains a port of the Spyc software - -Url: http://spyc.sourceforge.net/ -Copyright: 2005-2006 Chris Wanstrath -License: MIT - http://www.opensource.org/licenses/mit-license.php - Zend Framework -------------- diff --git a/lib/Doctrine/Common/DoctrineException.php b/lib/Doctrine/Common/DoctrineException.php index a68907a60..b3de944de 100644 --- a/lib/Doctrine/Common/DoctrineException.php +++ b/lib/Doctrine/Common/DoctrineException.php @@ -66,9 +66,9 @@ class DoctrineException extends \Exception if ($method && $class) { return new self("The method '$method' is not implemented in class '$class'."); } else if ($method && ! $class) { - return new self($method); + return new self($method); } else { - return new self('Functionality is not implemented.'); + return new self('Functionality is not implemented.'); } } diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index a90340932..a7142c196 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -92,6 +92,10 @@ final class Events * into the current EntityManager from the database or after the refresh operation * has been applied to it. * + * Note that the postLoad event occurs for an entity before any associations have been + * initialized. Therefore it is not safe to access associations in a postLoad callback + * or event handler. + * * This is an entity lifecycle event. * * @var string diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 8114ea320..0a8ca7303 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -162,7 +162,10 @@ abstract class AbstractHydrator /** * Gets the row container used during row-by-row hydration through {@link iterate()}. */ - abstract protected function _getRowContainer(); + protected function _getRowContainer() + { + return array(); + } /** * Processes a row of the result set. diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index caf3caaf7..b00b34d81 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -228,10 +228,4 @@ class ArrayHydrator extends AbstractHydrator } return $this->_ce[$className]; } - - /** {@inheritdoc} */ - protected function _getRowContainer() - { - return array(); - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php b/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php index 6c4060a0d..413430a4b 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php +++ b/lib/Doctrine/ORM/Internal/Hydration/HydrationException.php @@ -8,4 +8,10 @@ class HydrationException extends \Doctrine\Common\DoctrineException { return new self("The result returned by the query was not unique."); } + + public static function parentObjectOfRelationNotFound($alias, $parentAlias) + { + return new self("The parent object of entity result with alias '$alias' was not found." + . " The parent alias is '$parentAlias'."); + } } \ 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 bb5b62063..ace2efde8 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -21,8 +21,7 @@ namespace Doctrine\ORM\Internal\Hydration; -use Doctrine\DBAL\Connection, - Doctrine\ORM\PersistentCollection, +use Doctrine\ORM\PersistentCollection, Doctrine\ORM\Query, Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\Collection; @@ -35,29 +34,25 @@ use Doctrine\DBAL\Connection, */ class ObjectHydrator extends AbstractHydrator { - /* - * These two properties maintain their values between hydration runs. + /* Local ClassMetadata cache to avoid going to the EntityManager all the time. + * This local cache is maintained between hydration runs and not cleared. */ - /* Local ClassMetadata cache to avoid going to the EntityManager all the time. */ private $_ce = array(); - /* - * The following parts are reinitialized on every hydration run. - */ - private $_isSimpleQuery = false; + + /* The following parts are reinitialized on every hydration run. */ + private $_allowPartialObjects = false; - private $_identifierMap = array(); - private $_resultPointers = array(); - private $_idTemplate = array(); + private $_identifierMap; + private $_resultPointers; + private $_idTemplate; private $_resultCounter; - private $_rootAliases = array(); private $_fetchedAssociations; - /** Memory for initialized collections. */ + private $_rootAliases = array(); private $_initializedCollections = array(); /** @override */ protected function _prepare() { - $this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1; $this->_allowPartialObjects = $this->_em->getConfiguration()->getAllowPartialObjects() || isset($this->_hints[Query::HINT_FORCE_PARTIAL_LOAD]); @@ -77,7 +72,8 @@ class ObjectHydrator extends AbstractHydrator $this->_ce[$className] = $class; } - // Remember which associations are "fetch joined" + // Remember which associations are "fetch joined", so that we know where to inject + // collection stubs or proxies and where not. if (isset($this->_rsm->relationMap[$dqlAlias])) { $targetClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]]; $targetClass = $this->_em->getClassMetadata($targetClassName); @@ -96,6 +92,11 @@ class ObjectHydrator extends AbstractHydrator } } + /** + * {@inheritdoc} + * + * @override + */ protected function _cleanup() { parent::_cleanup(); @@ -112,7 +113,7 @@ class ObjectHydrator extends AbstractHydrator { $result = array(); $cache = array(); - while ($data = $this->_stmt->fetch(Connection::FETCH_ASSOC)) { + while ($data = $this->_stmt->fetch(\Doctrine\DBAL\Connection::FETCH_ASSOC)) { $this->_hydrateRow($data, $cache, $result); } @@ -131,7 +132,7 @@ class ObjectHydrator extends AbstractHydrator * @param object $entity The entity to which the collection belongs. * @param string $name The name of the field on the entity that holds the collection. */ - private function initRelatedCollection($entity, $name) + private function _initRelatedCollection($entity, $name) { $oid = spl_object_hash($entity); $class = $this->_ce[get_class($entity)]; @@ -155,7 +156,7 @@ class ObjectHydrator extends AbstractHydrator * @param $dqlAlias The DQL alias of the entity's class. * @return object The entity. */ - private function getEntity(array $data, $dqlAlias) + private function _getEntity(array $data, $dqlAlias) { $className = $this->_rsm->aliasMap[$dqlAlias]; if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) { @@ -232,7 +233,7 @@ class ObjectHydrator extends AbstractHydrator */ protected function _hydrateRow(array &$data, array &$cache, array &$result) { - // 1) Initialize + // Initialize $id = $this->_idTemplate; // initialize the id-memory $nonemptyComponents = array(); $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); @@ -253,17 +254,14 @@ class ObjectHydrator extends AbstractHydrator $parent = $this->_rsm->parentAliasMap[$dqlAlias]; - // Get a reference to the right element in the result tree. - // This element will get the associated element attached. + // Get a reference to the right object to which the joined result belongs. if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) { $first = reset($this->_resultPointers); - // TODO: Exception if key($first) === null ? $baseElement = $this->_resultPointers[$parent][key($first)]; } else if (isset($this->_resultPointers[$parent])) { $baseElement = $this->_resultPointers[$parent]; } else { - unset($this->_resultPointers[$dqlAlias]); // Ticket #1228 - continue; + throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $parent); } $parentClass = get_class($baseElement); @@ -278,16 +276,15 @@ class ObjectHydrator extends AbstractHydrator // Collection-valued association if (isset($nonemptyComponents[$dqlAlias])) { if ( ! isset($this->_initializedCollections[$oid . $relationField])) { - $reflFieldValue = $this->initRelatedCollection($baseElement, $relationField); + $reflFieldValue = $this->_initRelatedCollection($baseElement, $relationField); } - $path = $parent . '.' . $dqlAlias; - $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); - $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; - $indexIsValid = $index !== false ? $reflFieldValue->containsKey($index) : false; + $indexExists = isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]]); + $index = $indexExists ? $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] : false; + $indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false; if ( ! $indexExists || ! $indexIsValid) { - $element = $this->getEntity($data, $dqlAlias); + $element = $this->_getEntity($data, $dqlAlias); // If it's a bi-directional many-to-many, also initialize the reverse collection. if ($relation->isManyToMany()) { @@ -295,45 +292,42 @@ class ObjectHydrator extends AbstractHydrator $inverseFieldName = $this->_ce[$entityName]->inverseMappings[$relation->sourceEntityName][$relationField]->sourceFieldName; // Only initialize reverse collection if it is not yet initialized. if ( ! isset($this->_initializedCollections[spl_object_hash($element) . $inverseFieldName])) { - $this->initRelatedCollection($element, $inverseFieldName); + $this->_initRelatedCollection($element, $inverseFieldName); } } else if ($relation->mappedByFieldName) { // Only initialize reverse collection if it is not yet initialized. if ( ! isset($this->_initializedCollections[spl_object_hash($element) . $relation->mappedByFieldName])) { - $this->initRelatedCollection($element, $relation->mappedByFieldName); + $this->_initRelatedCollection($element, $relation->mappedByFieldName); } } } if (isset($this->_rsm->indexByMap[$dqlAlias])) { $field = $this->_rsm->indexByMap[$dqlAlias]; - $indexValue = $this->_ce[$entityName] - ->reflFields[$field] - ->getValue($element); + $indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element); $reflFieldValue->hydrateSet($indexValue, $element); + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $indexValue; } else { $reflFieldValue->hydrateAdd($element); + $reflFieldValue->last(); + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $reflFieldValue->key(); } - - $reflFieldValue->last(); - $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $reflFieldValue->key(); } + + // Update result pointer + $this->_resultPointers[$dqlAlias] = $index === false ? $element : $reflFieldValue[$index]; + } else if ( ! $reflFieldValue) { $coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection); $reflField->setValue($baseElement, $coll); - $reflFieldValue = $coll; $this->_uow->setOriginalEntityProperty($oid, $relationField, $coll); } - - // Update result pointer - $this->_resultPointers[$dqlAlias] = $index === false ? - $reflFieldValue->last() : $reflFieldValue[$index]; } else { // Single-valued association $reflFieldValue = $reflField->getValue($baseElement); if ( ! $reflFieldValue) { if (isset($nonemptyComponents[$dqlAlias])) { - $element = $this->getEntity($data, $dqlAlias); + $element = $this->_getEntity($data, $dqlAlias); $reflField->setValue($baseElement, $element); $this->_uow->setOriginalEntityProperty($oid, $relationField, $element); $targetClass = $this->_ce[$relation->targetEntityName]; @@ -351,10 +345,9 @@ class ObjectHydrator extends AbstractHydrator $targetClass->reflFields[$relation->mappedByFieldName]->setValue($element, $baseElement); } } - } - - // Update result pointer - if ($reflFieldValue !== null) { + // else leave $reflFieldValue null for single-valued associations + } else { + // Update result pointer $this->_resultPointers[$dqlAlias] = $reflFieldValue; } } @@ -362,36 +355,34 @@ class ObjectHydrator extends AbstractHydrator // Its a root result element $this->_rootAliases[$dqlAlias] = true; // Mark as root alias - if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { - $element = $this->getEntity($rowData[$dqlAlias], $dqlAlias); + if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) { + $element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias); if (isset($this->_rsm->indexByMap[$dqlAlias])) { $field = $this->_rsm->indexByMap[$dqlAlias]; + $key = $this->_ce[$entityName]->reflFields[$field]->getValue($element); if ($this->_rsm->isMixed) { - $result[] = array( - $this->_ce[$entityName] - ->reflFields[$field] - ->getValue($element) => $element - ); + $element = array($key => $element); + $result[] = $element; ++$this->_resultCounter; + end($result); + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result); } else { - $result[$this->_ce[$entityName] - ->reflFields[$field] - ->getValue($element)] = $element; + $result[$key] = $element; + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key; } } else { if ($this->_rsm->isMixed) { - $result[] = array($element); + $element = array(0 => $element); ++$this->_resultCounter; - } else { - $result[] = $element; } + $result[] = $element; + end($result); + $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result); } - $last = end($result); - $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = key($result); - // Update result pointer - $this->_resultPointers[$dqlAlias] = $last; + $this->_resultPointers[$dqlAlias] = $element; + } else { // Update result pointer $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; @@ -407,10 +398,4 @@ class ObjectHydrator extends AbstractHydrator } } } - - /** {@inheritdoc} */ - protected function _getRowContainer() - { - return array(); - } }