Skip to content

Commit a3ea953

Browse files
committed
Removing an entity that is contained in a m2m association fails when flush() is called twice
Here is a failing test for #10483. When an entity is removed that is part of a many-to-many collection, that collection will be updated during the `flush()` operation to no longer contain the entity. However, afterwards the collection will be in a dirty state and the internal snapshot still contains the removed entity. That causes the collection to be processed again when `flush()` is called another time. This time, the ORM assumes the entity has been removed from the collection. It will try to build a `DELETE` operation but fails since the entity (from the snapshot) is no longer in the identity map.
1 parent d6c0031 commit a3ea953

File tree

1 file changed

+114
-0
lines changed

1 file changed

+114
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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 GH10483Test extends OrmFunctionalTestCase
13+
{
14+
protected function setUp(): void
15+
{
16+
parent::setUp();
17+
18+
$this->createSchemaForModels(
19+
GH10483Role::class,
20+
GH10483User::class
21+
);
22+
}
23+
24+
public function testFlushChangedUserAfterRoleHasBeenDeleted(): void
25+
{
26+
$em = $this->_em;
27+
28+
$role = new GH10483Role();
29+
$role->name = 'test';
30+
$em->persist($role);
31+
32+
$user = new GH10483User();
33+
$user->name = 'test';
34+
$user->roles->add($role);
35+
$em->persist($user);
36+
37+
$em->flush();
38+
39+
self::assertFalse($user->roles->isDirty());
40+
41+
$em->remove($role);
42+
$em->flush();
43+
44+
// UnitOfWork::computeAssociationChanges(), lines 968 ff. will remove the removed entity from the collection:
45+
self::assertEmpty($user->roles);
46+
47+
// The UoW left the collection in a dirty state, is that correct?
48+
self::assertTrue($user->roles->isDirty()); // <-- might need to assert "false" (?)
49+
50+
// The collection's snapshot still contains the removed $role entity, is that correct?
51+
self::assertSame([$role], $user->roles->getSnapshot()); // <-- might need to assert snapshot being empty (?)
52+
53+
// Since the collection is dirty and/or has a snapshot that differs from the state,
54+
// this flush will try to remove the $role from the collection, and fails when looking for
55+
// it in the identity map
56+
$em->flush();
57+
}
58+
}
59+
60+
/**
61+
* @ORM\Entity
62+
*/
63+
class GH10483Role
64+
{
65+
/**
66+
* @ORM\Id
67+
* @ORM\GeneratedValue(strategy="AUTO")
68+
* @ORM\Column(type="integer")
69+
*
70+
* @var int
71+
*/
72+
public $id;
73+
74+
/**
75+
* @ORM\Column
76+
*
77+
* @var string
78+
*/
79+
public $name;
80+
}
81+
82+
/**
83+
* @ORM\Entity
84+
*/
85+
class GH10483User
86+
{
87+
/**
88+
* @ORM\Id
89+
* @ORM\GeneratedValue(strategy="AUTO")
90+
* @ORM\Column(type="integer")
91+
*
92+
* @var int
93+
*/
94+
public $id;
95+
96+
/**
97+
* @ORM\Column
98+
*
99+
* @var string
100+
*/
101+
public $name;
102+
103+
/**
104+
* @ORM\ManyToMany(targetEntity="GH10483Role")
105+
*
106+
* @var Collection
107+
*/
108+
public $roles;
109+
110+
public function __construct()
111+
{
112+
$this->roles = new ArrayCollection();
113+
}
114+
}

0 commit comments

Comments
 (0)