Refactored UnitOfWork::createEntity, improving its performance.
This commit is contained in:
parent
62ec98a9fc
commit
24f6b74427
3 changed files with 313 additions and 198 deletions
|
@ -23,6 +23,8 @@ use PDO,
|
|||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\ORM\PersistentCollection,
|
||||
Doctrine\ORM\Query,
|
||||
Doctrine\ORM\Event\LifecycleEventArgs,
|
||||
Doctrine\ORM\Events,
|
||||
Doctrine\Common\Collections\ArrayCollection,
|
||||
Doctrine\Common\Collections\Collection;
|
||||
|
||||
|
@ -235,7 +237,21 @@ class ObjectHydrator extends AbstractHydrator
|
|||
}
|
||||
|
||||
$this->_hints['fetchAlias'] = $dqlAlias;
|
||||
return $this->_uow->createEntity($className, $data, $this->_hints);
|
||||
|
||||
$entity = $this->_uow->createEntity($className, $data, $this->_hints);
|
||||
|
||||
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
|
||||
if (isset($this->_ce[$className]->lifecycleCallbacks[Events::postLoad])) {
|
||||
$this->_ce[$className]->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
|
||||
$evm = $this->_em->getEventManager();
|
||||
|
||||
if ($evm->hasListeners(Events::postLoad)) {
|
||||
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
private function _getEntityFromIdentityMap($className, array $data)
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
|
||||
namespace Doctrine\ORM\Internal\Hydration;
|
||||
|
||||
use \PDO;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Query;
|
||||
use \PDO,
|
||||
Doctrine\DBAL\Types\Type,
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\ORM\Event\LifecycleEventArgs,
|
||||
Doctrine\ORM\Events,
|
||||
Doctrine\ORM\Query;
|
||||
|
||||
class SimpleObjectHydrator extends AbstractHydrator
|
||||
{
|
||||
|
@ -125,7 +127,21 @@ class SimpleObjectHydrator extends AbstractHydrator
|
|||
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
|
||||
}
|
||||
|
||||
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints);
|
||||
$uow = $this->_em->getUnitOfWork();
|
||||
$entity = $uow->createEntity($entityName, $data, $this->_hints);
|
||||
|
||||
//TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
|
||||
if (isset($this->class->lifecycleCallbacks[Events::postLoad])) {
|
||||
$this->class->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
|
||||
$evm = $this->_em->getEventManager();
|
||||
|
||||
if ($evm->hasListeners(Events::postLoad)) {
|
||||
$evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
|
||||
}
|
||||
|
||||
$result[] = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1790,6 +1790,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||
public function detach($entity)
|
||||
{
|
||||
$visited = array();
|
||||
|
||||
$this->doDetach($entity, $visited);
|
||||
}
|
||||
|
||||
|
@ -1803,6 +1804,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||
private function doDetach($entity, array &$visited, $noCascade = false)
|
||||
{
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
if (isset($visited[$oid])) {
|
||||
return; // Prevent infinite recursion
|
||||
}
|
||||
|
@ -1814,16 +1816,22 @@ class UnitOfWork implements PropertyChangedListener
|
|||
if ($this->isInIdentityMap($entity)) {
|
||||
$this->removeFromIdentityMap($entity);
|
||||
}
|
||||
unset($this->entityInsertions[$oid], $this->entityUpdates[$oid],
|
||||
$this->entityDeletions[$oid], $this->entityIdentifiers[$oid],
|
||||
$this->entityStates[$oid], $this->originalEntityData[$oid]);
|
||||
|
||||
unset(
|
||||
$this->entityInsertions[$oid],
|
||||
$this->entityUpdates[$oid],
|
||||
$this->entityDeletions[$oid],
|
||||
$this->entityIdentifiers[$oid],
|
||||
$this->entityStates[$oid],
|
||||
$this->originalEntityData[$oid]
|
||||
);
|
||||
break;
|
||||
case self::STATE_NEW:
|
||||
case self::STATE_DETACHED:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$noCascade) {
|
||||
if ( ! $noCascade) {
|
||||
$this->cascadeDetach($entity, $visited);
|
||||
}
|
||||
}
|
||||
|
@ -1838,6 +1846,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||
public function refresh($entity)
|
||||
{
|
||||
$visited = array();
|
||||
|
||||
$this->doRefresh($entity, $visited);
|
||||
}
|
||||
|
||||
|
@ -1851,6 +1860,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||
private function doRefresh($entity, array &$visited)
|
||||
{
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
if (isset($visited[$oid])) {
|
||||
return; // Prevent infinite recursion
|
||||
}
|
||||
|
@ -1858,14 +1868,15 @@ class UnitOfWork implements PropertyChangedListener
|
|||
$visited[$oid] = $entity; // mark visited
|
||||
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
if ($this->getEntityState($entity) == self::STATE_MANAGED) {
|
||||
$this->getEntityPersister($class->name)->refresh(
|
||||
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
|
||||
$entity
|
||||
);
|
||||
} else {
|
||||
|
||||
if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
|
||||
throw new InvalidArgumentException("Entity is not MANAGED.");
|
||||
}
|
||||
|
||||
$this->getEntityPersister($class->name)->refresh(
|
||||
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
|
||||
$entity
|
||||
);
|
||||
|
||||
$this->cascadeRefresh($entity, $visited);
|
||||
}
|
||||
|
@ -1879,21 +1890,33 @@ class UnitOfWork implements PropertyChangedListener
|
|||
private function cascadeRefresh($entity, array &$visited)
|
||||
{
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ( ! $assoc['isCascadeRefresh']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
if ($relatedEntities instanceof Collection) {
|
||||
if ($relatedEntities instanceof PersistentCollection) {
|
||||
|
||||
switch (true) {
|
||||
case ($relatedEntities instanceof PersistentCollection):
|
||||
// Unwrap so that foreach() does not initialize
|
||||
$relatedEntities = $relatedEntities->unwrap();
|
||||
}
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doRefresh($relatedEntity, $visited);
|
||||
}
|
||||
} else if ($relatedEntities !== null) {
|
||||
$this->doRefresh($relatedEntities, $visited);
|
||||
// break; is commented intentionally!
|
||||
|
||||
case ($relatedEntities instanceof Collection):
|
||||
case (is_array($relatedEntities)):
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doRefresh($relatedEntity, $visited);
|
||||
}
|
||||
break;
|
||||
|
||||
case ($relatedEntities !== null):
|
||||
$this->doRefresh($relatedEntities, $visited);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1907,21 +1930,33 @@ class UnitOfWork implements PropertyChangedListener
|
|||
private function cascadeDetach($entity, array &$visited)
|
||||
{
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ( ! $assoc['isCascadeDetach']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
if ($relatedEntities instanceof Collection) {
|
||||
if ($relatedEntities instanceof PersistentCollection) {
|
||||
|
||||
switch (true) {
|
||||
case ($relatedEntities instanceof PersistentCollection):
|
||||
// Unwrap so that foreach() does not initialize
|
||||
$relatedEntities = $relatedEntities->unwrap();
|
||||
}
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doDetach($relatedEntity, $visited);
|
||||
}
|
||||
} else if ($relatedEntities !== null) {
|
||||
$this->doDetach($relatedEntities, $visited);
|
||||
// break; is commented intentionally!
|
||||
|
||||
case ($relatedEntities instanceof Collection):
|
||||
case (is_array($relatedEntities)):
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doDetach($relatedEntity, $visited);
|
||||
}
|
||||
break;
|
||||
|
||||
case ($relatedEntities !== null):
|
||||
$this->doDetach($relatedEntities, $visited);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1969,22 +2004,33 @@ class UnitOfWork implements PropertyChangedListener
|
|||
private function cascadePersist($entity, array &$visited)
|
||||
{
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ( ! $assoc['isCascadePersist']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
if (($relatedEntities instanceof Collection || is_array($relatedEntities))) {
|
||||
if ($relatedEntities instanceof PersistentCollection) {
|
||||
|
||||
switch (true) {
|
||||
case ($relatedEntities instanceof PersistentCollection):
|
||||
// Unwrap so that foreach() does not initialize
|
||||
$relatedEntities = $relatedEntities->unwrap();
|
||||
}
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doPersist($relatedEntity, $visited);
|
||||
}
|
||||
} else if ($relatedEntities !== null) {
|
||||
$this->doPersist($relatedEntities, $visited);
|
||||
// break; is commented intentionally!
|
||||
|
||||
case ($relatedEntities instanceof Collection):
|
||||
case (is_array($relatedEntities)):
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doPersist($relatedEntity, $visited);
|
||||
}
|
||||
break;
|
||||
|
||||
case ($relatedEntities !== null):
|
||||
$this->doPersist($relatedEntities, $visited);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2010,13 +2056,21 @@ class UnitOfWork implements PropertyChangedListener
|
|||
|
||||
$relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);
|
||||
|
||||
if ($relatedEntities instanceof Collection || is_array($relatedEntities)) {
|
||||
// If its a PersistentCollection initialization is intended! No unwrap!
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doRemove($relatedEntity, $visited);
|
||||
}
|
||||
} else if ($relatedEntities !== null) {
|
||||
$this->doRemove($relatedEntities, $visited);
|
||||
switch (true) {
|
||||
case ($relatedEntities instanceof Collection):
|
||||
case (is_array($relatedEntities)):
|
||||
// If its a PersistentCollection initialization is intended! No unwrap!
|
||||
foreach ($relatedEntities as $relatedEntity) {
|
||||
$this->doRemove($relatedEntity, $visited);
|
||||
}
|
||||
break;
|
||||
|
||||
case ($relatedEntities !== null):
|
||||
$this->doRemove($relatedEntities, $visited);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2037,29 +2091,40 @@ class UnitOfWork implements PropertyChangedListener
|
|||
$entityName = get_class($entity);
|
||||
$class = $this->em->getClassMetadata($entityName);
|
||||
|
||||
if ($lockMode == \Doctrine\DBAL\LockMode::OPTIMISTIC) {
|
||||
if (!$class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($entityName);
|
||||
}
|
||||
switch ($lockMode) {
|
||||
case \Doctrine\DBAL\LockMode::OPTIMISTIC;
|
||||
if ( ! $class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($entityName);
|
||||
}
|
||||
|
||||
if ($lockVersion != null) {
|
||||
if ($lockVersion === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
||||
|
||||
if ($entityVersion != $lockVersion) {
|
||||
throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entityVersion);
|
||||
}
|
||||
}
|
||||
} else if (in_array($lockMode, array(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE))) {
|
||||
|
||||
break;
|
||||
|
||||
case \Doctrine\DBAL\LockMode::PESSIMISTIC_READ:
|
||||
case \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE:
|
||||
if (!$this->em->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
|
||||
if (!$this->em->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
$this->getEntityPersister($class->name)->lock(
|
||||
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
|
||||
$lockMode
|
||||
);
|
||||
$this->getEntityPersister($class->name)->lock(
|
||||
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
|
||||
$lockMode
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2187,6 +2252,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||
|
||||
if ($class->isIdentifierComposite) {
|
||||
$id = array();
|
||||
|
||||
foreach ($class->identifier as $fieldName) {
|
||||
if (isset($class->associationMappings[$fieldName])) {
|
||||
$id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']];
|
||||
|
@ -2194,28 +2260,26 @@ class UnitOfWork implements PropertyChangedListener
|
|||
$id[$fieldName] = $data[$fieldName];
|
||||
}
|
||||
}
|
||||
|
||||
$idHash = implode(' ', $id);
|
||||
} else {
|
||||
if (isset($class->associationMappings[$class->identifier[0]])) {
|
||||
$idHash = $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']];
|
||||
} else {
|
||||
/*echo $className;
|
||||
\Doctrine\Common\Util\Debug::dump($data);
|
||||
\Doctrine\Common\Util\Debug::dump($class->identifier);
|
||||
ob_end_flush();
|
||||
ob_start();*/
|
||||
|
||||
$idHash = $data[$class->identifier[0]];
|
||||
}
|
||||
|
||||
$id = array($class->identifier[0] => $idHash);
|
||||
}
|
||||
|
||||
if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
|
||||
$entity = $this->identityMap[$class->rootEntityName][$idHash];
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||
$entity->__isInitialized__ = true;
|
||||
$overrideLocalValues = true;
|
||||
|
||||
if ($entity instanceof NotifyPropertyChanged) {
|
||||
$entity->addPropertyChangedListener($this);
|
||||
}
|
||||
|
@ -2239,156 +2303,175 @@ class UnitOfWork implements PropertyChangedListener
|
|||
} else {
|
||||
$entity = $this->newInstance($class);
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
$this->entityIdentifiers[$oid] = $id;
|
||||
$this->entityStates[$oid] = self::STATE_MANAGED;
|
||||
$this->originalEntityData[$oid] = $data;
|
||||
$this->identityMap[$class->rootEntityName][$idHash] = $entity;
|
||||
|
||||
if ($entity instanceof NotifyPropertyChanged) {
|
||||
$entity->addPropertyChangedListener($this);
|
||||
}
|
||||
|
||||
$overrideLocalValues = true;
|
||||
}
|
||||
|
||||
if ($overrideLocalValues) {
|
||||
foreach ($data as $field => $value) {
|
||||
if (isset($class->fieldMappings[$field])) {
|
||||
$class->reflFields[$field]->setValue($entity, $value);
|
||||
}
|
||||
if ( ! $overrideLocalValues) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
foreach ($data as $field => $value) {
|
||||
if (isset($class->fieldMappings[$field])) {
|
||||
$class->reflFields[$field]->setValue($entity, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// Loading the entity right here, if its in the eager loading map get rid of it there.
|
||||
unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
|
||||
|
||||
if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) {
|
||||
unset($this->eagerLoadingEntities[$class->rootEntityName]);
|
||||
}
|
||||
|
||||
// Properly initialize any unfetched associations, if partial objects are not allowed.
|
||||
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
foreach ($class->associationMappings as $field => $assoc) {
|
||||
// Check if the association is not among the fetch-joined associations already.
|
||||
if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Loading the entity right here, if its in the eager loading map get rid of it there.
|
||||
unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]);
|
||||
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
if (isset($this->eagerLoadingEntities[$class->rootEntityName]) &&
|
||||
! $this->eagerLoadingEntities[$class->rootEntityName]) {
|
||||
unset($this->eagerLoadingEntities[$class->rootEntityName]);
|
||||
}
|
||||
switch (true) {
|
||||
case ($assoc['type'] & ClassMetadata::TO_ONE):
|
||||
if ( ! $assoc['isOwningSide']) {
|
||||
// Inverse side of x-to-one can never be lazy
|
||||
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity));
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$associatedId = array();
|
||||
|
||||
// TODO: Is this even computed right in all cases of composite keys?
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
|
||||
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
|
||||
|
||||
if ($joinColumnValue !== null) {
|
||||
if ($targetClass->containsForeignIdentifier) {
|
||||
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
|
||||
} else {
|
||||
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $associatedId) {
|
||||
// Foreign key is NULL
|
||||
$class->reflFields[$field]->setValue($entity, null);
|
||||
$this->originalEntityData[$oid][$field] = null;
|
||||
|
||||
// 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 associations already.
|
||||
if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
if ($assoc['isOwningSide']) {
|
||||
$associatedId = array();
|
||||
// TODO: Is this even computed right in all cases of composite keys?
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
|
||||
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
|
||||
if ($joinColumnValue !== null) {
|
||||
if ($targetClass->containsForeignIdentifier) {
|
||||
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
|
||||
} else {
|
||||
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ! $associatedId) {
|
||||
// Foreign key is NULL
|
||||
$class->reflFields[$field]->setValue($entity, null);
|
||||
$this->originalEntityData[$oid][$field] = null;
|
||||
} else {
|
||||
if (!isset($hints['fetchMode'][$class->name][$field])) {
|
||||
$hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
|
||||
}
|
||||
|
||||
// Foreign key is set
|
||||
// Check identity map first
|
||||
// FIXME: Can break easily with composite keys if join column values are in
|
||||
// wrong order. The correct order is the one in ClassMetadata#identifier.
|
||||
$relatedIdHash = implode(' ', $associatedId);
|
||||
if (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])) {
|
||||
$newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
|
||||
|
||||
// if this is an uninitialized proxy, we are deferring eager loads,
|
||||
// this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
|
||||
// then we cann append this entity for eager loading!
|
||||
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER &&
|
||||
isset($hints['deferEagerLoad']) &&
|
||||
!$targetClass->isIdentifierComposite &&
|
||||
$newValue instanceof Proxy &&
|
||||
$newValue->__isInitialized__ === false) {
|
||||
|
||||
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
|
||||
}
|
||||
} else {
|
||||
if ($targetClass->subClasses) {
|
||||
// If it might be a subtype, it can not be lazy. There isn't even
|
||||
// a way to solve this with deferred eager loading, which means putting
|
||||
// an entity with subclasses at a *-to-one location is really bad! (performance-wise)
|
||||
$newValue = $this->getEntityPersister($assoc['targetEntity'])
|
||||
->loadOneToOneEntity($assoc, $entity, $associatedId);
|
||||
} else {
|
||||
// Deferred eager load only works for single identifier classes
|
||||
|
||||
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER) {
|
||||
if (isset($hints['deferEagerLoad']) && !$targetClass->isIdentifierComposite) {
|
||||
// TODO: Is there a faster approach?
|
||||
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
|
||||
|
||||
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
|
||||
} else {
|
||||
// TODO: This is very imperformant, ignore it?
|
||||
$newValue = $this->em->find($assoc['targetEntity'], $associatedId);
|
||||
}
|
||||
} else {
|
||||
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
|
||||
}
|
||||
// PERF: Inlined & optimized code from UnitOfWork#registerManaged()
|
||||
$newValueOid = spl_object_hash($newValue);
|
||||
$this->entityIdentifiers[$newValueOid] = $associatedId;
|
||||
$this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
|
||||
$this->entityStates[$newValueOid] = self::STATE_MANAGED;
|
||||
// make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
|
||||
}
|
||||
}
|
||||
$this->originalEntityData[$oid][$field] = $newValue;
|
||||
$class->reflFields[$field]->setValue($entity, $newValue);
|
||||
|
||||
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
|
||||
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Inverse side of x-to-one can never be lazy
|
||||
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])
|
||||
->loadOneToOneEntity($assoc, $entity));
|
||||
}
|
||||
} else {
|
||||
// Inject collection
|
||||
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
|
||||
$pColl->setOwner($entity, $assoc);
|
||||
|
||||
$reflField = $class->reflFields[$field];
|
||||
$reflField->setValue($entity, $pColl);
|
||||
|
||||
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
|
||||
$this->loadCollection($pColl);
|
||||
$pColl->takeSnapshot();
|
||||
} else {
|
||||
$pColl->setInitialized(false);
|
||||
}
|
||||
$this->originalEntityData[$oid][$field] = $pColl;
|
||||
if ( ! isset($hints['fetchMode'][$class->name][$field])) {
|
||||
$hints['fetchMode'][$class->name][$field] = $assoc['fetch'];
|
||||
}
|
||||
}
|
||||
|
||||
// Foreign key is set
|
||||
// Check identity map first
|
||||
// FIXME: Can break easily with composite keys if join column values are in
|
||||
// wrong order. The correct order is the one in ClassMetadata#identifier.
|
||||
$relatedIdHash = implode(' ', $associatedId);
|
||||
|
||||
switch (true) {
|
||||
case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])):
|
||||
$newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash];
|
||||
|
||||
// if this is an uninitialized proxy, we are deferring eager loads,
|
||||
// this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
|
||||
// then we cann append this entity for eager loading!
|
||||
if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER &&
|
||||
isset($hints['deferEagerLoad']) &&
|
||||
!$targetClass->isIdentifierComposite &&
|
||||
$newValue instanceof Proxy &&
|
||||
$newValue->__isInitialized__ === false) {
|
||||
|
||||
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ($targetClass->subClasses):
|
||||
// If it might be a subtype, it can not be lazy. There isn't even
|
||||
// a way to solve this with deferred eager loading, which means putting
|
||||
// an entity with subclasses at a *-to-one location is really bad! (performance-wise)
|
||||
$newValue = $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity, $associatedId);
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (true) {
|
||||
// We are negating the condition here. Other cases will assume it is valid!
|
||||
case ($hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER):
|
||||
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
|
||||
break;
|
||||
|
||||
// Deferred eager load only works for single identifier classes
|
||||
case (isset($hints['deferEagerLoad']) && ! $targetClass->isIdentifierComposite):
|
||||
// TODO: Is there a faster approach?
|
||||
$this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId);
|
||||
|
||||
$newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $associatedId);
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO: This is very imperformant, ignore it?
|
||||
$newValue = $this->em->find($assoc['targetEntity'], $associatedId);
|
||||
break;
|
||||
}
|
||||
|
||||
// PERF: Inlined & optimized code from UnitOfWork#registerManaged()
|
||||
$newValueOid = spl_object_hash($newValue);
|
||||
$this->entityIdentifiers[$newValueOid] = $associatedId;
|
||||
$this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;
|
||||
$this->entityStates[$newValueOid] = self::STATE_MANAGED;
|
||||
// make sure that when an proxy is then finally loaded, $this->originalEntityData is set also!
|
||||
break;
|
||||
}
|
||||
|
||||
$this->originalEntityData[$oid][$field] = $newValue;
|
||||
$class->reflFields[$field]->setValue($entity, $newValue);
|
||||
|
||||
if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']];
|
||||
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// Inject collection
|
||||
$pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection);
|
||||
$pColl->setOwner($entity, $assoc);
|
||||
$pColl->setInitialized(false);
|
||||
|
||||
$reflField = $class->reflFields[$field];
|
||||
$reflField->setValue($entity, $pColl);
|
||||
|
||||
if ($assoc['fetch'] == ClassMetadata::FETCH_EAGER) {
|
||||
$this->loadCollection($pColl);
|
||||
$pColl->takeSnapshot();
|
||||
}
|
||||
|
||||
$this->originalEntityData[$oid][$field] = $pColl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
|
||||
if ($this->evm->hasListeners(Events::postLoad)) {
|
||||
$this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue