/**
  * 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);
     }
 }
示例#5
0
 /**
  * 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;
 }
示例#6
0
 /**
  * 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));
     }
 }
示例#7
0
 /**
  * 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);
         }
     }
 }