3 namespace Doctrine\Tests\ORM\Functional;
5 use Doctrine\Tests\Models\CMS\CmsUser,
6 Doctrine\Tests\Models\CMS\CmsAddress,
7 Doctrine\Tests\Models\CMS\CmsPhonenumber,
10 require_once __DIR__ . '/../../TestInit.php';
15 * Tests correct behavior and usage of the identity map. Local values and associations
16 * that are already fetched always prevail, unless explicitly refreshed.
18 * @author Roman Borschel <roman@code-factory.org>
20 class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
22 protected function setUp() {
23 $this->useModelSet('cms');
27 public function testBasicIdentityManagement()
30 $user->status = 'dev';
31 $user->username = 'romanb';
32 $user->name = 'Roman B.';
34 $address = new CmsAddress;
35 $address->country = 'de';
37 $address->city = 'Berlin';
39 $user->setAddress($address);
41 $this->_em->persist($user);
45 $user2 = $this->_em->find(get_class($user), $user->getId());
46 $this->assertTrue($user2 !== $user);
47 $user3 = $this->_em->find(get_class($user), $user->getId());
48 $this->assertTrue($user2 === $user3);
50 $address2 = $this->_em->find(get_class($address), $address->getId());
51 $this->assertTrue($address2 !== $address);
52 $address3 = $this->_em->find(get_class($address), $address->getId());
53 $this->assertTrue($address2 === $address3);
55 $this->assertTrue($user2->getAddress() === $address2); // !!!
58 public function testSingleValuedAssociationIdentityMapBehaviorWithRefresh()
60 $address = new CmsAddress;
61 $address->country = 'de';
62 $address->zip = '12345';
63 $address->city = 'Berlin';
66 $user1->status = 'dev';
67 $user1->username = 'romanb';
68 $user1->name = 'Roman B.';
71 $user2->status = 'dev';
72 $user2->username = 'gblanco';
73 $user2->name = 'Guilherme Blanco';
75 $address->setUser($user1);
77 $this->_em->persist($address);
78 $this->_em->persist($user1);
79 $this->_em->persist($user2);
82 $this->assertSame($user1, $address->user);
84 //external update to CmsAddress
85 $this->_em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', array($user2->getId()));
87 // But we want to have this external change!
88 // Solution 1: refresh(), broken atm!
89 $this->_em->refresh($address);
91 // Now the association should be "correct", referencing $user2
92 $this->assertSame($user2, $address->user);
93 $this->assertSame($user2->address, $address); // check back reference also
95 // Attention! refreshes can result in broken bidirectional associations! this is currently expected!
96 // $user1 still points to $address!
97 $this->assertSame($user1->address, $address);
100 public function testSingleValuedAssociationIdentityMapBehaviorWithRefreshQuery()
102 $address = new CmsAddress;
103 $address->country = 'de';
104 $address->zip = '12345';
105 $address->city = 'Berlin';
107 $user1 = new CmsUser;
108 $user1->status = 'dev';
109 $user1->username = 'romanb';
110 $user1->name = 'Roman B.';
112 $user2 = new CmsUser;
113 $user2->status = 'dev';
114 $user2->username = 'gblanco';
115 $user2->name = 'Guilherme Blanco';
117 $address->setUser($user1);
119 $this->_em->persist($address);
120 $this->_em->persist($user1);
121 $this->_em->persist($user2);
125 $this->assertSame($user1, $address->user);
127 //external update to CmsAddress
128 $this->_em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', array($user2->getId()));
131 $q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
132 $address2 = $q->getSingleResult();
134 $this->assertSame($address, $address2);
136 // Should still be $user1
137 $this->assertSame($user1, $address2->user);
138 $this->assertTrue($user2->address === null);
140 // But we want to have this external change!
141 // Solution 2: Alternatively, a refresh query should work
142 $q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
143 $q->setHint(Query::HINT_REFRESH, true);
144 $address3 = $q->getSingleResult();
146 $this->assertSame($address, $address3); // should still be the same, always from identity map
148 // Now the association should be "correct", referencing $user2
149 $this->assertSame($user2, $address2->user);
150 $this->assertSame($user2->address, $address2); // check back reference also
152 // Attention! refreshes can result in broken bidirectional associations! this is currently expected!
153 // $user1 still points to $address2!
154 $this->assertSame($user1->address, $address2);
157 public function testCollectionValuedAssociationIdentityMapBehaviorWithRefreshQuery()
160 $user->status = 'dev';
161 $user->username = 'romanb';
162 $user->name = 'Roman B.';
164 $phone1 = new CmsPhonenumber;
165 $phone1->phonenumber = 123;
167 $phone2 = new CmsPhonenumber;
168 $phone2->phonenumber = 234;
170 $phone3 = new CmsPhonenumber;
171 $phone3->phonenumber = 345;
173 $user->addPhonenumber($phone1);
174 $user->addPhonenumber($phone2);
175 $user->addPhonenumber($phone3);
177 $this->_em->persist($user); // cascaded to phone numbers
180 $this->assertEquals(3, count($user->getPhonenumbers()));
181 $this->assertFalse($user->getPhonenumbers()->isDirty());
183 //external update to CmsAddress
184 $this->_em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', array(999, $user->getId()));
187 $q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
188 $user2 = $q->getSingleResult();
190 $this->assertSame($user, $user2);
192 // Should still be the same 3 phonenumbers
193 $this->assertEquals(3, count($user2->getPhonenumbers()));
195 // But we want to have this external change!
196 // Solution 1: refresh().
197 //$this->_em->refresh($user2); broken atm!
198 // Solution 2: Alternatively, a refresh query should work
199 $q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
200 $q->setHint(Query::HINT_REFRESH, true);
201 $user3 = $q->getSingleResult();
203 $this->assertSame($user, $user3); // should still be the same, always from identity map
205 // Now the collection should be refreshed with correct count
206 $this->assertEquals(4, count($user3->getPhonenumbers()));
209 public function testCollectionValuedAssociationIdentityMapBehaviorWithRefresh()
212 $user->status = 'dev';
213 $user->username = 'romanb';
214 $user->name = 'Roman B.';
216 $phone1 = new CmsPhonenumber;
217 $phone1->phonenumber = 123;
219 $phone2 = new CmsPhonenumber;
220 $phone2->phonenumber = 234;
222 $phone3 = new CmsPhonenumber;
223 $phone3->phonenumber = 345;
225 $user->addPhonenumber($phone1);
226 $user->addPhonenumber($phone2);
227 $user->addPhonenumber($phone3);
229 $this->_em->persist($user); // cascaded to phone numbers
232 $this->assertEquals(3, count($user->getPhonenumbers()));
234 //external update to CmsAddress
235 $this->_em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', array(999, $user->getId()));
238 $q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
239 $user2 = $q->getSingleResult();
241 $this->assertSame($user, $user2);
243 // Should still be the same 3 phonenumbers
244 $this->assertEquals(3, count($user2->getPhonenumbers()));
246 // But we want to have this external change!
247 // Solution 1: refresh().
248 $this->_em->refresh($user2);
250 $this->assertSame($user, $user2); // should still be the same, always from identity map
252 // Now the collection should be refreshed with correct count
253 $this->assertEquals(4, count($user2->getPhonenumbers()));
256 public function testReusedSplObjectHashDoesNotConfuseUnitOfWork()
258 $hash1 = $this->subRoutine($this->_em);
259 // Make sure cycles are collected NOW, because a PersistentCollection references
260 // its owner, hence without forcing gc on cycles now the object will not (yet)
261 // be garbage collected and thus the object hash is not reused.
262 // This is not a memory leak!
265 $user1 = new CmsUser;
266 $user1->status = 'dev';
267 $user1->username = 'jwage';
268 $user1->name = 'Jonathan W.';
269 $hash2 = spl_object_hash($user1);
270 $this->assertEquals($hash1, $hash2); // Hash reused!
271 $this->_em->persist($user1);
275 private function subRoutine($em) {
277 $user->status = 'dev';
278 $user->username = 'romanb';
279 $user->name = 'Roman B.';
285 return spl_object_hash($user);