Skip to content

Commit e3c320c

Browse files
authored
Merge pull request doctrine#12124 from doctrine/3.5.x-merge-up-into-3.6.x_N9Rr16zf
Merge release 3.5.2 into 3.6.x
2 parents 831232e + 5a541b8 commit e3c320c

13 files changed

+473
-36
lines changed

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/ManyToManyOwningSideMapping.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, Na
127127
$mapping->joinTableColumns = [];
128128

129129
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
130+
$joinColumn->nullable = false;
131+
130132
if (empty($joinColumn->referencedColumnName)) {
131133
$joinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
132134
}
@@ -150,6 +152,8 @@ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, Na
150152
}
151153

152154
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
155+
$inverseJoinColumn->nullable = false;
156+
153157
if (empty($inverseJoinColumn->referencedColumnName)) {
154158
$inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
155159
}

src/Mapping/ToOneOwningSideMapping.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ public static function fromMappingArrayAndName(
130130
$uniqueConstraintColumns = [];
131131

132132
foreach ($mapping->joinColumns as $joinColumn) {
133+
if ($mapping->id) {
134+
$joinColumn->nullable = false;
135+
} elseif ($joinColumn->nullable === null) {
136+
$joinColumn->nullable = true;
137+
}
138+
133139
if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) {
134140
if (count($mapping->joinColumns) === 1) {
135141
if (empty($mapping->id)) {

src/Persisters/Entity/BasicEntityPersister.php

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

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

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

14191413
public function getInsertSQL(): string
14201414
{
1421-
if ($this->insertSql !== null) {
1422-
return $this->insertSql;
1423-
}
1424-
14251415
$columns = $this->getInsertColumnList();
14261416
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
14271417

1428-
if (empty($columns)) {
1429-
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
1430-
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
1418+
if ($columns === []) {
1419+
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
14311420

1432-
return $this->insertSql;
1421+
return $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
14331422
}
14341423

1435-
$values = [];
1436-
$columns = array_unique($columns);
1424+
$placeholders = [];
1425+
$columns = array_unique($columns);
14371426

14381427
foreach ($columns as $column) {
14391428
$placeholder = '?';
@@ -1447,15 +1436,13 @@ public function getInsertSQL(): string
14471436
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
14481437
}
14491438

1450-
$values[] = $placeholder;
1439+
$placeholders[] = $placeholder;
14511440
}
14521441

1453-
$columns = implode(', ', $columns);
1454-
$values = implode(', ', $values);
1455-
1456-
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
1442+
$columns = implode(', ', $columns);
1443+
$placeholders = implode(', ', $placeholders);
14571444

1458-
return $this->insertSql;
1445+
return sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $placeholders);
14591446
}
14601447

14611448
/**

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

src/UnitOfWork.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,9 @@ private function persistNew(ClassMetadata $class, object $entity): void
934934

935935
$this->entityStates[$oid] = self::STATE_MANAGED;
936936

937-
$this->scheduleForInsert($entity);
937+
if (! isset($this->entityInsertions[$oid])) {
938+
$this->scheduleForInsert($entity);
939+
}
938940
}
939941

940942
/** @param mixed[] $idValue */
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional;
6+
7+
use Doctrine\ORM\Event\PrePersistEventArgs;
8+
use Doctrine\ORM\Events;
9+
use Doctrine\ORM\Mapping\Column;
10+
use Doctrine\ORM\Mapping\Entity;
11+
use Doctrine\ORM\Mapping\GeneratedValue;
12+
use Doctrine\ORM\Mapping\Id;
13+
use Doctrine\ORM\Mapping\ManyToOne;
14+
use Doctrine\Tests\OrmFunctionalTestCase;
15+
16+
use function uniqid;
17+
18+
class PrePersistEventTest extends OrmFunctionalTestCase
19+
{
20+
protected function setUp(): void
21+
{
22+
parent::setUp();
23+
24+
$this->createSchemaForModels(
25+
EntityWithUnmappedEntity::class,
26+
EntityWithCascadeAssociation::class,
27+
);
28+
}
29+
30+
public function testCallingPersistInPrePersistHook(): void
31+
{
32+
$entityWithUnmapped = new EntityWithUnmappedEntity();
33+
$entityWithCascade = new EntityWithCascadeAssociation();
34+
35+
$entityWithUnmapped->unmapped = $entityWithCascade;
36+
$entityWithCascade->cascaded = $entityWithUnmapped;
37+
38+
$this->_em->getEventManager()->addEventListener(Events::prePersist, new PrePersistUnmappedPersistListener());
39+
$this->_em->persist($entityWithUnmapped);
40+
41+
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithCascade));
42+
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithUnmapped));
43+
}
44+
}
45+
46+
class PrePersistUnmappedPersistListener
47+
{
48+
public function prePersist(PrePersistEventArgs $args): void
49+
{
50+
$object = $args->getObject();
51+
52+
if ($object instanceof EntityWithUnmappedEntity) {
53+
$uow = $args->getObjectManager()->getUnitOfWork();
54+
55+
if ($object->unmapped && ! $uow->isInIdentityMap($object->unmapped) && ! $uow->isScheduledForInsert($object->unmapped)) {
56+
$args->getObjectManager()->persist($object->unmapped);
57+
}
58+
}
59+
}
60+
}
61+
62+
#[Entity]
63+
class EntityWithUnmappedEntity
64+
{
65+
#[Id]
66+
#[Column(type: 'string', length: 255)]
67+
#[GeneratedValue(strategy: 'NONE')]
68+
public string $id;
69+
70+
public EntityWithCascadeAssociation|null $unmapped = null;
71+
72+
public function __construct()
73+
{
74+
$this->id = uniqid(self::class, true);
75+
}
76+
}
77+
78+
#[Entity]
79+
class EntityWithCascadeAssociation
80+
{
81+
#[Id]
82+
#[Column(type: 'string', length: 255)]
83+
#[GeneratedValue(strategy: 'NONE')]
84+
public string $id;
85+
86+
#[ManyToOne(targetEntity: EntityWithUnmappedEntity::class, cascade: ['persist'])]
87+
public EntityWithUnmappedEntity|null $cascaded = null;
88+
89+
public function __construct()
90+
{
91+
$this->id = uniqid(self::class, true);
92+
}
93+
}

0 commit comments

Comments
 (0)