Skip to content

Commit 0ecf3fe

Browse files
committed
Add test to show why delete-before-insert may be challenging
There are a few requests (doctrine#5742, doctrine#5368, doctrine#5109, doctrine#6776) that ask to change the order of operations in the UnitOfWork to perform "deletes before inserts", or where such a switch appears to solve a reported problem. I don't want to say that this is not doable. But this PR at least adds two tricky examples where INSERTs need to be done before an UPDATE can refer to new database rows; and where the UPDATE needs to happen to release foreign key references to other entities before those can be DELETEd. So, at least as long as all operations of a certain type are to be executed in blocks, this example allows no other order of operations than the current one.
1 parent 7ef4afc commit 0ecf3fe

File tree

1 file changed

+150
-0
lines changed

1 file changed

+150
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket;
6+
7+
use Doctrine\Common\Collections\ArrayCollection;
8+
use Doctrine\Common\Collections\Collection;
9+
use Doctrine\ORM\Mapping as ORM;
10+
use Doctrine\Tests\OrmFunctionalTestCase;
11+
12+
class GH5742Test extends OrmFunctionalTestCase
13+
{
14+
protected function setUp(): void
15+
{
16+
parent::setUp();
17+
18+
$this->createSchemaForModels(
19+
GH5742Person::class,
20+
GH5742Toothbrush::class,
21+
GH5742ToothpasteBrand::class
22+
);
23+
}
24+
25+
public function testUpdateOneToOneToNewEntityBeforePreviousEntityCanBeRemoved(): void
26+
{
27+
$this->expectNotToPerformAssertions();
28+
29+
$person = new GH5742Person();
30+
$oldToothbrush = new GH5742Toothbrush();
31+
$person->toothbrush = $oldToothbrush;
32+
33+
$this->_em->persist($person);
34+
$this->_em->persist($oldToothbrush);
35+
$this->_em->flush();
36+
37+
$newToothbrush = new GH5742Toothbrush();
38+
$person->toothbrush = $newToothbrush;
39+
40+
$this->_em->remove($oldToothbrush);
41+
$this->_em->persist($newToothbrush);
42+
43+
// The flush operation will have to make sure the new toothbrush
44+
// has been written to the database
45+
// _before_ the person can be updated to refer to it.
46+
// Likewise, the update must have happened _before_ the old
47+
// toothbrush can be removed (non-nullable FK constraint).
48+
49+
$this->_em->flush();
50+
}
51+
52+
public function testManyToManyCollectionUpdateBeforeRemoval(): void
53+
{
54+
$this->expectNotToPerformAssertions();
55+
56+
$person = new GH5742Person();
57+
$person->toothbrush = new GH5742Toothbrush(); // to satisfy not-null constraint
58+
$this->_em->persist($person);
59+
60+
$oldMice = new GH5742ToothpasteBrand();
61+
$this->_em->persist($oldMice);
62+
63+
$person->preferredBrands->set(1, $oldMice);
64+
$this->_em->flush();
65+
66+
$newSpice = new GH5742ToothpasteBrand();
67+
$this->_em->persist($newSpice);
68+
69+
$person->preferredBrands->set(1, $newSpice);
70+
71+
$this->_em->remove($oldMice);
72+
73+
// The flush operation will have to make sure the new brand
74+
// has been written to the database _before_ it can be referred
75+
// to from the m2m join table.
76+
// Likewise, the old join table entry must have been removed
77+
// _before_ the old brand can be removed.
78+
79+
$this->_em->flush();
80+
}
81+
}
82+
83+
/**
84+
* @ORM\Entity
85+
*/
86+
class GH5742Person
87+
{
88+
/**
89+
* @ORM\Id
90+
* @ORM\GeneratedValue(strategy="AUTO")
91+
* @ORM\Column(type="integer")
92+
*
93+
* @var int
94+
*/
95+
public $id;
96+
97+
/**
98+
* @ORM\OneToOne(targetEntity="GH5742Toothbrush", cascade={"persist"})
99+
* @ORM\JoinColumn(nullable=false)
100+
*
101+
* @var GH5742Toothbrush
102+
*/
103+
public $toothbrush;
104+
105+
/**
106+
* @ORM\ManyToMany(targetEntity="GH5742ToothpasteBrand")
107+
* @ORM\JoinTable(name="gh5742person_gh5742toothpastebrand",
108+
* joinColumns={@ORM\JoinColumn(name="person_id", referencedColumnName="id", onDelete="CASCADE")},
109+
* inverseJoinColumns={@ORM\JoinColumn(name="brand_id", referencedColumnName="id")}
110+
* )
111+
*
112+
* @var Collection<GH5742ToothpasteBrand>
113+
*/
114+
public $preferredBrands;
115+
116+
public function __construct()
117+
{
118+
$this->preferredBrands = new ArrayCollection();
119+
}
120+
}
121+
122+
/**
123+
* @ORM\Entity
124+
*/
125+
class GH5742Toothbrush
126+
{
127+
/**
128+
* @ORM\Id
129+
* @ORM\GeneratedValue(strategy="AUTO")
130+
* @ORM\Column(type="integer")
131+
*
132+
* @var int
133+
*/
134+
public $id;
135+
}
136+
137+
/**
138+
* @ORM\Entity
139+
*/
140+
class GH5742ToothpasteBrand
141+
{
142+
/**
143+
* @ORM\Id
144+
* @ORM\GeneratedValue(strategy="AUTO")
145+
* @ORM\Column(type="integer")
146+
*
147+
* @var int
148+
*/
149+
public $id;
150+
}

0 commit comments

Comments
 (0)