sMetadata $class) { $entity = $class->newInstance(); if ($entity instanceof ObjectManagerAware) { $entity->injectObjectManager($this->em, $class); } return $entity; } public function createEntity($className, array $data, &$hints = []) { $class = $this->em->getClassMetadata($className); $id = $this->identifierFlattener->flattenIdentifier($class, $data); $idHash = implode(' ', $id); if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_id($entity); if (isset($hints[Query::HINT_REFRESH], $hints[Query::HINT_REFRESH_ENTITY])) { $unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]; if ($unmanagedProxy !== $entity && $unmanagedProxy instanceof Proxy && $this->isIdentifierEquals($unmanagedProxy, $entity)) { // We will hydrate the given un-managed proxy anyway: // continue work, but consider it the entity from now on $entity = $unmanagedProxy; } } if ($entity instanceof Proxy && !$entity->__isInitialized()) { $entity->__setInitialized(\true); } else { if (!isset($hints[Query::HINT_REFRESH]) || isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity) { return $entity; } } // 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_id($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->identityMap[$class->rootEntityName][$idHash] = $entity; if (isset($hints[Query::HINT_READ_ONLY])) { $this->readOnlyObjects[$oid] = \true; } } if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } 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])) { Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/issues/8471', 'Partial Objects are deprecated (here entity %s)', $className); return $entity; } foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. if (isset($hints['fetchAlias'], $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_id($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_id($data[$field])])) { $class->reflFields[$field]->setValue($entity, $data[$field]); $this->originalEntityData[$oid][$field] = $data[$field]; break; } $associatedId = []; // TODO: Is this even computed right in all cases of composite keys? foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { $joinColumnValue = $data[$srcColumn] ?? null; if ($joinColumnValue !== null) { if ($joinColumnValue instanceof BackedEnum) { $joinColumnValue = $joinColumnValue->value; } if ($targetClass->containsForeignIdentifier) { $associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue; } else { $associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue; } } elseif ($targetClass->containsForeignIdentifier && in_array($targetClass->getFieldForColumn($targetColumn), $targetClass->identifier, \true)) { // the missing key is part of target's entity primary key $associatedId = []; break; } } if (!$associatedId) { // Foreign key is NULL $class->reflFields[$field]->setValue($entity, null); $this->originalEntityData[$oid][$field] = null; break; } 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: $normalizedAssociatedId = $this->normalizeIdentifier($targetClass, $associatedId); 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'], $normalizedAssociatedId); break; // 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($normalizedAssociatedId); $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $normalizedAssociatedId); break; default: // TODO: This is very imperformant, ignore it? $newValue = $this->em->find($assoc['targetEntity'], $normalizedAssociatedId); break; } if ($newValue === null) { break; } // PERF: Inlined & optimized code from UnitOfWork#registerManaged() $newValueOid = spl_object_id($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 && $newValue !== null) { $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; } } // defer invoking of postLoad event to hydration complete step $this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity); return $entity; } public function triggerEagerLoads() { if (!$this->eagerLoadingEntities) { return; } // avoid infinite recursion $eagerLoadingEntities = $this->eagerLoadingEntities; $this->eagerLoadingEntities = []; foreach ($eagerLoadingEntities as $entityName => $ids) { if (!$ids) { continue; } $class = $this->em->getClassMetadata($entityName); $this->getEntityPersister($entityName)->loadAll(array_combine($class->identifier, [array_values($ids)])); } } public function loadCollection(PersistentCollection $collection) { $assoc = $collection->getMapping(); $persister = $this->getEntityPersister($assoc['targetEntity']); switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); break; case ClassMetadata::MANY_TO_MANY: $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); break; } $collection->setInitialized(\true); } public function getIdentityMap() { return $this->identityMap; } public function getOriginalEntityData($entity) { $oid = spl_object_id($entity); return $this->originalEntityData[$oid] ?? []; } public function setOriginalEntityData($entity, array $data) { $this->originalEntityData[spl_object_id($entity)] = $data; } public function setOriginalEntityProperty($oid, $property, $value) { $this->originalEntityData[$oid][$property] = $value; } public function getEntityIdentifier($entity) { if (!isset($this->entityIdentifiers[spl_object_id($entity)])) { throw EntityNotFoundException::noIdentifierFound(get_debug_type($entity)); } return $this->entityIdentifiers[spl_object_id($entity)]; } public function getSingleIdentifierValue($entity) { $class = $this->em->getClassMetadata(get_class($entity)); if ($class->isIdentifierComposite) { throw ORMInvalidArgumentException::invalidCompositeIdentifier(); } $values = $this->isInIdentityMap($entity) ? $this->getEntityIdentifier($entity) : $class->getIdentifierValues($entity); return $values[$class->identifier[0]] ?? null; } public function tryGetById($id, $rootClassName) { $idHash = implode(' ', (array) $id); return $this->identityMap[$rootClassName][$idHash] ?? \false; } public function scheduleForDirtyCheck($entity) { $rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; $this->scheduledForSynchronization[$rootClassName][spl_object_id($entity)] = $entity; } public function hasPendingInsertions() { return !empty($this->entityInsertions); } public function size() { return array_sum(array_map('count', $this->identityMap)); } public function getEntityPersister($entityName) { if (isset($this->persisters[$entityName])) { return $this->persisters[$entityName]; } $class = $this->em->getClassMetadata($entityName); switch (\true) { case $class->isInheritanceTypeNone(): $persister = new BasicEntityPersister($this->em, $class); break; case $class->isInheritanceTypeSingleTable(): $persister = new SingleTablePersister($this->em, $class); break; case $class->isInheritanceTypeJoined(): $persister = new JoinedSubclassPersister($this->em, $class); break; default: throw new RuntimeException('No persister found for entity.'); } if ($this->hasCache && $class->cache !== null) { $persister = $this->em->getConfiguration()->getSecondLevelCacheConfiguration()->getCacheFactory()->buildCachedEntityPersister($this->em, $persister, $class); } $this->persisters[$entityName] = $persister; return $this->persisters[$entityName]; } public function getCollectionPersister(array $association) { $role = isset($association['cache']) ? $association['sourceEntity'] . '::' . $association['fieldName'] : $association['type']; if (isset($this->collectionPersisters[$role])) { return $this->collectionPersisters[$role]; } $persister = $association['type'] === ClassMetadata::ONE_TO_MANY ? new OneToManyPersister($this->em) : new ManyToManyPersister($this->em); if ($this->hasCache && isset($association['cache'])) { $persister = $this->em->getConfiguration()->getSecondLevelCacheConfiguration()->getCacheFactory()->buildCachedCollectionPersister($this->em, $persister, $association); } $this->collectionPersisters[$role] = $persister; return $this->collectionPersisters[$role]; } public function registerManaged($entity, array $id, array $data) { $oid = spl_object_id($entity); $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; $this->addToIdentityMap($entity); if ($entity instanceof NotifyPropertyChanged && (!$entity instanceof Proxy || $entity->__isInitialized())) { $entity->addPropertyChangedListener($this); } } public function clearEntityChangeSet($oid) { unset($this->entityChangeSets[$oid]); } public function propertyChanged($sender, $propertyName, $oldValue, $newValue) { $oid = spl_object_id($sender); $class = $this->em->getClassMetadata(get_class($sender)); $isAssocField = isset($class->associationMappings[$propertyName]); if (!$isAssocField && !isset($class->fieldMappings[$propertyName])) { return; // ignore non-persistent fields } // Update changeset and mark entity for synchronization $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue]; if (!isset($this->scheduledForSynchronization[$class->rootEntityName][$oid])) { $this->scheduleForDirtyCheck($sender); } } public function getScheduledEntityInsertions() { return $this->entityInsertions; } public function getScheduledEntityUpdates() { return $this->entityUpdates; } public function getScheduledEntityDeletions() { return $this->entityDeletions; } public function getScheduledCollectionDeletions() { return $this->collectionDeletions; } public function getScheduledCollectionUpdates() { return $this->collectionUpdates; } public function initializeObject($obj) { if ($obj instanceof Proxy) { $obj->__load(); return; } if ($obj instanceof PersistentCollection) { $obj->initialize(); } } private static function objToStr($obj) : string { return method_exists($obj, '__toString') ? (string) $obj : get_debug_type($obj) . '@' . spl_object_id($obj); } public function markReadOnly($object) { if (!is_object($object) || !$this->isInIdentityMap($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } $this->readOnlyObjects[spl_object_id($object)] = \true; } public function isReadOnly($object) { if (!is_object($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } return isset($this->readOnlyObjects[spl_object_id($object)]); } private function afterTransactionComplete() : void { $this->performCallbackOnCachedPersister(static function (CachedPersister $persister) { $persister->afterTransactionComplete(); }); } private function afterTransactionRolledBack() : void { $this->performCallbackOnCachedPersister(static function (CachedPersister $persister) { $persister->afterTransactionRolledBack(); }); } private function performCallbackOnCachedPersister(callable $callback) : void { if (!$this->hasCache) { return; } foreach (array_merge($this->persisters, $this->collectionPersisters) as $persister) { if ($persister instanceof CachedPersister) { $callback($persister); } } } private function dispatchOnFlushEvent() : void { if ($this->evm->hasListeners(Events::onFlush)) { $this->evm->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em)); } } private function dispatchPostFlushEvent() : void { if ($this->evm->hasListeners(Events::postFlush)) { $this->evm->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em)); } } private function isIdentifierEquals($entity1, $entity2) : bool { if ($entity1 === $entity2) { return \true; } $class = $this->em->getClassMetadata(get_class($entity1)); if ($class !== $this->em->getClassMetadata(get_class($entity2))) { return \false; } $oid1 = spl_object_id($entity1); $oid2 = spl_object_id($entity2); $id1 = $this->entityIdentifiers[$oid1] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity1)); $id2 = $this->entityIdentifiers[$oid2] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity2)); return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2); } private function assertThatThereAreNoUnintentionallyNonPersistedAssociations() : void { $entitiesNeedingCascadePersist = array_diff_key($this->nonCascadedNewDetectedEntities, $this->entityInsertions); $this->nonCascadedNewDetectedEntities = []; if ($entitiesNeedingCascadePersist) { throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships(array_values($entitiesNeedingCascadePersist)); } } private function mergeEntityStateIntoManagedCopy($entity, $managedCopy) : void { if (!$this->isLoaded($entity)) { return; } if (!$this->isLoaded($managedCopy)) { $managedCopy->__load(); } $class = $this->em->getClassMetadata(get_class($entity)); foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) { $name = $prop->name; $prop->setAccessible(\true); if (!isset($class->associationMappings[$name])) { if (!$class->isIdentifier($name)) { $prop->setValue($managedCopy, $prop->getValue($entity)); } } else { $assoc2 = $class->associationMappings[$name]; if ($assoc2['type'] & ClassMetadata::TO_ONE) { $other = $prop->getValue($entity); if ($other === null) { $prop->setValue($managedCopy, null); } else { if ($other instanceof Proxy && !$other->__isInitialized()) { // do not merge fields marked lazy that have not been fetched. continue; } if (!$assoc2['isCascadeMerge']) { if ($this->getEntityState($other) === self::STATE_DETACHED) { $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $relatedId = $targetClass->getIdentifierValues($other); if ($targetClass->subClasses) { $other = $this->em->find($targetClass->name, $relatedId); } else { $other = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); $this->registerManaged($other, $relatedId, []); } } $prop->setValue($managedCopy, $other); } } } else { $mergeCol = $prop->getValue($entity); if ($mergeCol instanceof PersistentCollection && !$mergeCol->isInitialized()) { // do not merge fields marked lazy that have not been fetched. // keep the lazy persistent collection of the managed copy. continue; } $managedCol = $prop->getValue($managedCopy); if (!$managedCol) { $managedCol = new PersistentCollection($this->em, $this->em->getClassMetadata($assoc2['targetEntity']), new ArrayCollection()); $managedCol->setOwner($managedCopy, $assoc2); $prop->setValue($managedCopy, $managedCol); } if ($assoc2['isCascadeMerge']) { $managedCol->initialize(); // clear and set dirty a managed collection if its not also the same collection to merge from. if (!$managedCol->isEmpty() && $managedCol !== $mergeCol) { $managedCol->unwrap()->clear(); $managedCol->setDirty(\true); if ($assoc2['isOwningSide'] && $assoc2['type'] === ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify()) { $this->scheduleForDirtyCheck($managedCopy); } } } } } if ($class->isChangeTrackingNotify()) { // Just treat all properties as changed, there is no other choice. $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); } } } public function hydrationComplete() { $this->hydrationCompleteHandler->hydrationComplete(); } private function clearIdentityMapForEntityName(string $entityName) : void { if (!isset($this->identityMap[$entityName])) { return; } $visited = []; foreach ($this->identityMap[$entityName] as $entity) { $this->doDetach($entity, $visited, \false); } } private function clearEntityInsertionsForEntityName(string $entityName) : void { foreach ($this->entityInsertions as $hash => $entity) { // note: performance optimization - `instanceof` is much faster than a function call if ($entity instanceof $entityName && get_class($entity) === $entityName) { unset($this->entityInsertions[$hash]); } } } private function convertSingleFieldIdentifierToPHPValue(ClassMetadata $class, $identifierValue) { return $this->em->getConnection()->convertToPHPValue($identifierValue, $class->getTypeOfField($class->getSingleIdentifierFieldName())); } private function normalizeIdentifier(ClassMetadata $targetClass, array $flatIdentifier) : array { $normalizedAssociatedId = []; foreach ($targetClass->getIdentifierFieldNames() as $name) { if (!array_key_exists($name, $flatIdentifier)) { continue; } if (!$targetClass->isSingleValuedAssociation($name)) { $normalizedAssociatedId[$name] = $flatIdentifier[$name]; continue; } $targetIdMetadata = $this->em->getClassMetadata($targetClass->getAssociationTargetClass($name)); // Note: the ORM prevents using an entity with a composite identifier as an identifier association // therefore, reset($targetIdMetadata->identifier) is always correct $normalizedAssociatedId[$name] = $this->em->getReference($targetIdMetadata->getName(), $this->normalizeIdentifier($targetIdMetadata, [(string) reset($targetIdMetadata->identifier) => $flatIdentifier[$name]])); } return $normalizedAssociatedId; } }