diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 2bbab4042..b03c51d73 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -42,30 +42,22 @@ class ObjectHydrator extends AbstractHydrator /* The following parts are reinitialized on every hydration run. */ - private $_allowPartialObjects = false; private $_identifierMap; private $_resultPointers; private $_idTemplate; private $_resultCounter; - private $_fetchedAssociations; private $_rootAliases = array(); private $_initializedCollections = array(); private $_existingCollections = array(); - private $_proxyFactory; //private $_createdEntities; /** @override */ protected function _prepare() { - $this->_allowPartialObjects = isset($this->_hints[Query::HINT_FORCE_PARTIAL_LOAD]); - - $this->_proxyFactory = $this->_em->getProxyFactory(); - $this->_identifierMap = $this->_resultPointers = - $this->_idTemplate = - $this->_fetchedAssociations = array(); + $this->_idTemplate = array(); $this->_resultCounter = 0; foreach ($this->_rsm->aliasMap as $dqlAlias => $className) { @@ -85,13 +77,13 @@ class ObjectHydrator extends AbstractHydrator $targetClass = $this->_getClassMetadata($targetClassName); $this->_ce[$targetClassName] = $targetClass; $assoc = $targetClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; - $this->_fetchedAssociations[$assoc->sourceEntityName][$assoc->sourceFieldName] = true; + $this->_hints['fetched'][$assoc->sourceEntityName][$assoc->sourceFieldName] = true; if ($assoc->mappedByFieldName) { - $this->_fetchedAssociations[$assoc->targetEntityName][$assoc->mappedByFieldName] = true; + $this->_hints['fetched'][$assoc->targetEntityName][$assoc->mappedByFieldName] = true; } else { if (isset($targetClass->inverseMappings[$className][$assoc->sourceFieldName])) { $inverseAssoc = $targetClass->inverseMappings[$className][$assoc->sourceFieldName]; - $this->_fetchedAssociations[$assoc->targetEntityName][$inverseAssoc->sourceFieldName] = true; + $this->_hints['fetched'][$assoc->targetEntityName][$inverseAssoc->sourceFieldName] = true; } } } @@ -186,53 +178,7 @@ class ObjectHydrator extends AbstractHydrator unset($data[$discrColumn]); } - $entity = $this->_uow->createEntity($className, $data, $this->_hints); - - //FIXME: If $entity comes from the identity map there is no need to do this! - // Properly initialize any unfetched associations, if partial objects are not allowed. - if ( ! $this->_allowPartialObjects) { - $oid = spl_object_hash($entity); - foreach ($this->_getClassMetadata($className)->associationMappings as $field => $assoc) { - // Check if the association is not among the fetch-joined associatons already. - if ( ! isset($this->_fetchedAssociations[$className][$field])) { - if ($assoc->isOneToOne()) { - $joinColumns = array(); - foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { - $joinColumns[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]]; - } - //TODO: If its in the identity map just get it from there if possible! - if ($assoc->isLazilyFetched() /*&& ! $assoc->isOptional*/) { - // Inject proxy - $proxy = $this->_proxyFactory->getAssociationProxy($entity, $assoc, $joinColumns); - $this->_uow->setOriginalEntityProperty($oid, $field, $proxy); - $this->_ce[$className]->reflFields[$field]->setValue($entity, $proxy); - } else { - // Eager load - //TODO: Allow more efficient and configurable batching of these loads - $assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumns); - } - } else { - // Inject collection - $reflField = $this->_ce[$className]->reflFields[$field]; - $pColl = new PersistentCollection($this->_em, - $this->_getClassMetadata($assoc->targetEntityName), - $reflField->getValue($entity) ?: new ArrayCollection - ); - $pColl->setOwner($entity, $assoc); - $reflField->setValue($entity, $pColl); - if ($assoc->isLazilyFetched()) { - $pColl->setInitialized(false); - } else { - //TODO: Allow more efficient and configurable batching of these loads - $assoc->load($entity, $pColl, $this->_em); - } - $this->_uow->setOriginalEntityProperty($oid, $field, $pColl); - } - } - } - } - - return $entity; + return $this->_uow->createEntity($className, $data, $this->_hints); } private function _getEntityFromIdentityMap($className, array $data) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 4cd74d105..b4b692523 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -185,7 +185,8 @@ class ClassMetadataFactory $class->setIdentifier($parent->identifier); $class->setVersioned($parent->isVersioned); $class->setVersionField($parent->versionField); - $class->setDiscriminatorMap($parent->discriminatorMap); + $class->setDiscriminatorMap($parent->discriminatorMap); + $class->resultColumnNames = $parent->resultColumnNames; } // Invoke driver diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 32f49c3bf..2f9f6c5a7 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -282,6 +282,7 @@ class JoinedSubclassPersister extends StandardEntityPersister $tableAliases[$className] = 't' . $aliasIndex++; } + // Add regular columns $columnList = ''; foreach ($this->_class->fieldMappings as $fieldName => $mapping) { $tableAlias = isset($mapping['inherited']) ? @@ -290,6 +291,15 @@ class JoinedSubclassPersister extends StandardEntityPersister $columnList .= $tableAlias . '.' . $this->_class->getQuotedColumnName($fieldName, $this->_platform); } + // Add foreign key columns + foreach ($this->_class->associationMappings as $assoc2) { + if ($assoc2->isOwningSide && $assoc2->isOneToOne()) { + foreach ($assoc2->targetToSourceKeyColumns as $srcColumn) { + $columnList .= ', ' . $assoc2->getQuotedJoinColumnName($srcColumn, $this->_platform); + } + } + } + // Add discriminator column if ($this->_class->rootEntityName == $this->_class->name) { $columnList .= ', ' . $baseTableAlias . '.' . @@ -315,6 +325,7 @@ class JoinedSubclassPersister extends StandardEntityPersister // OUTER JOIN sub tables foreach ($this->_class->subClasses as $subClassName) { + //FIXME: Add columns and foreign key columns of inherited classes to the select list $subClass = $this->_em->getClassMetadata($subClassName); $tableAlias = $tableAliases[$subClassName]; $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON '; diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 5225d3978..8e3a1dfd1 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -27,6 +27,7 @@ use Doctrine\Common\DoctrineException, Doctrine\DBAL\Types\Type, Doctrine\ORM\EntityManager, Doctrine\ORM\UnitOfWork, + Doctrine\ORM\Query, Doctrine\ORM\PersistentCollection, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Events; @@ -236,9 +237,8 @@ class StandardEntityPersister if ($isVersioned = $this->_class->isVersioned) { $versionField = $this->_class->versionField; $versionFieldType = $this->_class->getTypeOfField($versionField); - $where[$this->_class->fieldNames[$versionField]] = Type::getType( - $this->_class->fieldMappings[$versionField]['type'] - )->convertToDatabaseValue($entity->version, $this->_platform); + $where[$versionField] = Type::getType($versionFieldType) + ->convertToDatabaseValue($entity->version, $this->_platform); $versionFieldColumnName = $this->_class->getQuotedColumnName($versionField, $this->_platform); if ($versionFieldType == 'integer') { $set[] = $versionFieldColumnName . ' = ' . $versionFieldColumnName . ' + 1'; @@ -504,16 +504,15 @@ class StandardEntityPersister } else if ($this->_class->discriminatorColumn !== null && $column == $this->_class->discriminatorColumn['name']) { $entityName = $this->_class->discriminatorMap[$value]; } else { + $data[$column] = $value; $joinColumnValues[$column] = $value; } } - if ($entity === null) { - $entity = $this->_em->getUnitOfWork()->createEntity($entityName, $data); - } else { - foreach ($data as $field => $value) { - $this->_class->reflFields[$field]->setValue($entity, $value); - } + $hints = array(); + + if ($entity !== null) { + $hints[Query::HINT_REFRESH] = true; $id = array(); if ($this->_class->isIdentifierComposite) { foreach ($this->_class->identifier as $fieldName) { @@ -524,37 +523,8 @@ class StandardEntityPersister } $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } - - // Initialize associations - foreach ($this->_class->associationMappings as $field => $assoc) { - if ($assoc->isOneToOne()) { - if ($assoc->isLazilyFetched()) { - // Inject proxy - $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues); - $this->_class->reflFields[$field]->setValue($entity, $proxy); - } else { - // Eager load - //TODO: Allow more efficient and configurable batching of these loads - $assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumnValues); - } - } else { - // Inject collection - $coll = new PersistentCollection( - $this->_em, - $this->_em->getClassMetadata($assoc->targetEntityName), - /*$this->_class->reflFields[$field]->getValue($entity) ?:*/ new ArrayCollection); - $coll->setOwner($entity, $assoc); - $this->_class->reflFields[$field]->setValue($entity, $coll); - if ($assoc->isLazilyFetched()) { - $coll->setInitialized(false); - } else { - //TODO: Allow more efficient and configurable batching of these loads - $assoc->load($entity, $coll, $this->_em); - } - } - } - - return $entity; + + return $this->_em->getUnitOfWork()->createEntity($entityName, $data, $hints); } /** @@ -566,22 +536,23 @@ class StandardEntityPersister protected function _getSelectEntitiesSql(array &$criteria, $assoc = null) { $columnList = ''; + + // Add regular columns to select list foreach ($this->_class->fieldNames as $field) { if ($columnList != '') $columnList .= ', '; $columnList .= $this->_class->getQuotedColumnName($field, $this->_platform); } - $joinColumnNames = array(); + // Add join columns (foreign keys) to select list foreach ($this->_class->associationMappings as $assoc2) { if ($assoc2->isOwningSide && $assoc2->isOneToOne()) { foreach ($assoc2->targetToSourceKeyColumns as $srcColumn) { - $joinColumnNames[] = $srcColumn; $columnList .= ', ' . $assoc2->getQuotedJoinColumnName($srcColumn, $this->_platform); } } } - - $joinSql = ''; + + // Construct WHERE conditions $conditionSql = ''; foreach ($criteria as $field => $value) { if ($conditionSql != '') { @@ -600,7 +571,6 @@ class StandardEntityPersister return 'SELECT ' . $columnList . ' FROM ' . $this->_class->getQuotedTableName($this->_platform) - . $joinSql . ($conditionSql ? ' WHERE ' . $conditionSql : ''); } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 2afb51317..b11cf3f13 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1653,7 +1653,7 @@ class UnitOfWork implements PropertyChangedListener * @return object The created entity instance. * @internal Highly performance-sensitive method. */ - public function createEntity($className, array $data, $hints = array()) + public function createEntity($className, array $data, &$hints = array()) { $class = $this->_em->getClassMetadata($className); @@ -1695,9 +1695,53 @@ class UnitOfWork implements PropertyChangedListener } } } + + // Properly initialize any unfetched associations, if partial objects are not allowed. + if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { + foreach ($class->associationMappings as $field => $assoc) { + // Check if the association is not among the fetch-joined associatons already. + if ( ! isset($hints['fetched'][$className][$field])) { + if ($assoc->isOneToOne()) { + $joinColumns = array(); + if ($assoc->isOwningSide) { + foreach ($assoc->targetToSourceKeyColumns as $srcColumn) { + $joinColumns[$srcColumn] = $data[$assoc->joinColumnFieldNames[$srcColumn]]; + } + } + //TODO: If its in the identity map just get it from there if possible! + if ($assoc->isLazilyFetched() /*&& $assoc->isOwningSide)*/) { + // Inject proxy + $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumns); + $this->_originalEntityData[$oid][$field] = $proxy; + $class->reflFields[$field]->setValue($entity, $proxy); + } else { + // Eager load + //TODO: Allow more efficient and configurable batching of these loads + $assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumns); + } + } else { + // Inject collection + $reflField = $class->reflFields[$field]; + $pColl = new PersistentCollection($this->_em, + $this->_em->getClassMetadata($assoc->targetEntityName), + $reflField->getValue($entity) ?: new ArrayCollection + ); + $pColl->setOwner($entity, $assoc); + $reflField->setValue($entity, $pColl); + if ($assoc->isLazilyFetched()) { + $pColl->setInitialized(false); + } else { + //TODO: Allow more efficient and configurable batching of these loads + $assoc->load($entity, $pColl, $this->_em); + } + $this->_originalEntityData[$oid][$field] = $pColl; + } + } + } + } } - //TODO: These should be invoked later, because associations are not yet loaded here. + //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. if (isset($class->lifecycleCallbacks[Events::postLoad])) { $class->invokeLifecycleCallbacks(Events::postLoad, $entity); } diff --git a/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php index 014a99974..8a63e15bf 100644 --- a/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php +++ b/tests/Doctrine/Tests/ORM/Performance/HydrationPerformanceTest.php @@ -339,7 +339,7 @@ class HydrationPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase * * MAXIMUM TIME: 1 second */ - public function testMixedQueryFetchJoinFullObjectHydrationPerformance200Rows() + public function testMixedQueryFetchJoinFullObjectHydrationPerformance2000Rows() { $rsm = new ResultSetMapping; $rsm->addEntityResult('Doctrine\Tests\Models\CMS\CmsUser', 'u');