From ade5559ed591db95d84327f25e1cdd1c41a3ff78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Thu, 20 Apr 2023 00:02:50 +0200 Subject: [PATCH] Migrate basic entity persister to the object API of association mapping --- .../ORM/Mapping/AssociationMapping.php | 6 + lib/Doctrine/ORM/Mapping/ClassMetadata.php | 2 +- .../Entity/BasicEntityPersister.php | 226 ++++++++++-------- lib/Doctrine/ORM/Tools/SchemaValidator.php | 4 +- lib/Doctrine/ORM/Utility/PersisterHelper.php | 2 +- phpstan.neon | 18 +- psalm-baseline.xml | 4 +- 7 files changed, 149 insertions(+), 113 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index bd6a1b77ace..af0a48e993f 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -179,6 +179,12 @@ final public function isToMany(): bool return $this instanceof ToManyAssociationMapping; } + /** @psalm-assert-if-true OneToOneOwningSideMapping $this */ + final public function isOneToOneOwningSide(): bool + { + return $this->isOneToOne() && $this->isOwningSide(); + } + /** @psalm-assert-if-true OneToOneOwningSideMapping|ManyToOneAssociationMapping $this */ final public function isToOneOwningSide(): bool { diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index ca679e907e7..435c5f66a77 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -438,7 +438,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable * ) * * - * @psalm-var array + * @psalm-var array */ public array $associationMappings = []; diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index 56efbe8a4c1..02d9a6c481c 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -19,7 +19,9 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\JoinColumnMapping; use Doctrine\ORM\Mapping\MappingException; +use Doctrine\ORM\Mapping\OneToManyAssociationMapping; use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\PersistentCollection; @@ -434,14 +436,16 @@ final protected function updateTable( continue; } + assert($this->class->associationMappings[$idField]->isToOneOwningSide()); + $params[] = $identifier[$idField]; $where[] = $this->quoteStrategy->getJoinColumnName( - $this->class->associationMappings[$idField]['joinColumns'][0], + $this->class->associationMappings[$idField]->joinColumns[0], $this->class, $this->platform, ); - $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']); + $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]->targetEntity); $targetType = PersisterHelper::getTypeOfField($targetMapping->identifier[0], $targetMapping, $this->em); if ($targetType === []) { @@ -494,32 +498,35 @@ final protected function updateTable( protected function deleteJoinTableRecords(array $identifier, array $types): void { foreach ($this->class->associationMappings as $mapping) { - if ($mapping['type'] !== ClassMetadata::MANY_TO_MANY) { + if (! $mapping->isManyToMany()) { continue; } // @Todo this only covers scenarios with no inheritance or of the same level. Is there something // like self-referential relationship between different levels of an inheritance hierarchy? I hope not! - $selfReferential = ($mapping['targetEntity'] === $mapping['sourceEntity']); + $selfReferential = ($mapping->targetEntity === $mapping->sourceEntity); $class = $this->class; $association = $mapping; $otherColumns = []; $otherKeys = []; $keys = []; - if (! $mapping['isOwningSide']) { - $class = $this->em->getClassMetadata($mapping['targetEntity']); - $association = $class->associationMappings[$mapping['mappedBy']]; + if (! $mapping->isOwningSide()) { + assert(isset($mapping->mappedBy)); + $class = $this->em->getClassMetadata($mapping->targetEntity); + $association = $class->associationMappings[$mapping->mappedBy]; } - $joinColumns = $mapping['isOwningSide'] - ? $association['joinTable']['joinColumns'] - : $association['joinTable']['inverseJoinColumns']; + assert($association->isManyToManyOwningSide()); + + $joinColumns = $mapping->isOwningSide() + ? $association->joinTable['joinColumns'] + : $association->joinTable['inverseJoinColumns']; if ($selfReferential) { - $otherColumns = ! $mapping['isOwningSide'] - ? $association['joinTable']['joinColumns'] - : $association['joinTable']['inverseJoinColumns']; + $otherColumns = ! $mapping->isOwningSide() + ? $association->joinTable['joinColumns'] + : $association->joinTable['inverseJoinColumns']; } foreach ($joinColumns as $joinColumn) { @@ -530,7 +537,7 @@ protected function deleteJoinTableRecords(array $identifier, array $types): void $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } - if (isset($mapping['isOnDeleteCascade'])) { + if (isset($mapping->isOnDeleteCascade)) { continue; } @@ -648,10 +655,10 @@ protected function prepareUpdateData(object $entity, bool $isInsert = false): ar $newValId = $uow->getEntityIdentifier($newVal); } - $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); $owningTable = $this->getOwningTable($field); - foreach ($assoc['joinColumns'] as $joinColumn) { + foreach ($assoc->joinColumns as $joinColumn) { $sourceColumn = $joinColumn['name']; $targetColumn = $joinColumn['referencedColumnName']; $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); @@ -732,41 +739,43 @@ public function loadById(array $identifier, object|null $entity = null): object| */ public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null { - $foundEntity = $this->em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity']); + $foundEntity = $this->em->getUnitOfWork()->tryGetById($identifier, $assoc->targetEntity); if ($foundEntity !== false) { return $foundEntity; } - $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); - if ($assoc['isOwningSide']) { - $isInverseSingleValued = $assoc['inversedBy'] && ! $targetClass->isCollectionValuedAssociation($assoc['inversedBy']); + if ($assoc->isOwningSide()) { + $isInverseSingleValued = $assoc->inversedBy && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy); // Mark inverse side as fetched in the hints, otherwise the UoW would // try to load it in a separate query (remember: to-one inverse sides can not be lazy). $hints = []; if ($isInverseSingleValued) { - $hints['fetched']['r'][$assoc['inversedBy']] = true; + $hints['fetched']['r'][$assoc->inversedBy] = true; } $targetEntity = $this->load($identifier, null, $assoc, $hints); // Complete bidirectional association, if necessary if ($targetEntity !== null && $isInverseSingleValued) { - $targetClass->reflFields[$assoc['inversedBy']]->setValue($targetEntity, $sourceEntity); + $targetClass->reflFields[$assoc->inversedBy]->setValue($targetEntity, $sourceEntity); } return $targetEntity; } - $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); - $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); + assert(isset($assoc->mappedBy)); + $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); + $owningAssoc = $targetClass->getAssociationMapping($assoc->mappedBy); + assert($owningAssoc->isOneToOneOwningSide()); $computedIdentifier = []; // TRICKY: since the association is specular source and target are flipped - foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { if (! isset($sourceClass->fieldNames[$sourceKeyColumn])) { throw MappingException::joinColumnMustPointToMappedField( $sourceClass->name, @@ -781,7 +790,7 @@ public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEnti $targetEntity = $this->load($computedIdentifier, null, $assoc); if ($targetEntity !== null) { - $targetClass->setFieldValue($targetEntity, $assoc['mappedBy'], $sourceEntity); + $targetClass->setFieldValue($targetEntity, $assoc->mappedBy, $sourceEntity); } return $targetEntity; @@ -906,9 +915,9 @@ private function loadArrayFromResult(AssociationMapping $assoc, Result $stmt): a $rsm = $this->currentPersisterContext->rsm; $hints = [UnitOfWork::HINT_DEFEREAGERLOAD => true]; - if (isset($assoc['indexBy'])) { + if (isset($assoc->indexBy)) { $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. - $rsm->addIndexBy('r', $assoc['indexBy']); + $rsm->addIndexBy('r', $assoc->indexBy); } return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); @@ -930,9 +939,9 @@ private function loadCollectionFromStatement( 'collection' => $coll, ]; - if (isset($assoc['indexBy'])) { + if (isset($assoc->indexBy)) { $rsm = clone $this->currentPersisterContext->rsm; // this is necessary because the "default rsm" should be changed. - $rsm->addIndexBy('r', $assoc['indexBy']); + $rsm->addIndexBy('r', $assoc->indexBy); } return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); @@ -957,20 +966,23 @@ private function getManyToManyStatement( ): Result { $this->switchPersisterContext($offset, $limit); - $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); + $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); $class = $sourceClass; $association = $assoc; $criteria = []; $parameters = []; - if (! $assoc['isOwningSide']) { - $class = $this->em->getClassMetadata($assoc['targetEntity']); - $association = $class->associationMappings[$assoc['mappedBy']]; + if (! $assoc->isOwningSide()) { + assert(isset($assoc->mappedBy)); + $class = $this->em->getClassMetadata($assoc->targetEntity); + $association = $class->associationMappings[$assoc->mappedBy]; } - $joinColumns = $assoc['isOwningSide'] - ? $association['joinTable']['joinColumns'] - : $association['joinTable']['inverseJoinColumns']; + assert($association->isManyToManyOwningSide()); + + $joinColumns = $assoc->isOwningSide() + ? $association->joinTable['joinColumns'] + : $association->joinTable['inverseJoinColumns']; $quotedJoinTable = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); @@ -985,7 +997,7 @@ private function getManyToManyStatement( if (isset($sourceClass->associationMappings[$field])) { $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); - $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; + $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]->targetEntity)->identifier[0]]; } break; @@ -1034,8 +1046,8 @@ public function getSelectSQL( $joinSql = $this->getSelectManyToManyJoinSQL($assoc); } - if (isset($assoc['orderBy'])) { - $orderBy = $assoc['orderBy']; + if ($assoc !== null && isset($assoc->orderBy)) { + $orderBy = $assoc->orderBy; } if ($orderBy) { @@ -1131,15 +1143,18 @@ final protected function getOrderBySQL(array $orderBy, string $baseTableAlias): } if (isset($this->class->associationMappings[$fieldName])) { - if (! $this->class->associationMappings[$fieldName]['isOwningSide']) { + $association = $this->class->associationMappings[$fieldName]; + if (! $association->isOwningSide()) { throw InvalidFindByCall::fromInverseSideUsage($this->class->name, $fieldName); } - $tableAlias = isset($this->class->associationMappings[$fieldName]['inherited']) - ? $this->getSQLTableAlias($this->class->associationMappings[$fieldName]['inherited']) + assert($association->isToOneOwningSide()); + + $tableAlias = isset($association->inherited) + ? $this->getSQLTableAlias($association->inherited) : $baseTableAlias; - foreach ($this->class->associationMappings[$fieldName]['joinColumns'] as $joinColumn) { + foreach ($association->joinColumns as $joinColumn) { $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; } @@ -1186,8 +1201,8 @@ protected function getSelectColumnsSQL(): string $columnList[] = $assocColumnSQL; } - $isAssocToOneInverseSide = $assoc->isToOne() && ! $assoc['isOwningSide']; - $isAssocFromOneEager = ! $assoc->isManyToMany() && $assoc['fetch'] === ClassMetadata::FETCH_EAGER; + $isAssocToOneInverseSide = $assoc->isToOne() && ! $assoc->isOwningSide(); + $isAssocFromOneEager = ! $assoc->isManyToMany() && $assoc->fetch === ClassMetadata::FETCH_EAGER; if (! ($isAssocFromOneEager || $isAssocToOneInverseSide)) { continue; @@ -1197,14 +1212,14 @@ protected function getSelectColumnsSQL(): string continue; } - $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); + $eagerEntity = $this->em->getClassMetadata($assoc->targetEntity); if ($eagerEntity->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) { continue; // now this is why you shouldn't use inheritance } $assocAlias = 'e' . ($eagerAliasCounter++); - $this->currentPersisterContext->rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); + $this->currentPersisterContext->rsm->addJoinedEntityResult($assoc->targetEntity, $assocAlias, 'r', $assocField); foreach ($eagerEntity->fieldNames as $field) { $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias); @@ -1226,26 +1241,29 @@ protected function getSelectColumnsSQL(): string $association = $assoc; $joinCondition = []; - if (isset($assoc['indexBy'])) { - $this->currentPersisterContext->rsm->addIndexBy($assocAlias, $assoc['indexBy']); + if (isset($assoc->indexBy)) { + $this->currentPersisterContext->rsm->addIndexBy($assocAlias, $assoc->indexBy); } - if (! $assoc['isOwningSide']) { - $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); - $association = $eagerEntity->getAssociationMapping($assoc['mappedBy']); + if (! $assoc->isOwningSide()) { + assert(isset($assoc->mappedBy)); + $eagerEntity = $this->em->getClassMetadata($assoc->targetEntity); + $association = $eagerEntity->getAssociationMapping($assoc->mappedBy); } + assert($association->isToOneOwningSide()); + $joinTableAlias = $this->getSQLTableAlias($eagerEntity->name, $assocAlias); $joinTableName = $this->quoteStrategy->getTableName($eagerEntity, $this->platform); - if ($assoc['isOwningSide']) { - $tableAlias = $this->getSQLTableAlias($association['targetEntity'], $assocAlias); - $this->currentPersisterContext->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association['joinColumns']); + if ($assoc->isOwningSide()) { + $tableAlias = $this->getSQLTableAlias($association->targetEntity, $assocAlias); + $this->currentPersisterContext->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association->joinColumns); - foreach ($association['joinColumns'] as $joinColumn) { + foreach ($association->joinColumns as $joinColumn) { $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); - $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity']) + $joinCondition[] = $this->getSQLTableAlias($association->sourceEntity) . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol; } @@ -1257,12 +1275,12 @@ protected function getSelectColumnsSQL(): string } else { $this->currentPersisterContext->selectJoinSql .= ' LEFT JOIN'; - foreach ($association['joinColumns'] as $joinColumn) { + foreach ($association->joinColumns as $joinColumn) { $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); - $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' - . $this->getSQLTableAlias($association['targetEntity']) . '.' . $targetCol; + $joinCondition[] = $this->getSQLTableAlias($association->sourceEntity, $assocAlias) . '.' . $sourceCol . ' = ' + . $this->getSQLTableAlias($association->targetEntity) . '.' . $targetCol; } } @@ -1287,11 +1305,11 @@ protected function getSelectColumnAssociationSQL( } $columnList = []; - $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - $isIdentifier = isset($assoc['id']) && $assoc['id'] === true; + $targetClass = $this->em->getClassMetadata($assoc->targetEntity); + $isIdentifier = isset($assoc->id) && $assoc->id === true; $sqlTableAlias = $this->getSQLTableAlias($class->name, ($alias === 'r' ? '' : $alias)); - foreach ($assoc['joinColumns'] as $joinColumn) { + foreach ($assoc->joinColumns as $joinColumn) { $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); $resultColumnName = $this->getSQLColumnAlias($joinColumn['name']); $type = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); @@ -1314,15 +1332,18 @@ protected function getSelectManyToManyJoinSQL(AssociationMapping $manyToMany): s $association = $manyToMany; $sourceTableAlias = $this->getSQLTableAlias($this->class->name); - if (! $manyToMany['isOwningSide']) { - $targetEntity = $this->em->getClassMetadata($manyToMany['targetEntity']); - $association = $targetEntity->associationMappings[$manyToMany['mappedBy']]; + if (! $manyToMany->isOwningSide()) { + assert(isset($manyToMany->mappedBy)); + $targetEntity = $this->em->getClassMetadata($manyToMany->targetEntity); + $association = $targetEntity->associationMappings[$manyToMany->mappedBy]; } + assert($association->isManyToManyOwningSide()); + $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); - $joinColumns = $manyToMany['isOwningSide'] - ? $association['joinTable']['inverseJoinColumns'] - : $association['joinTable']['joinColumns']; + $joinColumns = $manyToMany->isOwningSide() + ? $association->joinTable['inverseJoinColumns'] + : $association->joinTable['joinColumns']; foreach ($joinColumns as $joinColumn) { $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); @@ -1400,7 +1421,7 @@ protected function getInsertColumnList(): array $assoc = $this->class->associationMappings[$name]; if ($assoc->isToOneOwningSide()) { - foreach ($assoc['joinColumns'] as $joinColumn) { + foreach ($assoc->joinColumns as $joinColumn) { $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } } @@ -1620,30 +1641,34 @@ private function getSelectConditionStatementColumnSQL( $class = $this->class; if ($association->isManyToMany()) { - if (! $association['isOwningSide']) { + assert($assoc !== null); + if (! $association->isOwningSide()) { $association = $assoc; } + assert($association->isManyToManyOwningSide()); + $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); - assert($assoc !== null); - $joinColumns = $assoc['isOwningSide'] - ? $association['joinTable']['joinColumns'] - : $association['joinTable']['inverseJoinColumns']; + $joinColumns = $assoc->isOwningSide() + ? $association->joinTable['joinColumns'] + : $association->joinTable['inverseJoinColumns']; foreach ($joinColumns as $joinColumn) { $columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } } else { - if (! $association['isOwningSide']) { + if (! $association->isOwningSide()) { throw InvalidFindByCall::fromInverseSideUsage( $this->class->name, $field, ); } - $className = $association['inherited'] ?? $this->class->name; + assert($association->isToOneOwningSide()); + + $className = $association->inherited ?? $this->class->name; - foreach ($association['joinColumns'] as $joinColumn) { + foreach ($association->joinColumns as $joinColumn) { $columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); } } @@ -1691,6 +1716,7 @@ public function getOneToManyCollection( int|null $offset = null, int|null $limit = null, ): array { + assert($assoc instanceof OneToManyAssociationMapping); $this->switchPersisterContext($offset, $limit); $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); @@ -1703,6 +1729,7 @@ public function loadOneToManyCollection( object $sourceEntity, PersistentCollection $collection, ): mixed { + assert($assoc instanceof OneToManyAssociationMapping); $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $collection); @@ -1710,27 +1737,29 @@ public function loadOneToManyCollection( /** Builds criteria and execute SQL statement to fetch the one to many entities from. */ private function getOneToManyStatement( - AssociationMapping $assoc, + OneToManyAssociationMapping $assoc, object $sourceEntity, int|null $offset = null, int|null $limit = null, ): Result { $this->switchPersisterContext($offset, $limit); - $criteria = []; - $parameters = []; - $owningAssoc = $this->class->associationMappings[$assoc['mappedBy']]; - $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); - $tableAlias = $this->getSQLTableAlias($owningAssoc['inherited'] ?? $this->class->name); + $criteria = []; + $parameters = []; + assert(isset($assoc->mappedBy)); + $owningAssoc = $this->class->associationMappings[$assoc->mappedBy]; + $sourceClass = $this->em->getClassMetadata($assoc->sourceEntity); + $tableAlias = $this->getSQLTableAlias($owningAssoc->inherited ?? $this->class->name); + assert($owningAssoc->isManyToOne()); - foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { + foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) { if ($sourceClass->containsForeignIdentifier) { $field = $sourceClass->getFieldForColumn($sourceKeyColumn); $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); if (isset($sourceClass->associationMappings[$field])) { $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); - $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; + $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]->targetEntity)->identifier[0]]; } $criteria[$tableAlias . '.' . $targetKeyColumn] = $value; @@ -1828,16 +1857,22 @@ private function getTypes(string $field, mixed $value, ClassMetadata $class): ar case isset($class->associationMappings[$field]): $assoc = $class->associationMappings[$field]; - $class = $this->em->getClassMetadata($assoc['targetEntity']); + $class = $this->em->getClassMetadata($assoc->targetEntity); - if (! $assoc['isOwningSide']) { - $assoc = $class->associationMappings[$assoc['mappedBy']]; - $class = $this->em->getClassMetadata($assoc['targetEntity']); + if (! $assoc->isOwningSide()) { + assert(isset($assoc->mappedBy)); + $assoc = $class->associationMappings[$assoc->mappedBy]; + $class = $this->em->getClassMetadata($assoc->targetEntity); } - $columns = $assoc->isManyToMany() - ? $assoc['relationToTargetKeyColumns'] - : $assoc['sourceToTargetKeyColumns']; + assert($assoc->isOwningSide()); + + if ($assoc->isManyToManyOwningSide()) { + $columns = $assoc->relationToTargetKeyColumns; + } else { + assert($assoc->isToOneOwningSide() || $assoc->isManyToOne()); + $columns = $assoc->sourceToTargetKeyColumns; + } foreach ($columns as $column) { $types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em); @@ -1962,8 +1997,7 @@ public function exists(object $entity, Criteria|null $extraConditions = null): b /** * Generates the appropriate join SQL for the given join column. * - * @param array[] $joinColumns The join columns definition of an association. - * @psalm-param array> $joinColumns + * @param list $joinColumns The join columns definition of an association. * * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. */ diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index def56a47d83..9222f9ba8a4 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -162,7 +162,7 @@ public function validateClass(ClassMetadata $class): array } if ($assoc->isOwningSide()) { - if ($assoc->isManyToMany()) { + if ($assoc->isManyToManyOwningSide()) { $identifierColumns = $class->getIdentifierColumnNames(); foreach ($assoc['joinTable']['joinColumns'] as $joinColumn) { if (! in_array($joinColumn['referencedColumnName'], $identifierColumns, true)) { @@ -194,7 +194,7 @@ public function validateClass(ClassMetadata $class): array "however '" . implode(', ', array_diff($class->getIdentifierColumnNames(), array_values($assoc['relationToSourceKeyColumns']))) . "' are missing."; } - } elseif ($assoc->isToOne()) { + } elseif ($assoc->isToOneOwningSide()) { $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinColumns'] as $joinColumn) { if (! in_array($joinColumn['referencedColumnName'], $identifierColumns, true)) { diff --git a/lib/Doctrine/ORM/Utility/PersisterHelper.php b/lib/Doctrine/ORM/Utility/PersisterHelper.php index 8f061fe99c2..fc7fe9b0702 100644 --- a/lib/Doctrine/ORM/Utility/PersisterHelper.php +++ b/lib/Doctrine/ORM/Utility/PersisterHelper.php @@ -40,7 +40,7 @@ public static function getTypeOfField(string $fieldName, ClassMetadata $class, E return self::getTypeOfField($assoc['mappedBy'], $em->getClassMetadata($assoc['targetEntity']), $em); } - if ($assoc->isManyToMany()) { + if ($assoc->isManyToManyOwningSide()) { $joinData = $assoc['joinTable']; } else { $joinData = $assoc; diff --git a/phpstan.neon b/phpstan.neon index 834c32118b7..d3bd7dee0cf 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -30,17 +30,15 @@ parameters: path: lib/Doctrine/ORM/Mapping/ToOneAssociationMapping.php - - message: "#^Access to an undefined property .*Mapping\\:\\:\\$(joinTableColumns|relationTo(Target|Source)KeyColumns|joinTable|indexBy)\\.$#" - path: lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php + message: "#^Access to an undefined property .*Mapping\\:\\:\\$(joinColumns|joinTableColumns|(relation|source|target)To(Target|Source)KeyColumns|joinTable|indexBy)\\.$#" + paths: + - lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php + - lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php + - lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php + - lib/Doctrine/ORM/Query/AST/Functions/IdentityFunction.php + - lib/Doctrine/ORM/Query/SqlWalker.php + - lib/Doctrine/ORM/UnitOfWork.php - message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ToOneAssociationMapping\\:\\:fromMappingArrayAndName\\(\\) should return Doctrine\\\\ORM\\\\Mapping\\\\ManyToOneAssociationMapping\\|Doctrine\\\\ORM\\\\Mapping\\\\OneToOneAssociationMapping but returns static\\(Doctrine\\\\ORM\\\\Mapping\\\\ToOneAssociationMapping\\)\\.$#" path: lib/Doctrine/ORM/Mapping/ToOneAssociationMapping.php - - - - message: "#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata\\\\:\\:\\$associationMappings \\(array\\\\) does not accept array\\\\.$#" - path: lib/Doctrine/ORM/Mapping/ClassMetadata.php - - - - message: "#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata\\\\:\\:\\$associationMappings \\(array\\\\) does not accept array\\\\.$#" - path: lib/Doctrine/ORM/Mapping/ClassMetadata.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index bd73a88be25..d69ad4504e0 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -737,9 +737,7 @@ array ]]> - - $association - + getValue getValue