nction ($assoc) { return $assoc['isCascadeRefresh']; }); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (\true) { case $relatedEntities instanceof PersistentCollection: // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case $relatedEntities instanceof Collection: case is_array($relatedEntities): foreach ($relatedEntities as $relatedEntity) { $this->doRefresh($relatedEntity, $visited, $lockMode); } break; case $relatedEntities !== null: $this->doRefresh($relatedEntities, $visited, $lockMode); break; default: } } } private function cascadeDetach($entity, array &$visited) : void { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter($class->associationMappings, static function ($assoc) { return $assoc['isCascadeDetach']; }); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (\true) { case $relatedEntities instanceof PersistentCollection: // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // 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: } } } private function cascadeMerge($entity, $managedCopy, array &$visited) : void { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter($class->associationMappings, static function ($assoc) { return $assoc['isCascadeMerge']; }); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); if ($relatedEntities instanceof Collection) { if ($relatedEntities === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) { continue; } if ($relatedEntities instanceof PersistentCollection) { // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); } foreach ($relatedEntities as $relatedEntity) { $this->doMerge($relatedEntity, $visited, $managedCopy, $assoc); } } elseif ($relatedEntities !== null) { $this->doMerge($relatedEntities, $visited, $managedCopy, $assoc); } } } private function cascadePersist($entity, array &$visited) : void { if ($entity instanceof Proxy && !$entity->__isInitialized()) { // nothing to do - proxy is not initialized, therefore we don't do anything with it return; } $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter($class->associationMappings, static function ($assoc) { return $assoc['isCascadePersist']; }); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (\true) { case $relatedEntities instanceof PersistentCollection: // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case $relatedEntities instanceof Collection: case is_array($relatedEntities): if (($assoc['type'] & ClassMetadata::TO_MANY) <= 0) { throw ORMInvalidArgumentException::invalidAssociation($this->em->getClassMetadata($assoc['targetEntity']), $assoc, $relatedEntities); } foreach ($relatedEntities as $relatedEntity) { $this->doPersist($relatedEntity, $visited); } break; case $relatedEntities !== null: if (!$relatedEntities instanceof $assoc['targetEntity']) { throw ORMInvalidArgumentException::invalidAssociation($this->em->getClassMetadata($assoc['targetEntity']), $assoc, $relatedEntities); } $this->doPersist($relatedEntities, $visited); break; default: } } } private function cascadeRemove($entity, array &$visited) : void { $class = $this->em->getClassMetadata(get_class($entity)); $associationMappings = array_filter($class->associationMappings, static function ($assoc) { return $assoc['isCascadeRemove']; }); $entitiesToCascade = []; foreach ($associationMappings as $assoc) { if ($entity instanceof Proxy && !$entity->__isInitialized()) { $entity->__load(); } $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); switch (\true) { case $relatedEntities instanceof Collection: case is_array($relatedEntities): // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { $entitiesToCascade[] = $relatedEntity; } break; case $relatedEntities !== null: $entitiesToCascade[] = $relatedEntities; break; default: } } foreach ($entitiesToCascade as $relatedEntity) { $this->doRemove($relatedEntity, $visited); } } public function lock($entity, int $lockMode, $lockVersion = null) : void { if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); } $class = $this->em->getClassMetadata(get_class($entity)); switch (\true) { case $lockMode === LockMode::OPTIMISTIC: if (!$class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } if ($lockVersion === null) { return; } if ($entity instanceof Proxy && !$entity->__isInitialized()) { $entity->__load(); } assert($class->versionField !== null); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator if ($entityVersion != $lockVersion) { throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion); } break; case $lockMode === LockMode::NONE: case $lockMode === LockMode::PESSIMISTIC_READ: case $lockMode === LockMode::PESSIMISTIC_WRITE: if (!$this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } $oid = spl_object_id($entity); $this->getEntityPersister($class->name)->lock(array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]), $lockMode); break; default: } } public function getCommitOrderCalculator() { return new Internal\CommitOrderCalculator(); } public function clear($entityName = null) { if ($entityName === null) { $this->identityMap = $this->entityIdentifiers = $this->originalEntityData = $this->entityChangeSets = $this->entityStates = $this->scheduledForSynchronization = $this->entityInsertions = $this->entityUpdates = $this->entityDeletions = $this->nonCascadedNewDetectedEntities = $this->collectionDeletions = $this->collectionUpdates = $this->extraUpdates = $this->readOnlyObjects = $this->visitedCollections = $this->eagerLoadingEntities = $this->orphanRemovals = []; } else { Deprecation::triggerIfCalledFromOutside('doctrine/orm', 'https://github.com/doctrine/orm/issues/8460', 'Calling %s() with any arguments to clear specific entities is deprecated and will not be supported in Doctrine ORM 3.0.', __METHOD__); $this->clearIdentityMapForEntityName($entityName); $this->clearEntityInsertionsForEntityName($entityName); } if ($this->evm->hasListeners(Events::onClear)) { $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName)); } } public function scheduleOrphanRemoval($entity) { $this->orphanRemovals[spl_object_id($entity)] = $entity; } public function cancelOrphanRemoval($entity) { unset($this->orphanRemovals[spl_object_id($entity)]); } public function scheduleCollectionDeletion(PersistentCollection $coll) { $coid = spl_object_id($coll); // TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? unset($this->collectionUpdates[$coid]); $this->collectionDeletions[$coid] = $coll; } public function isCollectionScheduledForDeletion(PersistentCollection $coll) { return isset($this->collectionDeletions[spl_object_id($coll)]); } private function newInstance(ClassMetadata $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; } }