Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f934b35

Browse files
committed
Add test: Commit order calculation must happen on the entity level
Add tests for entity insertion and deletion that require the commit order calculation to happen on the entity level. This demonstrates the necessity for the changes in #10547. This PR contains two tests with carefully constructed entity relationships, where we have a non-nullable `parent` foreign key relationships between entities stored in the same table. Class diagram: ```mermaid classDiagram direction LR class A class B A --> B : parent B --|> A ``` Object graph: ```mermaid graph LR; b1 --> b2; b2 --> a; b3 --> b2; ``` #### Situation before #10547 The commit order is computed by looking at the associations at the _table_ (= _class_) level. Once the ordering of _tables_ has been found, entities being mapped to the same table will be processed in the order they were given to `persist()` or `remove()`. That means only a particular ordering of `persist()` or `remove()` calls (see comment in the test) works: For inserts, the order must be `$a, $b2, $b1, $b3` (or `... $b3, $b1`), for deletions `$b1, $b3, $b2, $a`. #### Situation with entity-level commit order computation (as in #10547) The ORM computes the commit order by considering associations at the _entity_ level. It will be able to find a working order by itself.
1 parent 330c0bc commit f934b35

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
use Doctrine\Tests\OrmFunctionalTestCase;
9+
10+
class GH10531Test extends OrmFunctionalTestCase
11+
{
12+
protected function setUp(): void
13+
{
14+
parent::setUp();
15+
16+
$this->createSchemaForModels(
17+
GH10531A::class,
18+
GH10531B::class
19+
);
20+
}
21+
22+
public function tearDown(): void
23+
{
24+
$conn = static::$sharedConn;
25+
$conn->executeStatement('DELETE FROM gh10531_b');
26+
$conn->executeStatement('DELETE FROM gh10531_a');
27+
}
28+
29+
public function testInserts(): void
30+
{
31+
$a = new GH10531A();
32+
$b1 = new GH10531B();
33+
$b2 = new GH10531B();
34+
$b3 = new GH10531B();
35+
36+
$b1->parent = $b2;
37+
$b3->parent = $b2;
38+
$b2->parent = $a;
39+
40+
/*
41+
* The following would force a working commit order, but that's not what
42+
* we want (the ORM shall sort this out internally).
43+
*
44+
* $this->_em->persist($a);
45+
* $this->_em->persist($b2);
46+
* $this->_em->flush();
47+
* $this->_em->persist($b1);
48+
* $this->_em->persist($b3);
49+
* $this->_em->flush();
50+
*/
51+
52+
// Pass $b2 to persist() between $b1 and $b3, so that any potential reliance upon the
53+
// order of persist() calls is spotted: No matter if it is in the order that persist()
54+
// was called or the other way round, in both cases there is an entity that will come
55+
// "before" $b2 but depend on its primary key, so the ORM must re-order the inserts.
56+
57+
$this->_em->persist($a);
58+
$this->_em->persist($b1);
59+
$this->_em->persist($b2);
60+
$this->_em->persist($b3);
61+
$this->_em->flush();
62+
63+
self::assertNotNull($a->id);
64+
self::assertNotNull($b1->id);
65+
self::assertNotNull($b2->id);
66+
self::assertNotNull($b3->id);
67+
}
68+
69+
public function testDeletes(): void
70+
{
71+
$this->expectNotToPerformAssertions();
72+
$con = $this->_em->getConnection();
73+
74+
// The "a" entity
75+
$con->insert('gh10531_a', ['id' => 1, 'discr' => 'A']);
76+
$a = $this->_em->find(GH10531A::class, 1);
77+
78+
// The "b2" entity
79+
$con->insert('gh10531_a', ['id' => 2, 'discr' => 'B']);
80+
$con->insert('gh10531_b', ['id' => 2, 'parent_id' => 1]);
81+
$b2 = $this->_em->find(GH10531B::class, 2);
82+
83+
// The "b1" entity
84+
$con->insert('gh10531_a', ['id' => 3, 'discr' => 'B']);
85+
$con->insert('gh10531_b', ['id' => 3, 'parent_id' => 2]);
86+
$b1 = $this->_em->find(GH10531B::class, 3);
87+
88+
// The "b3" entity
89+
$con->insert('gh10531_a', ['id' => 4, 'discr' => 'B']);
90+
$con->insert('gh10531_b', ['id' => 4, 'parent_id' => 2]);
91+
$b3 = $this->_em->find(GH10531B::class, 4);
92+
93+
/*
94+
* The following would make the deletions happen in an order
95+
* where the not-nullable foreign key constraints would not be
96+
* violated. But, we want the ORM to be able to sort this out
97+
* internally.
98+
*
99+
* $this->_em->remove($b1);
100+
* $this->_em->remove($b3);
101+
* $this->_em->remove($b2);
102+
*/
103+
104+
// As before, put $b2 in between $b1 and $b3 so that the order of the
105+
// remove() calls alone (in either direction) does not solve the problem.
106+
// The ORM will have to sort $b2 to be deleted last, after $b1 and $b3.
107+
$this->_em->remove($b1);
108+
$this->_em->remove($b2);
109+
$this->_em->remove($b3);
110+
111+
$this->_em->flush();
112+
}
113+
}
114+
115+
/**
116+
* @ORM\Entity
117+
* @ORM\Table(name="gh10531_a")
118+
* @ORM\DiscriminatorColumn(name="discr", type="string")
119+
* @ORM\DiscriminatorMap({ "A": "GH10531A", "B": "GH10531B" })
120+
* @ORM\InheritanceType("JOINED", )
121+
*
122+
* We are using JTI here, since STI would relax the not-nullable constraint for the "parent"
123+
* column (it has to be NULL when the row contains a GH10531A instance). Causes another error,
124+
* but not the constraint violation I'd like to point out.
125+
*/
126+
class GH10531A
127+
{
128+
/**
129+
* @ORM\Id
130+
* @ORM\GeneratedValue(strategy="AUTO")
131+
* @ORM\Column(type="integer")
132+
*
133+
* @var int
134+
*/
135+
public $id;
136+
}
137+
138+
/**
139+
* @ORM\Entity
140+
* @ORM\Table(name="gh10531_b")
141+
*/
142+
class GH10531B extends GH10531A
143+
{
144+
/**
145+
* @ORM\ManyToOne(targetEntity="GH10531A")
146+
* @ORM\JoinColumn(nullable=false, name="parent_id")
147+
*
148+
* @var GH10531A
149+
*/
150+
public $parent;
151+
}

0 commit comments

Comments
 (0)