mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
[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:
@@ -70,6 +70,8 @@ abstract class AbstractQuery
|
||||
*/
|
||||
const HYDRATE_SIMPLEOBJECT = 5;
|
||||
|
||||
const HYDRATE_PROXY = 6;
|
||||
|
||||
/**
|
||||
* The parameter map of this query.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
43
lib/Doctrine/ORM/Internal/Hydration/ProxyHydrator.php
Normal file
43
lib/Doctrine/ORM/Internal/Hydration/ProxyHydrator.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +160,8 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
const FETCH_EXTRA_LAZY = 4;
|
||||
|
||||
const FETCH_USE_PROXY = 5;
|
||||
|
||||
/**
|
||||
* Identifies a one-to-one association.
|
||||
*/
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user