diff --git a/UPGRADE.md b/UPGRADE.md index e6a61238a89..71c2fc9a7b2 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,16 @@ # Upgrade to 3.0 +## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception + +Previously, calling +`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with +the owning side of an association returned `null`, which was undocumented, and +wrong according to the phpdoc of the parent method. + +If you do not know whether you are on the owning or inverse side of an association, +you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()` +to find out. + ## BC BREAK: `Doctrine\ORM\Proxy\Autoloader` no longer extends `Doctrine\Common\Proxy\Autoloader` Make sure to use the former when writing a type declaration or an `instanceof` check. diff --git a/src/Mapping/ClassMetadata.php b/src/Mapping/ClassMetadata.php index b3d7838cce6..8cd2767889e 100644 --- a/src/Mapping/ClassMetadata.php +++ b/src/Mapping/ClassMetadata.php @@ -41,6 +41,7 @@ use function ltrim; use function method_exists; use function spl_object_id; +use function sprintf; use function str_contains; use function str_replace; use function strtolower; @@ -2457,9 +2458,20 @@ public function isAssociationInverseSide(string $assocName): bool public function getAssociationMappedByTargetField(string $assocName): string { - $assoc = $this->associationMappings[$assocName]; + $assoc = $this->getAssociationMapping($assocName); - assert($assoc instanceof InverseSideMapping); + if (! $assoc instanceof InverseSideMapping) { + throw new LogicException(sprintf( + <<<'EXCEPTION' + Context: Calling %s() with "%s", which is the owning side of an association. + Problem: The owning side of an association has no "mappedBy" field. + Solution: Call %s::isAssociationInverseSide() to check first. + EXCEPTION, + __METHOD__, + $assocName, + self::class, + )); + } return $assoc->mappedBy; } diff --git a/tests/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Tests/ORM/Mapping/ClassMetadataTest.php index edd05959408..c83960daf83 100644 --- a/tests/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Tests/ORM/Mapping/ClassMetadataTest.php @@ -47,6 +47,7 @@ use Doctrine\Tests\ORM\Mapping\TypedFieldMapper\CustomIntAsStringTypedFieldMapper; use Doctrine\Tests\OrmTestCase; use DoctrineGlobalArticle; +use LogicException; use PHPUnit\Framework\Attributes\Group as TestGroup; use ReflectionClass; use stdClass; @@ -1054,6 +1055,21 @@ public function testItAddingLifecycleCallbackOnEmbeddedClassIsIllegal(): void $metadata->addLifecycleCallback('foo', 'bar'); } + + public function testItThrowsOnInvalidCallToGetAssociationMappedByTargetField(): void + { + $metadata = new ClassMetadata(self::class); + $metadata->mapOneToOne(['fieldName' => 'foo', 'targetEntity' => 'bar']); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage(<<<'EXCEPTION' + Context: Calling Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField() with "foo", which is the owning side of an association. + Problem: The owning side of an association has no "mappedBy" field. + Solution: Call Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide() to check first. + EXCEPTION); + + $metadata->getAssociationMappedByTargetField('foo'); + } } #[MappedSuperclass]