diff --git a/src/Mapping/JoinColumnMapping.php b/src/Mapping/JoinColumnMapping.php index cb54b196a9..86ea1dc09c 100644 --- a/src/Mapping/JoinColumnMapping.php +++ b/src/Mapping/JoinColumnMapping.php @@ -33,7 +33,7 @@ public function __construct( * @param array $mappingArray * @phpstan-param array{ * name: string, - * referencedColumnName: string, + * referencedColumnName: string|null, * unique?: bool|null, * quoted?: bool|null, * fieldName?: string|null, diff --git a/src/Mapping/JoinColumnProperties.php b/src/Mapping/JoinColumnProperties.php index 7d132952b3..b231d834d7 100644 --- a/src/Mapping/JoinColumnProperties.php +++ b/src/Mapping/JoinColumnProperties.php @@ -9,7 +9,7 @@ trait JoinColumnProperties /** @param array $options */ public function __construct( public readonly string|null $name = null, - public readonly string $referencedColumnName = 'id', + public readonly string|null $referencedColumnName = null, public readonly bool $unique = false, public readonly bool $nullable = true, public readonly mixed $onDelete = null, diff --git a/src/Mapping/ManyToManyOwningSideMapping.php b/src/Mapping/ManyToManyOwningSideMapping.php index d8abaedaee..3031e749c3 100644 --- a/src/Mapping/ManyToManyOwningSideMapping.php +++ b/src/Mapping/ManyToManyOwningSideMapping.php @@ -61,10 +61,13 @@ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, Na { if (isset($mappingArray['joinTable']['joinColumns'])) { foreach ($mappingArray['joinTable']['joinColumns'] as $key => $joinColumn) { + if (empty($joinColumn['referencedColumnName'])) { + $mappingArray['joinTable']['joinColumns'][$key]['referencedColumnName'] = $namingStrategy->referenceColumnName(); + } if (empty($joinColumn['name'])) { $mappingArray['joinTable']['joinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( $mappingArray['sourceEntity'], - $joinColumn['referencedColumnName'] ?? null, + $joinColumn['referencedColumnName'] ?? $namingStrategy->referenceColumnName(), ); } } @@ -72,10 +75,13 @@ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, Na if (isset($mappingArray['joinTable']['inverseJoinColumns'])) { foreach ($mappingArray['joinTable']['inverseJoinColumns'] as $key => $joinColumn) { + if (empty($joinColumn['referencedColumnName'])) { + $mappingArray['joinTable']['inverseJoinColumns'][$key]['referencedColumnName'] = $namingStrategy->referenceColumnName(); + } if (empty($joinColumn['name'])) { $mappingArray['joinTable']['inverseJoinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName( $mappingArray['targetEntity'], - $joinColumn['referencedColumnName'] ?? null, + $joinColumn['referencedColumnName'] ?? $namingStrategy->referenceColumnName(), ); } } diff --git a/src/Mapping/ToOneOwningSideMapping.php b/src/Mapping/ToOneOwningSideMapping.php index ed3d596f80..57ed66e6a9 100644 --- a/src/Mapping/ToOneOwningSideMapping.php +++ b/src/Mapping/ToOneOwningSideMapping.php @@ -107,6 +107,9 @@ public static function fromMappingArrayAndName( if (empty($joinColumn['name'])) { $mappingArray['joinColumns'][$index]['name'] = $namingStrategy->joinColumnName($mappingArray['fieldName'], $name); } + if (empty($joinColumn['referencedColumnName'])) { + $mappingArray['joinColumns'][$index]['referencedColumnName'] = $namingStrategy->referenceColumnName(); + } } } diff --git a/tests/Tests/ORM/Mapping/MappingDriverTestCase.php b/tests/Tests/ORM/Mapping/MappingDriverTestCase.php index 10d9d4ba96..1f91a02de8 100644 --- a/tests/Tests/ORM/Mapping/MappingDriverTestCase.php +++ b/tests/Tests/ORM/Mapping/MappingDriverTestCase.php @@ -62,6 +62,7 @@ use Doctrine\Tests\Models\TypedProperties\UserTypedWithCustomTypedField; use Doctrine\Tests\Models\Upsertable\Insertable; use Doctrine\Tests\Models\Upsertable\Updatable; +use Doctrine\Tests\ORM\Mapping\NamingStrategy\CustomPascalNamingStrategy; use Doctrine\Tests\OrmTestCase; use PHPUnit\Framework\Attributes\Depends; use stdClass; @@ -946,6 +947,16 @@ public function testEnumType(): void self::assertEquals(Suit::class, $metadata->fieldMappings['suit']->enumType); } + + public function testCustomNamingStrategyIsRespected(): void + { + $ns = new CustomPascalNamingStrategy(); + $metadata = $this->createClassMetadata(BlogPostComment::class, $ns); + + self::assertEquals('id', $metadata->fieldNames['Id']); + self::assertEquals('Id', $metadata->associationMappings['blogPost']->joinColumns[0]->referencedColumnName); + self::assertFalse($metadata->associationMappings['blogPost']->joinColumns[0]->nullable); + } } #[ORM\Entity()] @@ -1547,3 +1558,49 @@ public static function loadMetadata(ClassMetadata $metadata): void class GH10288EnumTypeBoss extends GH10288EnumTypePerson { } + +/** + * Two small related entities to test default namings with barebone attributes + */ +#[Entity] +class BlogPost +{ + #[Id, Column, GeneratedValue(strategy: 'NONE')] + public int $id; +} + +#[Entity] +class BlogPostComment +{ + #[Id, Column, GeneratedValue(strategy: 'AUTO')] + public int $id; + + #[ORM\ManyToOne, ORM\JoinColumn(nullable: false)] + public BlogPost $blogPost; + + public static function loadMetadata(ClassMetadata $metadata): void + { + $metadata->mapField( + [ + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer', + ], + ); + $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); + + $metadata->mapManyToOne( + [ + 'fieldName' => 'blogPost', + 'targetEntity' => BlogPost::class, + 'joinColumns' => + [ + 0 => + [ + 'nullable' => false, + ], + ], + ] + ); + } +} diff --git a/tests/Tests/ORM/Mapping/NamingStrategy/CustomPascalNamingStrategy.php b/tests/Tests/ORM/Mapping/NamingStrategy/CustomPascalNamingStrategy.php new file mode 100644 index 0000000000..0936b45cd0 --- /dev/null +++ b/tests/Tests/ORM/Mapping/NamingStrategy/CustomPascalNamingStrategy.php @@ -0,0 +1,101 @@ +classToTableName($className)) . 'id') { + return 'Id'; + } + + return ucfirst($propertyName); + } + + /** + * Returns a column name for an embedded property. + */ + public function embeddedFieldToColumnName(string $propertyName, string $embeddedColumnName, ?string $className = null, $embeddedClassName = null): string + { + throw new \LogicException(sprintf('Method %s is not implemented', __METHOD__)); + } + + /** + * Returns the default reference column name. + * + * @return string A column name + */ + public function referenceColumnName(): string + { + return 'Id'; + } + + /** + * Returns a join column name for a property. + * + * @return string A join column name + */ + public function joinColumnName(string $propertyName, string $className): string + { + return ucfirst($propertyName) . $this->referenceColumnName(); + } + + /** + * Returns a join table name. + * + * @param string $sourceEntity The source entity + * @param string $targetEntity The target entity + * @param string|null $propertyName A property name + * + * @return string A join table name + */ + public function joinTableName(string $sourceEntity, string $targetEntity, ?string $propertyName = null): string + { + return $this->classToTableName($sourceEntity) . $this->classToTableName($targetEntity); + } + + /** + * Returns the foreign key column name for the given parameters. + * + * @param string $entityName An entity + * @param string|null $referencedColumnName A property + * + * @return string A join column name + */ + public function joinKeyColumnName(string $entityName, ?string $referencedColumnName = null): string + { + return $this->classToTableName($entityName) . ($referencedColumnName ?: $this->referenceColumnName()); + } +} diff --git a/tests/Tests/ORM/Mapping/NamingStrategyTest.php b/tests/Tests/ORM/Mapping/NamingStrategyTest.php index ecade64739..18ee29fbe4 100644 --- a/tests/Tests/ORM/Mapping/NamingStrategyTest.php +++ b/tests/Tests/ORM/Mapping/NamingStrategyTest.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Mapping\DefaultNamingStrategy; use Doctrine\ORM\Mapping\NamingStrategy; use Doctrine\ORM\Mapping\UnderscoreNamingStrategy; +use Doctrine\Tests\ORM\Mapping\NamingStrategy\CustomPascalNamingStrategy; use Doctrine\Tests\ORM\Mapping\NamingStrategy\JoinColumnClassNamingStrategy; use Doctrine\Tests\OrmTestCase; use PHPUnit\Framework\Attributes\DataProvider; @@ -33,6 +34,11 @@ private static function underscoreNamingUpper(): UnderscoreNamingStrategy return new UnderscoreNamingStrategy(CASE_UPPER); } + private static function customNaming(): CustomPascalNamingStrategy + { + return new CustomPascalNamingStrategy(); + } + /** * Data Provider for NamingStrategy#classToTableName * @@ -56,6 +62,10 @@ public static function dataClassToTableName(): array [self::underscoreNamingUpper(), 'NAME', '\Some\Class\Name'], [self::underscoreNamingUpper(), 'NAME2_TEST', '\Some\Class\Name2Test'], [self::underscoreNamingUpper(), 'NAME2TEST', '\Some\Class\Name2test'], + + // CustomPascalNamingStrategy + [self::customNaming(), 'SomeClassName', 'SomeClassName'], + [self::customNaming(), 'Name2Test', '\Some\Class\Name2Test'], ]; } @@ -89,6 +99,10 @@ public static function dataPropertyToColumnName(): array [self::underscoreNamingUpper(), 'SOME_PROPERTY', 'SOME_PROPERTY', 'Some\Class'], [self::underscoreNamingUpper(), 'BASE64_ENCODED', 'base64Encoded', 'Some\Class'], [self::underscoreNamingUpper(), 'BASE64ENCODED', 'base64encoded', 'Some\Class'], + + // CustomPascalNamingStrategy + [self::customNaming(), 'SomeProperty', 'someProperty', 'Some\Class'], + [self::customNaming(), 'Base64Encoded', 'base64Encoded', 'Some\Class'], ]; } @@ -116,6 +130,9 @@ public static function dataReferenceColumnName(): array // UnderscoreNamingStrategy [self::underscoreNamingLower(), 'id'], [self::underscoreNamingUpper(), 'ID'], + + // CustomPascalNamingStrategy + [self::customNaming(), 'Id'], ]; } diff --git a/tests/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.BlogPost.dcm.xml b/tests/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.BlogPost.dcm.xml new file mode 100644 index 0000000000..48eda68df1 --- /dev/null +++ b/tests/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.BlogPost.dcm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/tests/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.BlogPostComment.dcm.xml b/tests/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.BlogPostComment.dcm.xml new file mode 100644 index 0000000000..58c6356529 --- /dev/null +++ b/tests/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.BlogPostComment.dcm.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + +