IdentityMapTest.php 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <?php
  2. namespace Doctrine\Tests\ORM\Functional;
  3. use Doctrine\Tests\Models\CMS\CmsUser,
  4. Doctrine\Tests\Models\CMS\CmsAddress,
  5. Doctrine\Tests\Models\CMS\CmsPhonenumber,
  6. Doctrine\ORM\Query;
  7. require_once __DIR__ . '/../../TestInit.php';
  8. /**
  9. * IdentityMapTest
  10. *
  11. * Tests correct behavior and usage of the identity map. Local values and associations
  12. * that are already fetched always prevail, unless explicitly refreshed.
  13. *
  14. * @author Roman Borschel <roman@code-factory.org>
  15. */
  16. class IdentityMapTest extends \Doctrine\Tests\OrmFunctionalTestCase
  17. {
  18. protected function setUp() {
  19. $this->useModelSet('cms');
  20. parent::setUp();
  21. }
  22. public function testBasicIdentityManagement()
  23. {
  24. $user = new CmsUser;
  25. $user->status = 'dev';
  26. $user->username = 'romanb';
  27. $user->name = 'Roman B.';
  28. $address = new CmsAddress;
  29. $address->country = 'de';
  30. $address->zip = 1234;
  31. $address->city = 'Berlin';
  32. $user->setAddress($address);
  33. $this->_em->persist($user);
  34. $this->_em->flush();
  35. $this->_em->clear();
  36. $user2 = $this->_em->find(get_class($user), $user->getId());
  37. $this->assertTrue($user2 !== $user);
  38. $user3 = $this->_em->find(get_class($user), $user->getId());
  39. $this->assertTrue($user2 === $user3);
  40. $address2 = $this->_em->find(get_class($address), $address->getId());
  41. $this->assertTrue($address2 !== $address);
  42. $address3 = $this->_em->find(get_class($address), $address->getId());
  43. $this->assertTrue($address2 === $address3);
  44. $this->assertTrue($user2->getAddress() === $address2); // !!!
  45. }
  46. public function testSingleValuedAssociationIdentityMapBehaviorWithRefresh()
  47. {
  48. $address = new CmsAddress;
  49. $address->country = 'de';
  50. $address->zip = '12345';
  51. $address->city = 'Berlin';
  52. $user1 = new CmsUser;
  53. $user1->status = 'dev';
  54. $user1->username = 'romanb';
  55. $user1->name = 'Roman B.';
  56. $user2 = new CmsUser;
  57. $user2->status = 'dev';
  58. $user2->username = 'gblanco';
  59. $user2->name = 'Guilherme Blanco';
  60. $address->setUser($user1);
  61. $this->_em->persist($address);
  62. $this->_em->persist($user1);
  63. $this->_em->persist($user2);
  64. $this->_em->flush();
  65. $this->assertSame($user1, $address->user);
  66. //external update to CmsAddress
  67. $this->_em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', array($user2->getId()));
  68. // But we want to have this external change!
  69. // Solution 1: refresh(), broken atm!
  70. $this->_em->refresh($address);
  71. // Now the association should be "correct", referencing $user2
  72. $this->assertSame($user2, $address->user);
  73. $this->assertSame($user2->address, $address); // check back reference also
  74. // Attention! refreshes can result in broken bidirectional associations! this is currently expected!
  75. // $user1 still points to $address!
  76. $this->assertSame($user1->address, $address);
  77. }
  78. public function testSingleValuedAssociationIdentityMapBehaviorWithRefreshQuery()
  79. {
  80. $address = new CmsAddress;
  81. $address->country = 'de';
  82. $address->zip = '12345';
  83. $address->city = 'Berlin';
  84. $user1 = new CmsUser;
  85. $user1->status = 'dev';
  86. $user1->username = 'romanb';
  87. $user1->name = 'Roman B.';
  88. $user2 = new CmsUser;
  89. $user2->status = 'dev';
  90. $user2->username = 'gblanco';
  91. $user2->name = 'Guilherme Blanco';
  92. $address->setUser($user1);
  93. $this->_em->persist($address);
  94. $this->_em->persist($user1);
  95. $this->_em->persist($user2);
  96. $this->_em->flush();
  97. $this->assertSame($user1, $address->user);
  98. //external update to CmsAddress
  99. $this->_em->getConnection()->executeUpdate('update cms_addresses set user_id = ?', array($user2->getId()));
  100. //select
  101. $q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
  102. $address2 = $q->getSingleResult();
  103. $this->assertSame($address, $address2);
  104. // Should still be $user1
  105. $this->assertSame($user1, $address2->user);
  106. $this->assertTrue($user2->address === null);
  107. // But we want to have this external change!
  108. // Solution 2: Alternatively, a refresh query should work
  109. $q = $this->_em->createQuery('select a, u from Doctrine\Tests\Models\CMS\CmsAddress a join a.user u');
  110. $q->setHint(Query::HINT_REFRESH, true);
  111. $address3 = $q->getSingleResult();
  112. $this->assertSame($address, $address3); // should still be the same, always from identity map
  113. // Now the association should be "correct", referencing $user2
  114. $this->assertSame($user2, $address2->user);
  115. $this->assertSame($user2->address, $address2); // check back reference also
  116. // Attention! refreshes can result in broken bidirectional associations! this is currently expected!
  117. // $user1 still points to $address2!
  118. $this->assertSame($user1->address, $address2);
  119. }
  120. public function testCollectionValuedAssociationIdentityMapBehaviorWithRefreshQuery()
  121. {
  122. $user = new CmsUser;
  123. $user->status = 'dev';
  124. $user->username = 'romanb';
  125. $user->name = 'Roman B.';
  126. $phone1 = new CmsPhonenumber;
  127. $phone1->phonenumber = 123;
  128. $phone2 = new CmsPhonenumber;
  129. $phone2->phonenumber = 234;
  130. $phone3 = new CmsPhonenumber;
  131. $phone3->phonenumber = 345;
  132. $user->addPhonenumber($phone1);
  133. $user->addPhonenumber($phone2);
  134. $user->addPhonenumber($phone3);
  135. $this->_em->persist($user); // cascaded to phone numbers
  136. $this->_em->flush();
  137. $this->assertEquals(3, count($user->getPhonenumbers()));
  138. $this->assertFalse($user->getPhonenumbers()->isDirty());
  139. //external update to CmsAddress
  140. $this->_em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', array(999, $user->getId()));
  141. //select
  142. $q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
  143. $user2 = $q->getSingleResult();
  144. $this->assertSame($user, $user2);
  145. // Should still be the same 3 phonenumbers
  146. $this->assertEquals(3, count($user2->getPhonenumbers()));
  147. // But we want to have this external change!
  148. // Solution 1: refresh().
  149. //$this->_em->refresh($user2); broken atm!
  150. // Solution 2: Alternatively, a refresh query should work
  151. $q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
  152. $q->setHint(Query::HINT_REFRESH, true);
  153. $user3 = $q->getSingleResult();
  154. $this->assertSame($user, $user3); // should still be the same, always from identity map
  155. // Now the collection should be refreshed with correct count
  156. $this->assertEquals(4, count($user3->getPhonenumbers()));
  157. }
  158. public function testCollectionValuedAssociationIdentityMapBehaviorWithRefresh()
  159. {
  160. $user = new CmsUser;
  161. $user->status = 'dev';
  162. $user->username = 'romanb';
  163. $user->name = 'Roman B.';
  164. $phone1 = new CmsPhonenumber;
  165. $phone1->phonenumber = 123;
  166. $phone2 = new CmsPhonenumber;
  167. $phone2->phonenumber = 234;
  168. $phone3 = new CmsPhonenumber;
  169. $phone3->phonenumber = 345;
  170. $user->addPhonenumber($phone1);
  171. $user->addPhonenumber($phone2);
  172. $user->addPhonenumber($phone3);
  173. $this->_em->persist($user); // cascaded to phone numbers
  174. $this->_em->flush();
  175. $this->assertEquals(3, count($user->getPhonenumbers()));
  176. //external update to CmsAddress
  177. $this->_em->getConnection()->executeUpdate('insert into cms_phonenumbers (phonenumber, user_id) VALUES (?,?)', array(999, $user->getId()));
  178. //select
  179. $q = $this->_em->createQuery('select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p');
  180. $user2 = $q->getSingleResult();
  181. $this->assertSame($user, $user2);
  182. // Should still be the same 3 phonenumbers
  183. $this->assertEquals(3, count($user2->getPhonenumbers()));
  184. // But we want to have this external change!
  185. // Solution 1: refresh().
  186. $this->_em->refresh($user2);
  187. $this->assertSame($user, $user2); // should still be the same, always from identity map
  188. // Now the collection should be refreshed with correct count
  189. $this->assertEquals(4, count($user2->getPhonenumbers()));
  190. }
  191. public function testReusedSplObjectHashDoesNotConfuseUnitOfWork()
  192. {
  193. $hash1 = $this->subRoutine($this->_em);
  194. // Make sure cycles are collected NOW, because a PersistentCollection references
  195. // its owner, hence without forcing gc on cycles now the object will not (yet)
  196. // be garbage collected and thus the object hash is not reused.
  197. // This is not a memory leak!
  198. gc_collect_cycles();
  199. $user1 = new CmsUser;
  200. $user1->status = 'dev';
  201. $user1->username = 'jwage';
  202. $user1->name = 'Jonathan W.';
  203. $hash2 = spl_object_hash($user1);
  204. $this->assertEquals($hash1, $hash2); // Hash reused!
  205. $this->_em->persist($user1);
  206. $this->_em->flush();
  207. }
  208. private function subRoutine($em) {
  209. $user = new CmsUser;
  210. $user->status = 'dev';
  211. $user->username = 'romanb';
  212. $user->name = 'Roman B.';
  213. $em->persist($user);
  214. $em->flush();
  215. $em->remove($user);
  216. $em->flush();
  217. return spl_object_hash($user);
  218. }
  219. }