/** * This method should me called after any hydration cycle completed. * * Method fires all deferred invocations of postLoad events */ public function hydrationComplete() { $toInvoke = $this->deferredPostLoadInvocations; $this->deferredPostLoadInvocations = array(); foreach ($toInvoke as $classAndEntity) { list($class, $invoke, $entity) = $classAndEntity; $this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } }
protected function hydrateRowData(array $sqlResult, array &$cache, array &$result) { parent::hydrateRowData($sqlResult, $cache, $result); if (empty($this->aliasInvokers)) { return; } $invokers = reset($this->aliasInvokers); $dqlAlias = key($this->aliasInvokers); $class = $this->aliasMetadata[$dqlAlias]; $entity = end($result); $this->lifecycleEventsInvoker->invoke($class, Events::postLoadRelations, $entity, new LifecycleEventArgs($entity, $this->_em), $invokers); }
public function testSkipsDeferredPostLoadOfMetadataWithNoInvokedListeners() { /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ $metadata = $this->getMock('Doctrine\\ORM\\Mapping\\ClassMetadata', array(), array(), '', false); $entity = new stdClass(); $this->listenersInvoker->expects($this->any())->method('getSubscribedSystems')->with($metadata)->will($this->returnValue(ListenersInvoker::INVOKE_NONE)); $this->handler->deferPostLoadInvoking($metadata, $entity); $this->listenersInvoker->expects($this->never())->method('invoke'); $this->handler->hydrationComplete(); }
protected function hydrateRowData(array $row, array &$cache, array &$result) { parent::hydrateRowData($row, $cache, $result); $identityMap = $this->uow->getIdentityMap(); foreach ($this->aliasInvokers as $dqlAlias => $invokers) { $class = $this->aliasMetadata[$dqlAlias]; $id = array(); foreach ($class->identifier as $idProperty) { if (!isset($this->lastRowData[$dqlAlias][$idProperty])) { if (!isset($class->associationMappings[$idProperty])) { continue 2; } elseif (!isset($this->lastRowData[$dqlAlias][$idProperty = $class->getSingleAssociationJoinColumnName($idProperty)])) { continue 2; } } $id[$idProperty] = $this->lastRowData[$dqlAlias][$idProperty]; } if (!isset($identityMap[$class->rootEntityName][$idHash = implode(' ', (array) $id)])) { continue; } $entity = $identityMap[$class->rootEntityName][$idHash]; $this->lifecycleEventsInvoker->invoke($class, Events::postLoadRelations, $entity, new LifecycleEventArgs($entity, $this->_em), $invokers); } }
/** * INTERNAL: * Creates an entity. Used for reconstitution of persistent entities. * * @ignore * * @param string $className The name of the entity class. * @param array $data The data for the entity. * @param array $hints Any hints to account for during reconstitution/lookup of the entity. * * @return object The managed entity instance. * * @internal Highly performance-sensitive method. * * @todo Rename: getOrCreateEntity */ public function createEntity($className, array $data, &$hints = array()) { $class = $this->em->getClassMetadata($className); //$isReadOnly = isset($hints[Query::HINT_READ_ONLY]); if ($class->isIdentifierComposite) { $id = array(); foreach ($class->identifier as $fieldName) { $id[$fieldName] = isset($class->associationMappings[$fieldName]) ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] : $data[$fieldName]; } } else { $id = isset($class->associationMappings[$class->identifier[0]]) ? $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']] : $data[$class->identifier[0]]; $id = array($class->identifier[0] => $id); } $idHash = implode(' ', $id); if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); if (isset($hints[Query::HINT_REFRESH]) && isset($hints[Query::HINT_REFRESH_ENTITY]) && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity && $unmanagedProxy instanceof Proxy && $this->isIdentifierEquals($unmanagedProxy, $entity)) { // DDC-1238 - we have a managed instance, but it isn't the provided one. // Therefore we clear its identifier. Also, we must re-fetch metadata since the // refreshed object may be anything foreach ($class->identifier as $fieldName) { $class->reflFields[$fieldName]->setValue($unmanagedProxy, null); } return $unmanagedProxy; } if ($entity instanceof Proxy && !$entity->__isInitialized()) { $entity->__setInitialized(true); $overrideLocalValues = true; if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } } else { $overrideLocalValues = isset($hints[Query::HINT_REFRESH]); // If only a specific entity is set to refresh, check that it's the one if (isset($hints[Query::HINT_REFRESH_ENTITY])) { $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity; } } if ($overrideLocalValues) { // inject ObjectManager upon refresh. if ($entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } $this->originalEntityData[$oid] = $data; } } 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) { 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; } $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); switch (true) { case $assoc['type'] & ClassMetadata::TO_ONE: if (!$assoc['isOwningSide']) { // use the given entity association if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) { $this->originalEntityData[$oid][$field] = $data[$field]; $class->reflFields[$field]->setValue($entity, $data[$field]); $targetClass->reflFields[$assoc['mappedBy']]->setValue($data[$field], $entity); continue 2; } // Inverse side of x-to-one can never be lazy $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity)); continue 2; } // use the entity association if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) { $class->reflFields[$field]->setValue($entity, $data[$field]); $this->originalEntityData[$oid][$field] = $data[$field]; continue; } $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; continue; } 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 can append this entity for eager loading! if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER && isset($hints[self::HINT_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 // Deferred eager load only works for single identifier classes case isset($hints[self::HINT_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; if ($newValue instanceof NotifyPropertyChanged && (!$newValue instanceof Proxy || $newValue->__isInitialized())) { $newValue->addPropertyChangedListener($this); } $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: // Ignore if its a cached collection if (isset($hints[Query::HINT_CACHE_ENABLED]) && $class->getFieldValue($entity, $field) instanceof PersistentCollection) { break; } // use the given collection if (isset($data[$field]) && $data[$field] instanceof PersistentCollection) { $data[$field]->setOwner($entity, $assoc); $class->reflFields[$field]->setValue($entity, $data[$field]); $this->originalEntityData[$oid][$field] = $data[$field]; break; } // 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; } } if ($overrideLocalValues) { $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } } return $entity; }
/** * Deletes an entity as part of the current unit of work. * * This method is internally called during delete() cascades as it tracks * the already visited entities to prevent infinite recursions. * * @param object $entity The entity to delete. * @param array $visited The map of the already visited entities. * * @return void * * @throws ORMInvalidArgumentException If the instance is a detached entity. * @throws UnexpectedValueException */ private function doRemove($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited // Cascade first, because scheduleForDelete() removes the entity from the identity map, which // can cause problems when a lazy proxy has to be initialized for the cascade operation. $this->cascadeRemove($entity, $visited); $class = $this->em->getClassMetadata(get_class($entity)); $entityState = $this->getEntityState($entity); switch ($entityState) { case self::STATE_NEW: case self::STATE_REMOVED: // nothing to do break; case self::STATE_MANAGED: $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove); if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } $this->scheduleForDelete($entity); break; case self::STATE_DETACHED: throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed"); default: throw new UnexpectedValueException("Unexpected entity state: {$entityState}." . self::objToStr($entity)); } }
/** * Executes all entity deletions for entities of the specified type. * * @param \Doctrine\ORM\Mapping\ClassMetadata $class * * @return void */ private function executeDeletions($class) { $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove); foreach ($this->entityDeletions as $oid => $entity) { if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $persister->delete($entity); unset($this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->originalEntityData[$oid], $this->entityStates[$oid]); // Entity with this $oid after deletion treated as NEW, even if the $oid // is obtained by a new entity because the old one went out of scope. //$this->entityStates[$oid] = self::STATE_NEW; if (!$class->isIdentifierNatural()) { $class->reflFields[$class->identifier[0]]->setValue($entity, null); } if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } } }