Skip to content

Commit 3c8325d

Browse files
committed
Fix #7877
1 parent d528f70 commit 3c8325d

File tree

6 files changed

+222
-3
lines changed

6 files changed

+222
-3
lines changed

lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php

+21-3
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,8 @@ protected function prepareUpdateData($entity)
639639
: null;
640640

641641
// @todo guilhermeblanco This should check column insertability/updateability instead of field changeset
642-
foreach ($uow->getEntityChangeSet($entity) as $propertyName => $propertyChangeSet) {
642+
$entityChangeSet = $uow->getEntityChangeSet($entity);
643+
foreach ($entityChangeSet as $propertyName => $propertyChangeSet) {
643644
if ($versionPropertyName === $propertyName) {
644645
continue;
645646
}
@@ -665,9 +666,26 @@ protected function prepareUpdateData($entity)
665666
// set $newVal = null, in order to insert a null value and schedule an
666667
// extra update on the UnitOfWork.
667668
if ($newValue !== null && $uow->isScheduledForInsert($newValue)) {
668-
$uow->scheduleExtraUpdate($entity, [$propertyName => [null, $newValue]]);
669+
// This is only required if the associated entity is different from the current one,
670+
// and if the current entity relies on the database to generate its id
671+
if ($newValue !== $entity) {
672+
$scheduleExtraUpdate = true;
673+
} else {
674+
$identifiers = $this->class->getIdentifier();
675+
// Only single-column identifiers are supported
676+
if (1 === count($identifiers) && isset($entityChangeSet[$identifiers[0]])) {
677+
// Extra update is required if the current entity does not have yet a value for its identifier
678+
$scheduleExtraUpdate = ($entityChangeSet[$identifiers[0]][1] === null);
679+
} else {
680+
$scheduleExtraUpdate = true;
681+
}
682+
}
683+
684+
if ($scheduleExtraUpdate) {
685+
$uow->scheduleExtraUpdate($entity, [$propertyName => [null, $newValue]]);
686+
$newValue = null;
687+
}
669688

670-
$newValue = null;
671689
}
672690

673691
$targetClass = $this->em->getClassMetadata($property->getTargetEntity());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\Issue7877;
6+
7+
use Doctrine\ORM\Annotation as ORM;
8+
9+
/**
10+
* @ORM\Entity
11+
*/
12+
class Issue7877ApplicationGenerated implements Issue7877Interface
13+
{
14+
use Issue7877Trait;
15+
16+
public function __construct(int $id)
17+
{
18+
$this->id = $id;
19+
}
20+
21+
/**
22+
* @ORM\Id
23+
* @ORM\Column(type="integer")
24+
* @ORM\GeneratedValue(strategy="NONE")
25+
*/
26+
public $id;
27+
28+
/**
29+
* @var self
30+
* @ORM\ManyToOne(targetEntity="Doctrine\Tests\Models\Issue7877\Issue7877ApplicationGenerated")
31+
*/
32+
public $parent;
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\Issue7877;
6+
7+
use Doctrine\ORM\Annotation as ORM;
8+
9+
/**
10+
* @ORM\Entity
11+
*/
12+
class Issue7877DatabaseGenerated implements Issue7877Interface
13+
{
14+
use Issue7877Trait;
15+
16+
/**
17+
* @ORM\Id
18+
* @ORM\Column(type="integer")
19+
* @ORM\GeneratedValue(strategy="AUTO")
20+
*/
21+
public $id;
22+
23+
/**
24+
* @var self
25+
* @ORM\ManyToOne(targetEntity="Doctrine\Tests\Models\Issue7877\Issue7877DatabaseGenerated")
26+
*/
27+
public $parent;
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\Issue7877;
6+
7+
interface Issue7877Interface
8+
{
9+
public function getId(): ?int;
10+
public function setId(int $id);
11+
12+
public function getParent(): ?self;
13+
public function setParent(self $parent);
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\Issue7877;
6+
7+
trait Issue7877Trait
8+
{
9+
public $id;
10+
11+
public function getId(): ?int
12+
{
13+
return $this->id;
14+
}
15+
16+
public function setId(int $id)
17+
{
18+
$this->id = $id;
19+
}
20+
21+
public $parent;
22+
23+
public function getParent(): ?Issue7877Interface
24+
{
25+
return $this->parent;
26+
}
27+
28+
public function setParent(Issue7877Interface $parent)
29+
{
30+
$this->parent = $parent;
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional;
6+
7+
use Doctrine\Tests\Models\Issue7877\Issue7877ApplicationGenerated;
8+
use Doctrine\Tests\Models\Issue7877\Issue7877DatabaseGenerated;
9+
use Doctrine\Tests\Models\Issue7877\Issue7877Interface;
10+
use Doctrine\Tests\OrmFunctionalTestCase;
11+
12+
class SelfReferencingTest extends OrmFunctionalTestCase
13+
{
14+
protected function setUp() : void
15+
{
16+
parent::setUp();
17+
try {
18+
$classMetadatas = [
19+
$this->em->getClassMetadata(Issue7877ApplicationGenerated::class),
20+
$this->em->getClassMetadata(Issue7877DatabaseGenerated::class),
21+
];
22+
// We first drop the schema to avoid collision between tests
23+
$this->schemaTool->dropSchema($classMetadatas);
24+
$this->schemaTool->createSchema($classMetadatas);
25+
} catch (Exception $e) {
26+
}
27+
}
28+
29+
public function providerDifferentEntity()
30+
{
31+
yield [Issue7877ApplicationGenerated::class];
32+
yield [Issue7877DatabaseGenerated::class];
33+
}
34+
35+
/**
36+
* @dataProvider providerDifferentEntity
37+
*/
38+
public function testDifferentEntity(string $class)
39+
{
40+
$count = count($this->sqlLoggerStack->queries);
41+
42+
/** @var Issue7877Interface $parent */
43+
$parent = new $class($parentId = 1);
44+
$this->em->persist($parent);
45+
46+
/** @var Issue7877Interface $child */
47+
$child = new $class($childId = 2);
48+
$child->setParent($parent);
49+
$this->em->persist($child);
50+
51+
$this->em->flush();
52+
$this->assertCount($count + 4, $this->sqlLoggerStack->queries);
53+
54+
$this->em->clear();
55+
56+
$child = $this->em->find($class, $childId);
57+
$this->assertSame($parentId, $child->getParent()->getId());
58+
}
59+
60+
public function testSameEntityApplicationGenerated()
61+
{
62+
$count = count($this->sqlLoggerStack->queries);
63+
64+
$entity = new Issue7877ApplicationGenerated($entityId = 1);
65+
$entity->setParent($entity);
66+
$this->em->persist($entity);
67+
68+
$this->em->flush();
69+
$this->assertCount($count + 3, $this->sqlLoggerStack->queries);
70+
71+
$this->em->clear();
72+
73+
$child = $this->em->find(Issue7877ApplicationGenerated::class, $entityId);
74+
$this->assertSame($entityId, $child->getParent()->getId());
75+
}
76+
77+
public function testSameEntityDatabaseGenerated()
78+
{
79+
$count = count($this->sqlLoggerStack->queries);
80+
81+
$entity = new Issue7877DatabaseGenerated();
82+
$entity->setParent($entity);
83+
$this->em->persist($entity);
84+
85+
$this->em->flush();
86+
$this->assertCount($count + 4, $this->sqlLoggerStack->queries);
87+
$entityId = $entity->getId();
88+
89+
$this->em->clear();
90+
91+
$child = $this->em->find(Issue7877DatabaseGenerated::class, $entityId);
92+
$this->assertSame($entityId, $child->getParent()->getId());
93+
}
94+
}

0 commit comments

Comments
 (0)