1
0
mirror of synced 2024-12-14 23:26:04 +03:00
doctrine2/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php

403 lines
16 KiB
PHP

<?php
/*
* $Id$
*
* 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 LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Internal\Hydration;
use \PDO;
use Doctrine\ORM\PersistentCollection;
/**
* The ObjectHydrator constructs an object graph out of an SQL result set.
*
* @author robo
* @since 2.0
*/
class ObjectHydrator extends AbstractHydrator
{
/** Collections initialized by the hydrator */
private $_collections = array();
/** Memory for initialized relations */
private $_initializedRelations = array();
private $_metadataMap = array();
private $_rootAlias;
private $_rootEntityName;
private $_isSimpleQuery = false;
private $_identifierMap = array();
private $_resultPointers = array();
private $_idTemplate = array();
private $_resultCounter = 0;
protected function _prepare($parserResult)
{
parent::_prepare($parserResult);
//reset($this->_queryComponents);
//$this->_rootAlias = key($this->_queryComponents);
$this->_rootAlias = $parserResult->getDefaultQueryComponentAlias();
$this->_rootEntityName = $this->_queryComponents[$this->_rootAlias]['metadata']->getClassName();
if (isset($this->_queryComponents['dctrn'])) {
$this->_isSimpleQuery = count($this->_queryComponents) <= 2;
} else {
$this->_isSimpleQuery = count($this->_queryComponents) <= 1;
}
$this->_identifierMap = array();
$this->_resultPointers = array();
$this->_idTemplate = array();
$this->_resultCounter = 0;
foreach ($this->_queryComponents as $dqlAlias => $component) {
$this->_identifierMap[$dqlAlias] = array();
$this->_resultPointers[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = '';
}
}
/**
* {@inheritdoc}
*
* @override
*/
protected function _hydrateAll()
{
$s = microtime(true);
if ($this->_parserResult->isMixedQuery()) {
$result = array();
} else {
$result = new \Doctrine\Common\Collections\Collection;
}
$cache = array();
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->_hydrateRow($data, $cache, $result);
}
// Take snapshots from all initialized collections
foreach ($this->_collections as $coll) {
$coll->takeSnapshot();
$coll->setHydrationFlag(false);
}
// Clean up
$this->_collections = array();
$this->_initializedRelations = array();
$this->_metadataMap = array();
$e = microtime(true);
echo 'Hydration took: ' . ($e - $s) . PHP_EOL;
return $result;
}
/**
* Updates the result pointer for an Entity. The result pointers point to the
* last seen instance of each Entity type. This is used for graph construction.
*
* @param array $resultPointers The result pointers.
* @param Collection $coll The element.
* @param boolean|integer $index Index of the element in the collection.
* @param string $dqlAlias
* @param boolean $oneToOne Whether it is a single-valued association or not.
*/
private function updateResultPointer(&$coll, $index, $dqlAlias, $oneToOne)
{
if ($coll === null) {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
return;
}
if ($index !== false) {
$this->_resultPointers[$dqlAlias] = $coll[$index];
return;
}
if ( ! is_object($coll)) {
end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
} else if ($coll instanceof \Doctrine\Common\Collections\Collection) {
if (count($coll) > 0) {
$this->_resultPointers[$dqlAlias] = $coll->last();
}
} else {
$this->_resultPointers[$dqlAlias] = $coll;
}
}
private function getCollection($component)
{
$coll = new PersistentCollection($this->_em, $component);
$this->_collections[] = $coll;
return $coll;
}
private function initRelatedCollection($entity, $name)
{
$oid = spl_object_hash($entity);
$classMetadata = $this->_metadataMap[$oid];
if ( ! isset($this->_initializedRelations[$oid][$name])) {
$relation = $classMetadata->getAssociationMapping($name);
$relatedClass = $this->_em->getClassMetadata($relation->getTargetEntityName());
$coll = $this->getCollection($relatedClass->getClassName());
$coll->setOwner($entity, $relation);
$coll->setHydrationFlag(true);
$classMetadata->getReflectionProperty($name)->setValue($entity, $coll);
$this->_initializedRelations[$oid][$name] = true;
$this->_uow->setOriginalEntityProperty($oid, $name, $coll);
}
}
private function isIndexKeyInUse($entity, $assocField, $indexField)
{
return $this->_metadataMap[spl_object_hash($entity)]->getReflectionProperty($assocField)
->getValue($entity)->containsKey($indexField);
}
private function getLastKey($coll)
{
// Check needed because of mixed results.
// is_object instead of is_array because is_array is slow on large arrays.
if (is_object($coll)) {
$coll->last();
return $coll->key();
} else {
end($coll);
return key($coll);
}
}
private function getEntity(array $data, $className)
{
$entity = $this->_uow->createEntity($className, $data);
$oid = spl_object_hash($entity);
$this->_metadataMap[$oid] = $this->_em->getClassMetadata($className);
return $entity;
}
/**
* Adds an element to an indexed collection-valued property.
*
* @param <type> $entity1
* @param <type> $property
* @param <type> $entity2
* @param <type> $indexField
*/
private function addRelatedIndexedEntity($entity1, $property, $entity2, $indexField)
{
$classMetadata1 = $this->_metadataMap[spl_object_hash($entity1)];
$classMetadata2 = $this->_metadataMap[spl_object_hash($entity2)];
$indexValue = $classMetadata2->getReflectionProperty($indexField)->getValue($entity2);
$classMetadata1->getReflectionProperty($property)->getValue($entity1)->set($indexValue, $entity2);
}
/**
* Adds an element to a collection-valued property.
*
* @param <type> $entity1
* @param <type> $property
* @param <type> $entity2
*/
private function addRelatedEntity($entity1, $property, $entity2)
{
$classMetadata1 = $this->_metadataMap[spl_object_hash($entity1)];
$classMetadata1->getReflectionProperty($property)->getValue($entity1)->add($entity2);
}
/**
* Checks whether a field on an entity has a non-null value.
*
* @param object $entity
* @param string $field
* @return boolean
*/
private function isFieldSet($entity, $field)
{
return $this->_metadataMap[spl_object_hash($entity)]
->getReflectionProperty($field)
->getValue($entity) !== null;
}
/**
* Sets a related element.
*
* @param <type> $entity1
* @param <type> $property
* @param <type> $entity2
*/
private function setRelatedElement($entity1, $property, $entity2)
{
$oid = spl_object_hash($entity1);
$classMetadata1 = $this->_metadataMap[$oid];
$classMetadata1->getReflectionProperty($property)->setValue($entity1, $entity2);
$this->_uow->setOriginalEntityProperty($oid, $property, $entity2);
$relation = $classMetadata1->getAssociationMapping($property);
if ($relation->isOneToOne()) {
$targetClass = $this->_em->getClassMetadata($relation->getTargetEntityName());
if ($relation->isOwningSide()) {
// If there is an inverse mapping on the target class its bidirectional
if ($targetClass->hasInverseAssociationMapping($property)) {
$oid2 = spl_object_hash($entity2);
$sourceProp = $targetClass->getInverseAssociationMapping($fieldName)->getSourceFieldName();
$targetClass->getReflectionProperty($sourceProp)->setValue($entity2, $entity1);
}
} else {
// For sure bidirectional, as there is no inverse side in unidirectional
$mappedByProp = $relation->getMappedByFieldName();
$targetClass->getReflectionProperty($mappedByProp)->setValue($entity2, $entity1);
}
}
}
/**
* {@inheritdoc}
*
* @override
*/
protected function _hydrateRow(array &$data, array &$cache, &$result)
{
// 1) Initialize
$id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = array();
$rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
$rootAlias = $this->_rootAlias;
// 2) Hydrate the data of the root entity from the current row
// Check for an existing element
$index = false;
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$rootAlias][$id[$rootAlias]])) {
$element = $this->_uow->createEntity($this->_rootEntityName, $rowData[$rootAlias]);
$oid = spl_object_hash($element);
$this->_metadataMap[$oid] = $this->_em->getClassMetadata($this->_rootEntityName);
if ($field = $this->_getCustomIndexField($rootAlias)) {
if ($this->_parserResult->isMixedQuery()) {
$result[] = array(
$this->_metadataMap[$oid]->getReflectionProperty($field)
->getValue($element) => $element
);
++$this->_resultCounter;
} else {
$result->set($element, $this->_metadataMap[$oid]
->getReflectionProperty($field)
->getValue($element));
}
} else {
if ($this->_parserResult->isMixedQuery()) {
$result[] = array($element);
++$this->_resultCounter;
} else {
$result->add($element);
}
}
$this->_identifierMap[$rootAlias][$id[$rootAlias]] = $this->getLastKey($result);
} else {
$index = $this->_identifierMap[$rootAlias][$id[$rootAlias]];
}
$this->updateResultPointer($result, $index, $rootAlias, false);
unset($rowData[$rootAlias]);
// end hydrate data of the root component for the current row
// Extract scalar values. They're appended at the end.
if (isset($rowData['scalars'])) {
$scalars = $rowData['scalars'];
unset($rowData['scalars']);
}
// 3) Now hydrate the rest of the data found in the current row, that
// belongs to other (related) entities.
foreach ($rowData as $dqlAlias => $data) {
$index = false;
$map = $this->_queryComponents[$dqlAlias];
$entityName = $map['metadata']->getClassName();
$parent = $map['parent'];
$relationAlias = $map['relation']->getSourceFieldName();
$path = $parent . '.' . $dqlAlias;
// Get a reference to the right element in the result tree.
// This element will get the associated element attached.
if ($this->_parserResult->isMixedQuery() && $parent == $rootAlias) {
$key = key(reset($this->_resultPointers));
// TODO: Exception if $key === null ?
$baseElement =& $this->_resultPointers[$parent][$key];
} else if (isset($this->_resultPointers[$parent])) {
$baseElement =& $this->_resultPointers[$parent];
} else {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
continue;
}
$oid = spl_object_hash($baseElement);
// Check the type of the relation (many or single-valued)
if ( ! $map['relation']->isOneToOne()) {
$oneToOne = false;
if (isset($nonemptyComponents[$dqlAlias])) {
$this->initRelatedCollection($baseElement, $relationAlias);
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? $this->isIndexKeyInUse($baseElement, $relationAlias, $index) : false;
if ( ! $indexExists || ! $indexIsValid) {
$element = $this->getEntity($data, $entityName);
if ($field = $this->_getCustomIndexField($dqlAlias)) {
$this->addRelatedIndexedEntity($baseElement, $relationAlias, $element, $field);
} else {
$this->addRelatedEntity($baseElement, $relationAlias, $element);
}
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $this->getLastKey(
$this->_metadataMap[$oid]
->getReflectionProperty($relationAlias)
->getValue($baseElement));
}
} else if ( ! $this->isFieldSet($baseElement, $relationAlias)) {
$coll = new PersistentCollection($this->_em, $entityName);
$this->_collections[] = $coll;
$this->setRelatedElement($baseElement, $relationAlias, $coll);
}
} else {
$oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) &&
! $this->isFieldSet($baseElement, $relationAlias)) {
$this->setRelatedElement($baseElement, $relationAlias, null);
} else if ( ! $this->isFieldSet($baseElement, $relationAlias)) {
$this->setRelatedElement($baseElement, $relationAlias,
$this->getEntity($data, $entityName));
}
}
$coll = $this->_metadataMap[$oid]
->getReflectionProperty($relationAlias)
->getValue($baseElement);
if ($coll !== null) {
$this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
}
}
// Append scalar values to mixed result sets
if (isset($scalars)) {
foreach ($scalars as $name => $value) {
$result[$this->_resultCounter - 1][$name] = $value;
}
}
}
/** {@inheritdoc} */
protected function _getRowContainer()
{
return new \Doctrine\Common\Collections\Collection;
}
}