diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0b333e3..f6058a9 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -29,6 +29,20 @@ parameters: identifier: 'identical.alwaysFalse' reportUnmatched: false path: 'src/EntityPreloader.php' + - + message: '#internal interface Doctrine\\ORM\\Mapping\\PropertyAccessors\\PropertyAccessor#' # internal, although returned from public ClassMetadata::getPropertyAccessor + reportUnmatched: false # only new Doctrine + path: 'src/EntityPreloader.php' + - + message: '#Doctrine\\ORM\\Mapping\\PropertyAccessors\\PropertyAccessor#' + reportUnmatched: false # only old Doctrine + identifier: class.notFound + path: 'src/EntityPreloader.php' + - + message: '#Call to function method_exists#' + reportUnmatched: false # only new Doctrine + identifier: function.alreadyNarrowedType + path: 'src/EntityPreloader.php' - message: '#Result of \|\| is always false#' identifier: 'booleanOr.alwaysFalse' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index dd725e3..6fe7de4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,15 +13,21 @@ displayDetailsOnTestsThatTriggerErrors="true" displayDetailsOnTestsThatTriggerWarnings="true" displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerDeprecations="true" cacheDirectory="cache/phpunit" > + + + + + tests - + src diff --git a/src/EntityPreloader.php b/src/EntityPreloader.php index 0d7143e..e010de6 100644 --- a/src/EntityPreloader.php +++ b/src/EntityPreloader.php @@ -5,6 +5,7 @@ use ArrayAccess; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\PropertyAccessors\PropertyAccessor; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\QueryBuilder; use LogicException; @@ -14,6 +15,7 @@ use function count; use function get_parent_class; use function is_a; +use function method_exists; /** * @template E of object @@ -120,10 +122,10 @@ private function loadProxies( int $maxFetchJoinSameFieldCount, ): array { - $identifierReflection = $classMetadata->getSingleIdReflectionProperty(); // e.g. Order::$id reflection + $identifierAccessor = $this->getSingleIdPropertyAccessor($classMetadata); // e.g. Order::$id reflection $identifierName = $classMetadata->getSingleIdentifierFieldName(); // e.g. 'id' - if ($identifierReflection === null) { + if ($identifierAccessor === null) { throw new LogicException('Doctrine should use RuntimeReflectionService which never returns null.'); } @@ -131,7 +133,7 @@ private function loadProxies( $uninitializedIds = []; foreach ($entities as $entity) { - $entityId = $identifierReflection->getValue($entity); + $entityId = $identifierAccessor->getValue($entity); $entityKey = (string) $entityId; $uniqueEntities[$entityKey] = $entity; @@ -167,11 +169,11 @@ private function preloadToMany( int $maxFetchJoinSameFieldCount, ): array { - $sourceIdentifierReflection = $sourceClassMetadata->getSingleIdReflectionProperty(); // e.g. Order::$id reflection - $sourcePropertyReflection = $sourceClassMetadata->getReflectionProperty($sourcePropertyName); // e.g. Order::$items reflection - $targetIdentifierReflection = $targetClassMetadata->getSingleIdReflectionProperty(); + $sourceIdentifierAccessor = $this->getSingleIdPropertyAccessor($sourceClassMetadata); // e.g. Order::$id reflection + $sourcePropertyAccessor = $this->getPropertyAccessor($sourceClassMetadata, $sourcePropertyName); // e.g. Order::$items reflection + $targetIdentifierAccessor = $this->getSingleIdPropertyAccessor($targetClassMetadata); - if ($sourceIdentifierReflection === null || $sourcePropertyReflection === null || $targetIdentifierReflection === null) { + if ($sourceIdentifierAccessor === null || $sourcePropertyAccessor === null || $targetIdentifierAccessor === null) { throw new LogicException('Doctrine should use RuntimeReflectionService which never returns null.'); } @@ -181,9 +183,9 @@ private function preloadToMany( $uninitializedCollections = []; foreach ($sourceEntities as $sourceEntity) { - $sourceEntityId = $sourceIdentifierReflection->getValue($sourceEntity); + $sourceEntityId = $sourceIdentifierAccessor->getValue($sourceEntity); $sourceEntityKey = (string) $sourceEntityId; - $sourceEntityCollection = $sourcePropertyReflection->getValue($sourceEntity); + $sourceEntityCollection = $sourcePropertyAccessor->getValue($sourceEntity); if ( $sourceEntityCollection instanceof PersistentCollection @@ -196,7 +198,7 @@ private function preloadToMany( } foreach ($sourceEntityCollection as $targetEntity) { - $targetEntityKey = (string) $targetIdentifierReflection->getValue($targetEntity); + $targetEntityKey = (string) $targetIdentifierAccessor->getValue($targetEntity); $targetEntities[$targetEntityKey] = $targetEntity; } } @@ -213,10 +215,10 @@ private function preloadToMany( $targetEntitiesChunk = $innerLoader( associationMapping: $associationMapping, sourceClassMetadata: $sourceClassMetadata, - sourceIdentifierReflection: $sourceIdentifierReflection, + sourceIdentifierAccessor: $sourceIdentifierAccessor, sourcePropertyName: $sourcePropertyName, targetClassMetadata: $targetClassMetadata, - targetIdentifierReflection: $targetIdentifierReflection, + targetIdentifierAccessor: $targetIdentifierAccessor, uninitializedSourceEntityIdsChunk: array_values($uninitializedSourceEntityIdsChunk), uninitializedCollections: $uninitializedCollections, maxFetchJoinSameFieldCount: $maxFetchJoinSameFieldCount, @@ -250,20 +252,20 @@ private function preloadToMany( private function preloadOneToManyInner( array|ArrayAccess $associationMapping, ClassMetadata $sourceClassMetadata, - ReflectionProperty $sourceIdentifierReflection, + PropertyAccessor|ReflectionProperty $sourceIdentifierAccessor, string $sourcePropertyName, ClassMetadata $targetClassMetadata, - ReflectionProperty $targetIdentifierReflection, + PropertyAccessor|ReflectionProperty $targetIdentifierAccessor, array $uninitializedSourceEntityIdsChunk, array $uninitializedCollections, int $maxFetchJoinSameFieldCount, ): array { $targetPropertyName = $sourceClassMetadata->getAssociationMappedByTargetField($sourcePropertyName); // e.g. 'order' - $targetPropertyReflection = $targetClassMetadata->getReflectionProperty($targetPropertyName); // e.g. Item::$order reflection + $targetPropertyAccessor = $this->getPropertyAccessor($targetClassMetadata, $targetPropertyName); // e.g. Item::$order reflection $targetEntities = []; - if ($targetPropertyReflection === null) { + if ($targetPropertyAccessor === null) { throw new LogicException('Doctrine should use RuntimeReflectionService which never returns null.'); } @@ -276,11 +278,11 @@ private function preloadOneToManyInner( ); foreach ($targetEntitiesList as $targetEntity) { - $sourceEntity = $targetPropertyReflection->getValue($targetEntity); - $sourceEntityKey = (string) $sourceIdentifierReflection->getValue($sourceEntity); + $sourceEntity = $targetPropertyAccessor->getValue($targetEntity); + $sourceEntityKey = (string) $sourceIdentifierAccessor->getValue($sourceEntity); $uninitializedCollections[$sourceEntityKey]->add($targetEntity); - $targetEntityKey = (string) $targetIdentifierReflection->getValue($targetEntity); + $targetEntityKey = (string) $targetIdentifierAccessor->getValue($targetEntity); $targetEntities[$targetEntityKey] = $targetEntity; } @@ -302,10 +304,10 @@ private function preloadOneToManyInner( private function preloadManyToManyInner( array|ArrayAccess $associationMapping, ClassMetadata $sourceClassMetadata, - ReflectionProperty $sourceIdentifierReflection, + PropertyAccessor|ReflectionProperty $sourceIdentifierAccessor, string $sourcePropertyName, ClassMetadata $targetClassMetadata, - ReflectionProperty $targetIdentifierReflection, + PropertyAccessor|ReflectionProperty $targetIdentifierAccessor, array $uninitializedSourceEntityIdsChunk, array $uninitializedCollections, int $maxFetchJoinSameFieldCount, @@ -346,7 +348,7 @@ private function preloadManyToManyInner( } foreach ($this->loadEntitiesBy($targetClassMetadata, $targetIdentifierName, array_values($uninitializedTargetEntityIds), $maxFetchJoinSameFieldCount) as $targetEntity) { - $targetEntityKey = (string) $targetIdentifierReflection->getValue($targetEntity); + $targetEntityKey = (string) $targetIdentifierAccessor->getValue($targetEntity); $targetEntities[$targetEntityKey] = $targetEntity; } @@ -379,9 +381,9 @@ private function preloadToOne( int $maxFetchJoinSameFieldCount, ): array { - $sourcePropertyReflection = $sourceClassMetadata->getReflectionProperty($sourcePropertyName); // e.g. Item::$order reflection + $sourcePropertyAccessor = $this->getPropertyAccessor($sourceClassMetadata, $sourcePropertyName); // e.g. Item::$order reflection - if ($sourcePropertyReflection === null) { + if ($sourcePropertyAccessor === null) { throw new LogicException('Doctrine should use RuntimeReflectionService which never returns null.'); } @@ -389,7 +391,7 @@ private function preloadToOne( $targetEntities = []; foreach ($sourceEntities as $sourceEntity) { - $targetEntity = $sourcePropertyReflection->getValue($sourceEntity); + $targetEntity = $sourcePropertyAccessor->getValue($sourceEntity); if ($targetEntity === null) { continue; @@ -483,4 +485,31 @@ private function addFetchJoinsToPreventFetchDuringHydration( } } + /** + * @param ClassMetadata $classMetadata + */ + private function getSingleIdPropertyAccessor(ClassMetadata $classMetadata): PropertyAccessor|ReflectionProperty|null + { + if (method_exists($classMetadata, 'getSingleIdPropertyAccessor')) { + return $classMetadata->getSingleIdPropertyAccessor(); + } + + return $classMetadata->getSingleIdReflectionProperty(); + } + + /** + * @param ClassMetadata $classMetadata + */ + private function getPropertyAccessor( + ClassMetadata $classMetadata, + string $property, + ): PropertyAccessor|ReflectionProperty|null + { + if (method_exists($classMetadata, 'getPropertyAccessor')) { + return $classMetadata->getPropertyAccessor($property); + } + + return $classMetadata->getReflectionProperty($property); + } + } diff --git a/tests/Fixtures/Blog/Article.php b/tests/Fixtures/Blog/Article.php index 88626a0..b577b65 100644 --- a/tests/Fixtures/Blog/Article.php +++ b/tests/Fixtures/Blog/Article.php @@ -9,6 +9,8 @@ use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\InverseJoinColumn; +use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Mapping\ManyToMany; use Doctrine\ORM\Mapping\ManyToOne; use Doctrine\ORM\Mapping\OneToMany; @@ -36,6 +38,8 @@ class Article * @var Collection */ #[ManyToMany(targetEntity: Tag::class, inversedBy: 'articles')] + #[JoinColumn(nullable: false)] + #[InverseJoinColumn(nullable: false)] private Collection $tags; /**