Skip to content

Commit c03ed69

Browse files
authored
Merge pull request doctrine#12132 from doctrine/3.6.x
Merge 3.6.x up into 4.0.x
2 parents b521e89 + ce844d9 commit c03ed69

29 files changed

+827
-55
lines changed

UPGRADE.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,40 @@ using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\In
120120

121121
# Upgrade to 3.6
122122

123+
## Deprecate specifying `nullable` on columns that end up being used in a primary key
124+
125+
Specifying `nullable` on join columns that are part of a primary key is
126+
deprecated and will be an error in 4.0.
127+
128+
This can happen when using a join column mapping together with an id mapping,
129+
or when using a join column mapping or an inverse join column mapping on a
130+
many-to-many relationship.
131+
132+
```diff
133+
class User
134+
{
135+
#[ORM\Id]
136+
#[ORM\Column(type: 'integer')]
137+
private int $id;
138+
139+
#[ORM\Id]
140+
#[ORM\ManyToOne(targetEntity: Family::class, inversedBy: 'users')]
141+
- #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id', nullable: true)]
142+
+ #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id')]
143+
private ?Family $family;
144+
145+
#[ORM\ManyToMany(targetEntity: Group::class)]
146+
#[ORM\JoinTable(name: 'user_group')]
147+
- #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true)]
148+
- #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id', nullable: true)]
149+
+ #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id')]
150+
+ #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
151+
private Collection $groups;
152+
}
153+
```
154+
155+
## Deprecate `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
156+
123157
Using `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
124158
is deprecated in favor of using an associative array of join parts with the
125159
root alias as key.

src/Internal/Hydration/ScalarColumnHydrator.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Doctrine\DBAL\Driver\Exception;
88
use Doctrine\ORM\Exception\MultipleSelectorsFoundException;
99

10-
use function array_column;
1110
use function count;
1211

1312
/**
@@ -27,8 +26,6 @@ protected function hydrateAllData(): array
2726
throw MultipleSelectorsFoundException::create($this->resultSetMapping()->fieldMappings);
2827
}
2928

30-
$result = $this->statement()->fetchAllNumeric();
31-
32-
return array_column($result, 0);
29+
return $this->statement()->fetchFirstColumn();
3330
}
3431
}

src/Mapping/Builder/AssociationBuilder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ public function addJoinColumn(
113113
string|null $onDelete = null,
114114
string|null $columnDef = null,
115115
): static {
116+
if ($this->mapping['id'] ?? false) {
117+
$nullable = null;
118+
}
119+
116120
$this->joinColumns[] = [
117121
'name' => $columnName,
118122
'referencedColumnName' => $referencedColumnName,
@@ -133,6 +137,9 @@ public function addJoinColumn(
133137
public function makePrimaryKey(): static
134138
{
135139
$this->mapping['id'] = true;
140+
foreach ($this->joinColumns ?? [] as $i => $joinColumn) {
141+
$this->joinColumns[$i]['nullable'] = null;
142+
}
136143

137144
return $this;
138145
}

src/Mapping/Builder/ManyToManyAssociationBuilder.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@ public function setJoinTable(string $name): static
2424
return $this;
2525
}
2626

27+
/**
28+
* Add Join Columns.
29+
*
30+
* @return $this
31+
*/
32+
public function addJoinColumn(
33+
string $columnName,
34+
string $referencedColumnName,
35+
bool $nullable = true,
36+
bool $unique = false,
37+
string|null $onDelete = null,
38+
string|null $columnDef = null,
39+
): static {
40+
$this->joinColumns[] = [
41+
'name' => $columnName,
42+
'referencedColumnName' => $referencedColumnName,
43+
'unique' => $unique,
44+
'onDelete' => $onDelete,
45+
'columnDefinition' => $columnDef,
46+
];
47+
48+
return $this;
49+
}
50+
2751
/**
2852
* Adds Inverse Join Columns.
2953
*
@@ -40,7 +64,6 @@ public function addInverseJoinColumn(
4064
$this->inverseJoinColumns[] = [
4165
'name' => $columnName,
4266
'referencedColumnName' => $referencedColumnName,
43-
'nullable' => $nullable,
4467
'unique' => $unique,
4568
'onDelete' => $onDelete,
4669
'columnDefinition' => $columnDef,

src/Mapping/JoinColumnProperties.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public function __construct(
1212
public readonly string|null $referencedColumnName = null,
1313
public readonly bool $deferrable = false,
1414
public readonly bool $unique = false,
15-
public readonly bool $nullable = true,
15+
public readonly bool|null $nullable = null,
1616
public readonly mixed $onDelete = null,
1717
public readonly string|null $columnDefinition = null,
1818
public readonly string|null $fieldName = null,

src/Mapping/JoinTableMapping.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,14 @@ public function offsetSet(mixed $offset, mixed $value): void
7979
/** @return mixed[] */
8080
public function toArray(): array
8181
{
82-
$array = (array) $this;
82+
$array = (array) $this;
83+
$toArray = static function (JoinColumnMapping $column) {
84+
$array = (array) $column;
8385

84-
$toArray = static fn (JoinColumnMapping $column): array => (array) $column;
86+
unset($array['nullable']);
87+
88+
return $array;
89+
};
8590
$array['joinColumns'] = array_map($toArray, $array['joinColumns']);
8691
$array['inverseJoinColumns'] = array_map($toArray, $array['inverseJoinColumns']);
8792

src/Mapping/ManyToManyOwningSideMapping.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Doctrine\ORM\Mapping;
66

7+
use Doctrine\Deprecations\Deprecation;
8+
79
use function strtolower;
810
use function trim;
911

@@ -127,6 +129,22 @@ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, Na
127129
$mapping->joinTableColumns = [];
128130

129131
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
132+
if ($joinColumn->nullable !== null) {
133+
Deprecation::trigger(
134+
'doctrine/orm',
135+
'https://github/doctrine/orm/pull/12125',
136+
<<<'DEPRECATION'
137+
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
138+
The ORM will always set it to false.
139+
Doing so is deprecated and will be an error in 4.0.
140+
DEPRECATION,
141+
$mapping->sourceEntity,
142+
$mapping->fieldName,
143+
);
144+
}
145+
146+
$joinColumn->nullable = false;
147+
130148
if (empty($joinColumn->referencedColumnName)) {
131149
$joinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
132150
}
@@ -150,6 +168,22 @@ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, Na
150168
}
151169

152170
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
171+
if ($inverseJoinColumn->nullable !== null) {
172+
Deprecation::trigger(
173+
'doctrine/orm',
174+
'https://github/doctrine/orm/pull/12125',
175+
<<<'DEPRECATION'
176+
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
177+
The ORM will always set it to false.
178+
Doing so is deprecated and will be an error in 4.0.
179+
DEPRECATION,
180+
$mapping->targetEntity,
181+
$mapping->fieldName,
182+
);
183+
}
184+
185+
$inverseJoinColumn->nullable = false;
186+
153187
if (empty($inverseJoinColumn->referencedColumnName)) {
154188
$inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
155189
}

src/Mapping/ToOneOwningSideMapping.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Doctrine\ORM\Mapping;
66

7+
use Doctrine\Deprecations\Deprecation;
78
use RuntimeException;
89

910
use function array_flip;
@@ -130,6 +131,26 @@ public static function fromMappingArrayAndName(
130131
$uniqueConstraintColumns = [];
131132

132133
foreach ($mapping->joinColumns as $joinColumn) {
134+
if ($mapping->id) {
135+
if ($joinColumn->nullable !== null) {
136+
Deprecation::trigger(
137+
'doctrine/orm',
138+
'https://github/doctrine/orm/pull/12125',
139+
<<<'DEPRECATION'
140+
Specifying the "nullable" attribute for join columns in to-one associations (here, %s::$%s) that are part of the identifier is a no-op.
141+
The ORM will always set it to false.
142+
Doing so is deprecated and will be an error in 4.0.
143+
DEPRECATION,
144+
$mapping->sourceEntity,
145+
$mapping->fieldName,
146+
);
147+
}
148+
149+
$joinColumn->nullable = false;
150+
} elseif ($joinColumn->nullable === null) {
151+
$joinColumn->nullable = true;
152+
}
153+
133154
if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) {
134155
if (count($mapping->joinColumns) === 1) {
135156
if (empty($mapping->id)) {
@@ -194,7 +215,12 @@ public function toArray(): array
194215

195216
$joinColumns = [];
196217
foreach ($array['joinColumns'] as $column) {
197-
$joinColumns[] = (array) $column;
218+
$columnArray = (array) $column;
219+
if ($this->id) {
220+
unset($columnArray['nullable']);
221+
}
222+
223+
$joinColumns[] = $columnArray;
198224
}
199225

200226
$array['joinColumns'] = $joinColumns;

src/Persisters/Entity/BasicEntityPersister.php

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,6 @@ class BasicEntityPersister implements EntityPersister
152152
*/
153153
protected array $quotedColumns = [];
154154

155-
/**
156-
* The INSERT SQL statement used for entities handled by this persister.
157-
* This SQL is only generated once per request, if at all.
158-
*/
159-
private string|null $insertSql = null;
160-
161155
/**
162156
* The quote strategy.
163157
*/
@@ -272,8 +266,8 @@ public function executeInserts(): void
272266
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
273267
}
274268

275-
// Unset this queued insert, so that the prepareUpdateData() method knows right away
276-
// (for the next entity already) that the current entity has been written to the database
269+
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
270+
// knows right away (for the next entity already) that the current entity has been written to the database
277271
// and no extra updates need to be scheduled to refer to it.
278272
//
279273
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
@@ -1416,22 +1410,17 @@ protected function getSelectManyToManyJoinSQL(AssociationMapping&ManyToManyAssoc
14161410

14171411
public function getInsertSQL(): string
14181412
{
1419-
if ($this->insertSql !== null) {
1420-
return $this->insertSql;
1421-
}
1422-
14231413
$columns = $this->getInsertColumnList();
14241414
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
14251415

1426-
if (empty($columns)) {
1427-
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
1428-
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
1416+
if ($columns === []) {
1417+
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
14291418

1430-
return $this->insertSql;
1419+
return $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
14311420
}
14321421

1433-
$values = [];
1434-
$columns = array_unique($columns);
1422+
$placeholders = [];
1423+
$columns = array_unique($columns);
14351424

14361425
foreach ($columns as $column) {
14371426
$placeholder = '?';
@@ -1445,15 +1434,13 @@ public function getInsertSQL(): string
14451434
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
14461435
}
14471436

1448-
$values[] = $placeholder;
1437+
$placeholders[] = $placeholder;
14491438
}
14501439

1451-
$columns = implode(', ', $columns);
1452-
$values = implode(', ', $values);
1453-
1454-
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
1440+
$columns = implode(', ', $columns);
1441+
$placeholders = implode(', ', $placeholders);
14551442

1456-
return $this->insertSql;
1443+
return sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $placeholders);
14571444
}
14581445

14591446
/**

src/Persisters/Entity/JoinedSubclassPersister.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public function executeInserts(): void
134134
// Execute all inserts. For each entity:
135135
// 1) Insert on root table
136136
// 2) Insert on sub tables
137-
foreach ($this->queuedInserts as $entity) {
137+
foreach ($this->queuedInserts as $key => $entity) {
138138
$insertData = $this->prepareInsertData($entity);
139139

140140
// Execute insert on root table
@@ -179,9 +179,16 @@ public function executeInserts(): void
179179
if ($this->class->requiresFetchAfterChange) {
180180
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
181181
}
182-
}
183182

184-
$this->queuedInserts = [];
183+
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
184+
// knows right away (for the next entity already) that the current entity has been written to the database
185+
// and no extra updates need to be scheduled to refer to it.
186+
//
187+
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
188+
// from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they
189+
// were given to our addInsert() method.
190+
unset($this->queuedInserts[$key]);
191+
}
185192
}
186193

187194
public function update(object $entity): void

0 commit comments

Comments
 (0)