[DDC-2763] Inheritance. CTI & STI. Improve lazy load associated entity, when target entity in association mapping is not last leaf in class hierarchy.

Issue fixed by adding class "ProxyHydrator" and new fetch mode for associations "FETCH_USE_PROXY".
Users need to mark the associations with "fetch=USE_PROXY" to trigger new lazy loading behaviour.
This PR is not intended to be final. It still lacks of tests.
This commit is contained in:
Sergio Santoro
2015-06-27 19:45:08 +02:00
parent 6b3056ff8c
commit 92c1a8fe1e
7 changed files with 99 additions and 31 deletions

View File

@@ -70,6 +70,8 @@ abstract class AbstractQuery
*/
const HYDRATE_SIMPLEOBJECT = 5;
const HYDRATE_PROXY = 6;
/**
* The parameter map of this query.
*

View File

@@ -787,6 +787,9 @@ use Doctrine\Common\Util\ClassUtils;
case Query::HYDRATE_SIMPLEOBJECT:
return new Internal\Hydration\SimpleObjectHydrator($this);
case Query::HYDRATE_PROXY:
return new Internal\Hydration\ProxyHydrator($this);
default:
if (($class = $this->config->getCustomHydrationMode($hydrationMode)) !== null) {
return new $class($this);

View File

@@ -0,0 +1,43 @@
<?php
namespace Doctrine\ORM\Internal\Hydration;
class ProxyHydrator extends SimpleObjectHydrator
{
/**
* {@inheritdoc}
*/
protected function hydrateRowData(array $sqlResult, array &$result)
{
$entityName = $this->getEntityName($sqlResult);
$identifier = array();
foreach ($sqlResult as $column => $value) {
// An ObjectHydrator should be used instead of SimpleObjectHydrator
if (isset($this->_rsm->relationMap[$column])) {
throw new \Exception(sprintf('Unable to retrieve association information for column "%s"', $column));
}
$cacheKeyInfo = $this->hydrateColumnInfo($column);
if ( ! $cacheKeyInfo || ! $cacheKeyInfo['isIdentifier']) {
continue;
}
// Convert field to a valid PHP value
if (isset($cacheKeyInfo['type'])) {
$type = $cacheKeyInfo['type'];
$value = $type->convertToPHPValue($value, $this->_platform);
}
$fieldName = $cacheKeyInfo['fieldName'];
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
if ( ! isset($identifier[$fieldName]) || $value !== null) {
$identifier[$fieldName] = $value;
}
}
$result[] = $this->_em->getProxyFactory()->getProxy($entityName, $identifier);
}
}

View File

@@ -79,37 +79,9 @@ class SimpleObjectHydrator extends AbstractHydrator
*/
protected function hydrateRowData(array $sqlResult, array &$result)
{
$entityName = $this->class->name;
$entityName = $this->getEntityName($sqlResult);
$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']);
// Find mapped discriminator column from the result set.
if ($metaMappingDiscrColumnName = array_search($discrColumnName, $this->_rsm->metaMappings)) {
$discrColumnName = $metaMappingDiscrColumnName;
}
if ( ! isset($sqlResult[$discrColumnName])) {
throw HydrationException::missingDiscriminatorColumn($entityName, $discrColumnName, key($this->_rsm->aliasMap));
}
if ($sqlResult[$discrColumnName] === '') {
throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap));
}
$discrMap = $this->class->discriminatorMap;
if ( ! isset($discrMap[$sqlResult[$discrColumnName]])) {
throw HydrationException::invalidDiscriminatorValue($sqlResult[$discrColumnName], array_keys($discrMap));
}
$entityName = $discrMap[$sqlResult[$discrColumnName]];
unset($sqlResult[$discrColumnName]);
}
foreach ($sqlResult as $column => $value) {
// An ObjectHydrator should be used instead of SimpleObjectHydrator
if (isset($this->_rsm->relationMap[$column])) {
@@ -149,4 +121,39 @@ class SimpleObjectHydrator extends AbstractHydrator
$this->_uow->hydrationComplete();
}
}
protected function getEntityName(array &$sqlResult)
{
$entityName = $this->class->name;
// 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']);
// Find mapped discriminator column from the result set.
if ($metaMappingDiscrColumnName = array_search($discrColumnName, $this->_rsm->metaMappings)) {
$discrColumnName = $metaMappingDiscrColumnName;
}
if ( ! isset($sqlResult[$discrColumnName])) {
throw HydrationException::missingDiscriminatorColumn($entityName, $discrColumnName, key($this->_rsm->aliasMap));
}
if ($sqlResult[$discrColumnName] === '') {
throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap));
}
$discrMap = $this->class->discriminatorMap;
if ( ! isset($discrMap[$sqlResult[$discrColumnName]])) {
throw HydrationException::invalidDiscriminatorValue($sqlResult[$discrColumnName], array_keys($discrMap));
}
$entityName = $discrMap[$sqlResult[$discrColumnName]];
unset($sqlResult[$discrColumnName]);
}
return $entityName;
}
}

View File

@@ -160,6 +160,8 @@ class ClassMetadataInfo implements ClassMetadata
*/
const FETCH_EXTRA_LAZY = 4;
const FETCH_USE_PROXY = 5;
/**
* Identifies a one-to-one association.
*/

View File

@@ -40,7 +40,7 @@ final class ManyToOne implements Annotation
*
* @var string
*
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY", "USE_PROXY"})
*/
public $fetch = 'LAZY';

View File

@@ -716,7 +716,18 @@ class BasicEntityPersister implements EntityPersister
$hints[Query::HINT_REFRESH_ENTITY] = $entity;
}
$hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
$hydrationMode = $this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT;
if (
$assoc
&& ClassMetadata::FETCH_USE_PROXY === $assoc['fetch']
&& $assoc['isOwningSide']
&& $this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE
&& ! $this->currentPersisterContext->selectJoinSql
) {
$hydrationMode = Query::HYDRATE_PROXY;
}
$hydrator = $this->em->newHydrator($hydrationMode);
$entities = $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, $hints);
return $entities ? $entities[0] : null;