1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\Tests\ORM\Functional; |
6
|
|
|
|
7
|
|
|
use Doctrine\ORM\Query; |
8
|
|
|
use Doctrine\Tests\Models\CMS\CmsAddress; |
9
|
|
|
use Doctrine\Tests\Models\CMS\CmsPhonenumber; |
10
|
|
|
use Doctrine\Tests\Models\CMS\CmsUser; |
11
|
|
|
use Doctrine\Tests\OrmFunctionalTestCase; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* IdentityMapTest |
15
|
|
|
* |
16
|
|
|
* Tests correct behavior and usage of the identity map. Local values and associations |
17
|
|
|
* that are already fetched always prevail, unless explicitly refreshed. |
18
|
|
|
* |
19
|
|
|
* @author Roman Borschel <[email protected]> |
20
|
|
|
*/ |
21
|
|
|
class IdentityMapTest extends OrmFunctionalTestCase |
22
|
|
|
{ |
23
|
|
|
protected function setUp() |
24
|
|
|
{ |
25
|
|
|
$this->useModelSet('cms'); |
26
|
|
|
parent::setUp(); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
public function testBasicIdentityManagement() |
30
|
|
|
{ |
31
|
|
|
$user = new CmsUser; |
32
|
|
|
$user->status = 'dev'; |
33
|
|
|
$user->username = 'romanb'; |
34
|
|
|
$user->name = 'Roman B.'; |
35
|
|
|
|
36
|
|
|
$address = new CmsAddress; |
37
|
|
|
$address->country = 'de'; |
38
|
|
|
$address->zip = 1234; |
39
|
|
|
$address->city = 'Berlin'; |
40
|
|
|
|
41
|
|
|
$user->setAddress($address); |
42
|
|
|
|
43
|
|
|
$this->em->persist($user); |
44
|
|
|
$this->em->flush(); |
45
|
|
|
$this->em->clear(); |
46
|
|
|
|
47
|
|
|
$user2 = $this->em->find(get_class($user), $user->getId()); |
48
|
|
|
self::assertNotSame($user2, $user); |
49
|
|
|
$user3 = $this->em->find(get_class($user), $user->getId()); |
50
|
|
|
self::assertSame($user2, $user3); |
51
|
|
|
|
52
|
|
|
$address2 = $this->em->find(get_class($address), $address->getId()); |
53
|
|
|
self::assertNotSame($address2, $address); |
54
|
|
|
$address3 = $this->em->find(get_class($address), $address->getId()); |
55
|
|
|
self::assertSame($address2, $address3); |
56
|
|
|
|
57
|
|
|
self::assertSame($user2->getAddress(), $address2); // !!! |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
public function testSingleValuedAssociationIdentityMapBehaviorWithRefresh() |
61
|
|
|
{ |
62
|
|
|
$address = new CmsAddress; |
63
|
|
|
$address->country = 'de'; |
64
|
|
|
$address->zip = '12345'; |
65
|
|
|
$address->city = 'Berlin'; |
66
|
|
|
|
67
|
|
|
$user1 = new CmsUser; |
68
|
|
|
$user1->status = 'dev'; |
69
|
|
|
$user1->username = 'romanb'; |
70
|
|
|
$user1->name = 'Roman B.'; |
71
|
|
|
|
72
|
|
|
$user2 = new CmsUser; |
73
|
|
|
$user2->status = 'dev'; |
74
|
|
|
$user2->username = 'gblanco'; |
75
|
|
|
$user2->name = 'Guilherme Blanco'; |
76
|
|
|
|
77
|
|
|
$address->setUser($user1); |
78
|
|
|
|
79
|
|
|
$this->em->persist($address); |
80
|
|
|
$this->em->persist($user1); |
81
|
|
|
$this->em->persist($user2); |
82
|
|
|
$this->em->flush(); |
83
|
|
|
|
84
|
|
|
self::assertSame($user1, $address->user); |
85
|
|
|
|
86
|
|
|
//external update to CmsAddress |
87
|
|
|
$this->em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', [$user2->getId()]); |
88
|
|
|
|
89
|
|
|
// But we want to have this external change! |
90
|
|
|
// Solution 1: refresh(), broken atm! |
|
|
|
|
91
|
|
|
$this->em->refresh($address); |
92
|
|
|
|
93
|
|
|
// Now the association should be "correct", referencing $user2 |
94
|
|
|
self::assertSame($user2, $address->user); |
95
|
|
|
self::assertSame($user2->address, $address); // check back reference also |
96
|
|
|
|
97
|
|
|
// Attention! refreshes can result in broken bidirectional associations! this is currently expected! |
98
|
|
|
// $user1 still points to $address! |
99
|
|
|
self::assertSame($user1->address, $address); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
public function testSingleValuedAssociationIdentityMapBehaviorWithRefreshQuery() |
103
|
|
|
{ |
104
|
|
|
$address = new CmsAddress; |
105
|
|
|
$address->country = 'de'; |
106
|
|
|
$address->zip = '12345'; |
107
|
|
|
$address->city = 'Berlin'; |
108
|
|
|
|
109
|
|
|
$user1 = new CmsUser; |
110
|
|
|
$user1->status = 'dev'; |
111
|
|
|
$user1->username = 'romanb'; |
112
|
|
|
$user1->name = 'Roman B.'; |
113
|
|
|
|
114
|
|
|
$user2 = new CmsUser; |
115
|
|
|
$user2->status = 'dev'; |
116
|
|
|
$user2->username = 'gblanco'; |
117
|
|
|
$user2->name = 'Guilherme Blanco'; |
118
|
|
|
|
119
|
|
|
$address->setUser($user1); |
120
|
|
|
|
121
|
|
|
$this->em->persist($address); |
122
|
|
|
$this->em->persist($user1); |
123
|
|
|
$this->em->persist($user2); |
124
|
|
|
$this->em->flush(); |
125
|
|
|
|
126
|
|
|
|
127
|
|
|
self::assertSame($user1, $address->user); |
128
|
|
|
|
129
|
|
|
//external update to CmsAddress |
130
|
|
|
$this->em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', [$user2->getId()]); |
131
|
|
|
|
132
|
|
|
//select |
133
|
|
|
$q = $this->em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u'); |
134
|
|
|
$address2 = $q->getSingleResult(); |
135
|
|
|
|
136
|
|
|
self::assertSame($address, $address2); |
137
|
|
|
|
138
|
|
|
// Should still be $user1 |
139
|
|
|
self::assertSame($user1, $address2->user); |
140
|
|
|
self::assertNull($user2->address); |
141
|
|
|
|
142
|
|
|
// But we want to have this external change! |
143
|
|
|
// Solution 2: Alternatively, a refresh query should work |
144
|
|
|
$q = $this->em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u'); |
145
|
|
|
$q->setHint(Query::HINT_REFRESH, true); |
146
|
|
|
$address3 = $q->getSingleResult(); |
147
|
|
|
|
148
|
|
|
self::assertSame($address, $address3); // should still be the same, always from identity map |
149
|
|
|
|
150
|
|
|
// Now the association should be "correct", referencing $user2 |
151
|
|
|
self::assertSame($user2, $address2->user); |
152
|
|
|
self::assertSame($user2->address, $address2); // check back reference also |
153
|
|
|
|
154
|
|
|
// Attention! refreshes can result in broken bidirectional associations! this is currently expected! |
155
|
|
|
// $user1 still points to $address2! |
156
|
|
|
self::assertSame($user1->address, $address2); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
public function testCollectionValuedAssociationIdentityMapBehaviorWithRefreshQuery() |
160
|
|
|
{ |
161
|
|
|
$user = new CmsUser; |
162
|
|
|
$user->status = 'dev'; |
163
|
|
|
$user->username = 'romanb'; |
164
|
|
|
$user->name = 'Roman B.'; |
165
|
|
|
|
166
|
|
|
$phone1 = new CmsPhonenumber; |
167
|
|
|
$phone1->phonenumber = 123; |
168
|
|
|
|
169
|
|
|
$phone2 = new CmsPhonenumber; |
170
|
|
|
$phone2->phonenumber = 234; |
171
|
|
|
|
172
|
|
|
$phone3 = new CmsPhonenumber; |
173
|
|
|
$phone3->phonenumber = 345; |
174
|
|
|
|
175
|
|
|
$user->addPhonenumber($phone1); |
176
|
|
|
$user->addPhonenumber($phone2); |
177
|
|
|
$user->addPhonenumber($phone3); |
178
|
|
|
|
179
|
|
|
$this->em->persist($user); // cascaded to phone numbers |
180
|
|
|
$this->em->flush(); |
181
|
|
|
|
182
|
|
|
self::assertCount(3, $user->getPhonenumbers()); |
183
|
|
|
self::assertFalse($user->getPhonenumbers()->isDirty()); |
|
|
|
|
184
|
|
|
|
185
|
|
|
//external update to CmsAddress |
186
|
|
|
$this->em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', [999, $user->getId()] |
187
|
|
|
); |
188
|
|
|
|
189
|
|
|
//select |
190
|
|
|
$q = $this->em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p'); |
191
|
|
|
$user2 = $q->getSingleResult(); |
192
|
|
|
|
193
|
|
|
self::assertSame($user, $user2); |
194
|
|
|
|
195
|
|
|
// Should still be the same 3 phonenumbers |
196
|
|
|
self::assertCount(3, $user2->getPhonenumbers()); |
197
|
|
|
|
198
|
|
|
// But we want to have this external change! |
199
|
|
|
// Solution 1: refresh(). |
|
|
|
|
200
|
|
|
//$this->em->refresh($user2); broken atm! |
|
|
|
|
201
|
|
|
// Solution 2: Alternatively, a refresh query should work |
202
|
|
|
$q = $this->em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p'); |
203
|
|
|
$q->setHint(Query::HINT_REFRESH, true); |
204
|
|
|
$user3 = $q->getSingleResult(); |
205
|
|
|
|
206
|
|
|
self::assertSame($user, $user3); // should still be the same, always from identity map |
207
|
|
|
|
208
|
|
|
// Now the collection should be refreshed with correct count |
209
|
|
|
self::assertCount(4, $user3->getPhonenumbers()); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
public function testCollectionValuedAssociationIdentityMapBehaviorWithRefresh() |
213
|
|
|
{ |
214
|
|
|
$user = new CmsUser; |
215
|
|
|
$user->status = 'dev'; |
216
|
|
|
$user->username = 'romanb'; |
217
|
|
|
$user->name = 'Roman B.'; |
218
|
|
|
|
219
|
|
|
$phone1 = new CmsPhonenumber; |
220
|
|
|
$phone1->phonenumber = 123; |
221
|
|
|
|
222
|
|
|
$phone2 = new CmsPhonenumber; |
223
|
|
|
$phone2->phonenumber = 234; |
224
|
|
|
|
225
|
|
|
$phone3 = new CmsPhonenumber; |
226
|
|
|
$phone3->phonenumber = 345; |
227
|
|
|
|
228
|
|
|
$user->addPhonenumber($phone1); |
229
|
|
|
$user->addPhonenumber($phone2); |
230
|
|
|
$user->addPhonenumber($phone3); |
231
|
|
|
|
232
|
|
|
$this->em->persist($user); // cascaded to phone numbers |
233
|
|
|
$this->em->flush(); |
234
|
|
|
|
235
|
|
|
self::assertCount(3, $user->getPhonenumbers()); |
236
|
|
|
|
237
|
|
|
//external update to CmsAddress |
238
|
|
|
$this->em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', [999, $user->getId()] |
239
|
|
|
); |
240
|
|
|
|
241
|
|
|
//select |
242
|
|
|
$q = $this->em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p'); |
243
|
|
|
$user2 = $q->getSingleResult(); |
244
|
|
|
|
245
|
|
|
self::assertSame($user, $user2); |
246
|
|
|
|
247
|
|
|
// Should still be the same 3 phonenumbers |
248
|
|
|
self::assertCount(3, $user2->getPhonenumbers()); |
249
|
|
|
|
250
|
|
|
// But we want to have this external change! |
251
|
|
|
// Solution 1: refresh(). |
|
|
|
|
252
|
|
|
$this->em->refresh($user2); |
253
|
|
|
|
254
|
|
|
self::assertSame($user, $user2); // should still be the same, always from identity map |
255
|
|
|
|
256
|
|
|
// Now the collection should be refreshed with correct count |
257
|
|
|
self::assertCount(4, $user2->getPhonenumbers()); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.