Skip to content

Commit b6ac23f

Browse files
committed
Take hardcoded reference column name out of JoinColumn attribute
1 parent b1f8253 commit b6ac23f

9 files changed

+226
-4
lines changed

src/Mapping/JoinColumnMapping.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function __construct(
3333
* @param array<string, mixed> $mappingArray
3434
* @phpstan-param array{
3535
* name: string,
36-
* referencedColumnName: string,
36+
* referencedColumnName: string|null,
3737
* unique?: bool|null,
3838
* quoted?: bool|null,
3939
* fieldName?: string|null,

src/Mapping/JoinColumnProperties.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ trait JoinColumnProperties
99
/** @param array<string, mixed> $options */
1010
public function __construct(
1111
public readonly string|null $name = null,
12-
public readonly string $referencedColumnName = 'id',
12+
public readonly string|null $referencedColumnName = null,
1313
public readonly bool $unique = false,
1414
public readonly bool $nullable = true,
1515
public readonly mixed $onDelete = null,

src/Mapping/ManyToManyOwningSideMapping.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,27 @@ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, Na
6161
{
6262
if (isset($mappingArray['joinTable']['joinColumns'])) {
6363
foreach ($mappingArray['joinTable']['joinColumns'] as $key => $joinColumn) {
64+
if (empty($joinColumn['referencedColumnName'])) {
65+
$mappingArray['joinTable']['joinColumns'][$key]['referencedColumnName'] = $namingStrategy->referenceColumnName();
66+
}
6467
if (empty($joinColumn['name'])) {
6568
$mappingArray['joinTable']['joinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName(
6669
$mappingArray['sourceEntity'],
67-
$joinColumn['referencedColumnName'] ?? null,
70+
$joinColumn['referencedColumnName'] ?? $namingStrategy->referenceColumnName(),
6871
);
6972
}
7073
}
7174
}
7275

7376
if (isset($mappingArray['joinTable']['inverseJoinColumns'])) {
7477
foreach ($mappingArray['joinTable']['inverseJoinColumns'] as $key => $joinColumn) {
78+
if (empty($joinColumn['referencedColumnName'])) {
79+
$mappingArray['joinTable']['inverseJoinColumns'][$key]['referencedColumnName'] = $namingStrategy->referenceColumnName();
80+
}
7581
if (empty($joinColumn['name'])) {
7682
$mappingArray['joinTable']['inverseJoinColumns'][$key]['name'] = $namingStrategy->joinKeyColumnName(
7783
$mappingArray['targetEntity'],
78-
$joinColumn['referencedColumnName'] ?? null,
84+
$joinColumn['referencedColumnName'] ?? $namingStrategy->referenceColumnName(),
7985
);
8086
}
8187
}

src/Mapping/ToOneOwningSideMapping.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ public static function fromMappingArrayAndName(
107107
if (empty($joinColumn['name'])) {
108108
$mappingArray['joinColumns'][$index]['name'] = $namingStrategy->joinColumnName($mappingArray['fieldName'], $name);
109109
}
110+
if (empty($joinColumn['referencedColumnName'])) {
111+
$mappingArray['joinColumns'][$index]['referencedColumnName'] = $namingStrategy->referenceColumnName();
112+
}
110113
}
111114
}
112115

tests/Tests/ORM/Mapping/MappingDriverTestCase.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
use Doctrine\Tests\Models\TypedProperties\UserTypedWithCustomTypedField;
6363
use Doctrine\Tests\Models\Upsertable\Insertable;
6464
use Doctrine\Tests\Models\Upsertable\Updatable;
65+
use Doctrine\Tests\ORM\Mapping\NamingStrategy\CustomPascalNamingStrategy;
6566
use Doctrine\Tests\OrmTestCase;
6667
use PHPUnit\Framework\Attributes\Depends;
6768
use stdClass;
@@ -946,6 +947,16 @@ public function testEnumType(): void
946947

947948
self::assertEquals(Suit::class, $metadata->fieldMappings['suit']->enumType);
948949
}
950+
951+
public function testCustomNamingStrategyIsRespected(): void
952+
{
953+
$ns = new CustomPascalNamingStrategy();
954+
$metadata = $this->createClassMetadata(BlogPostComment::class, $ns);
955+
956+
self::assertEquals('id', $metadata->fieldNames['Id']);
957+
self::assertEquals('Id', $metadata->associationMappings['blogPost']->joinColumns[0]->referencedColumnName);
958+
self::assertFalse($metadata->associationMappings['blogPost']->joinColumns[0]->nullable);
959+
}
949960
}
950961

951962
#[ORM\Entity()]
@@ -1547,3 +1558,49 @@ public static function loadMetadata(ClassMetadata $metadata): void
15471558
class GH10288EnumTypeBoss extends GH10288EnumTypePerson
15481559
{
15491560
}
1561+
1562+
/**
1563+
* Two small related entities to test default namings with barebone attributes
1564+
*/
1565+
#[Entity]
1566+
class BlogPost
1567+
{
1568+
#[Id, Column, GeneratedValue(strategy: 'NONE')]
1569+
public int $id;
1570+
}
1571+
1572+
#[Entity]
1573+
class BlogPostComment
1574+
{
1575+
#[Id, Column, GeneratedValue(strategy: 'AUTO')]
1576+
public int $id;
1577+
1578+
#[ORM\ManyToOne, ORM\JoinColumn(nullable: false)]
1579+
public BlogPost $blogPost;
1580+
1581+
public static function loadMetadata(ClassMetadata $metadata): void
1582+
{
1583+
$metadata->mapField(
1584+
[
1585+
'id' => true,
1586+
'fieldName' => 'id',
1587+
'type' => 'integer',
1588+
],
1589+
);
1590+
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
1591+
1592+
$metadata->mapManyToOne(
1593+
[
1594+
'fieldName' => 'blogPost',
1595+
'targetEntity' => BlogPost::class,
1596+
'joinColumns' =>
1597+
[
1598+
0 =>
1599+
[
1600+
'nullable' => false,
1601+
],
1602+
],
1603+
]
1604+
);
1605+
}
1606+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Mapping\NamingStrategy;
6+
7+
use Doctrine\ORM\Mapping\NamingStrategy;
8+
9+
/**
10+
* Fully customized naming strategy changing all namings to a PascalCase model. Included to test some behaviours
11+
* regarding fully custom naming strategies.
12+
*/
13+
class CustomPascalNamingStrategy implements NamingStrategy
14+
{
15+
/**
16+
* Returns a table name for an entity class.
17+
*
18+
* @param string $className The fully-qualified class name
19+
* @return string A table name
20+
*/
21+
public function classToTableName(string $className): string
22+
{
23+
if (str_contains($className, '\\')) {
24+
return substr($className, strrpos($className, '\\') + 1);
25+
}
26+
27+
return $className;
28+
}
29+
30+
/**
31+
* Returns a column name for a property.
32+
*
33+
* @param string $propertyName A property name
34+
* @param string|null $className The fully-qualified class name
35+
*
36+
* @return string A column name
37+
*/
38+
public function propertyToColumnName(string $propertyName, ?string $className = null): string
39+
{
40+
if (null !== $className && strtolower($propertyName) == strtolower($this->classToTableName($className)) . 'id') {
41+
return 'Id';
42+
}
43+
44+
return ucfirst($propertyName);
45+
}
46+
47+
/**
48+
* Returns a column name for an embedded property.
49+
*/
50+
public function embeddedFieldToColumnName(string $propertyName, string $embeddedColumnName, ?string $className = null, $embeddedClassName = null): string
51+
{
52+
throw new \LogicException(sprintf('Method %s is not implemented', __METHOD__));
53+
}
54+
55+
/**
56+
* Returns the default reference column name.
57+
*
58+
* @return string A column name
59+
*/
60+
public function referenceColumnName(): string
61+
{
62+
return 'Id';
63+
}
64+
65+
/**
66+
* Returns a join column name for a property.
67+
*
68+
* @return string A join column name
69+
*/
70+
public function joinColumnName(string $propertyName, string $className): string
71+
{
72+
return ucfirst($propertyName) . $this->referenceColumnName();
73+
}
74+
75+
/**
76+
* Returns a join table name.
77+
*
78+
* @param string $sourceEntity The source entity
79+
* @param string $targetEntity The target entity
80+
* @param string|null $propertyName A property name
81+
*
82+
* @return string A join table name
83+
*/
84+
public function joinTableName(string $sourceEntity, string $targetEntity, ?string $propertyName = null): string
85+
{
86+
return $this->classToTableName($sourceEntity) . $this->classToTableName($targetEntity);
87+
}
88+
89+
/**
90+
* Returns the foreign key column name for the given parameters.
91+
*
92+
* @param string $entityName An entity
93+
* @param string|null $referencedColumnName A property
94+
*
95+
* @return string A join column name
96+
*/
97+
public function joinKeyColumnName(string $entityName, ?string $referencedColumnName = null): string
98+
{
99+
return $this->classToTableName($entityName) . ($referencedColumnName ?: $this->referenceColumnName());
100+
}
101+
}

tests/Tests/ORM/Mapping/NamingStrategyTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
88
use Doctrine\ORM\Mapping\NamingStrategy;
99
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
10+
use Doctrine\Tests\ORM\Mapping\NamingStrategy\CustomPascalNamingStrategy;
1011
use Doctrine\Tests\ORM\Mapping\NamingStrategy\JoinColumnClassNamingStrategy;
1112
use Doctrine\Tests\OrmTestCase;
1213
use PHPUnit\Framework\Attributes\DataProvider;
@@ -33,6 +34,11 @@ private static function underscoreNamingUpper(): UnderscoreNamingStrategy
3334
return new UnderscoreNamingStrategy(CASE_UPPER);
3435
}
3536

37+
private static function customNaming(): CustomPascalNamingStrategy
38+
{
39+
return new CustomPascalNamingStrategy();
40+
}
41+
3642
/**
3743
* Data Provider for NamingStrategy#classToTableName
3844
*
@@ -56,6 +62,10 @@ public static function dataClassToTableName(): array
5662
[self::underscoreNamingUpper(), 'NAME', '\Some\Class\Name'],
5763
[self::underscoreNamingUpper(), 'NAME2_TEST', '\Some\Class\Name2Test'],
5864
[self::underscoreNamingUpper(), 'NAME2TEST', '\Some\Class\Name2test'],
65+
66+
// CustomPascalNamingStrategy
67+
[self::customNaming(), 'SomeClassName', 'SomeClassName'],
68+
[self::customNaming(), 'Name2Test', '\Some\Class\Name2Test'],
5969
];
6070
}
6171

@@ -89,6 +99,10 @@ public static function dataPropertyToColumnName(): array
8999
[self::underscoreNamingUpper(), 'SOME_PROPERTY', 'SOME_PROPERTY', 'Some\Class'],
90100
[self::underscoreNamingUpper(), 'BASE64_ENCODED', 'base64Encoded', 'Some\Class'],
91101
[self::underscoreNamingUpper(), 'BASE64ENCODED', 'base64encoded', 'Some\Class'],
102+
103+
// CustomPascalNamingStrategy
104+
[self::customNaming(), 'SomeProperty', 'someProperty', 'Some\Class'],
105+
[self::customNaming(), 'Base64Encoded', 'base64Encoded', 'Some\Class'],
92106
];
93107
}
94108

@@ -116,6 +130,9 @@ public static function dataReferenceColumnName(): array
116130
// UnderscoreNamingStrategy
117131
[self::underscoreNamingLower(), 'id'],
118132
[self::underscoreNamingUpper(), 'ID'],
133+
134+
// CustomPascalNamingStrategy
135+
[self::customNaming(), 'Id'],
119136
];
120137
}
121138

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
6+
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
7+
8+
<entity name="Doctrine\Tests\ORM\Mapping\BlogPost">
9+
10+
<id name="id" type="integer" column="id">
11+
<generator strategy="NONE"/>
12+
</id>
13+
14+
</entity>
15+
16+
</doctrine-mapping>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
6+
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
7+
8+
<entity name="Doctrine\Tests\ORM\Mapping\BlogPostComment">
9+
10+
<id name="id" type="integer">
11+
<generator strategy="NONE"/>
12+
</id>
13+
14+
<many-to-one field="blogPost" target-entity="Doctrine\Tests\ORM\Mapping\BlogPost">
15+
<join-columns>
16+
<join-column nullable="false"/>
17+
</join-columns>
18+
</many-to-one>
19+
20+
</entity>
21+
22+
</doctrine-mapping>

0 commit comments

Comments
 (0)