CounterCacheBehaviorTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM\Behavior;
  16. use Cake\Database\Query;
  17. use Cake\Datasource\ConnectionManager;
  18. use Cake\Datasource\EntityInterface;
  19. use Cake\Event\Event;
  20. use Cake\ORM\Entity;
  21. use Cake\ORM\Table;
  22. use Cake\TestSuite\TestCase;
  23. /**
  24. * Used for testing counter cache with custom finder
  25. */
  26. class PostTable extends Table
  27. {
  28. public function findPublished(Query $query, array $options)
  29. {
  30. return $query->where(['published' => true]);
  31. }
  32. }
  33. /**
  34. * CounterCacheBehavior test case
  35. */
  36. class CounterCacheBehaviorTest extends TestCase
  37. {
  38. /**
  39. * Fixture
  40. *
  41. * @var array
  42. */
  43. public $fixtures = [
  44. 'core.CounterCacheCategories',
  45. 'core.CounterCachePosts',
  46. 'core.CounterCacheComments',
  47. 'core.CounterCacheUsers',
  48. 'core.CounterCacheUserCategoryPosts',
  49. ];
  50. /**
  51. * setup
  52. *
  53. * @return void
  54. */
  55. public function setUp()
  56. {
  57. parent::setUp();
  58. $this->connection = ConnectionManager::get('test');
  59. $this->user = $this->getTableLocator()->get('Users', [
  60. 'table' => 'counter_cache_users',
  61. 'connection' => $this->connection,
  62. ]);
  63. $this->category = $this->getTableLocator()->get('Categories', [
  64. 'table' => 'counter_cache_categories',
  65. 'connection' => $this->connection,
  66. ]);
  67. $this->comment = $this->getTableLocator()->get('Comments', [
  68. 'alias' => 'Comment',
  69. 'table' => 'counter_cache_comments',
  70. 'connection' => $this->connection,
  71. ]);
  72. $this->post = new PostTable([
  73. 'alias' => 'Post',
  74. 'table' => 'counter_cache_posts',
  75. 'connection' => $this->connection,
  76. ]);
  77. $this->userCategoryPosts = new Table([
  78. 'alias' => 'UserCategoryPosts',
  79. 'table' => 'counter_cache_user_category_posts',
  80. 'connection' => $this->connection,
  81. ]);
  82. }
  83. /**
  84. * teardown
  85. *
  86. * @return void
  87. */
  88. public function tearDown()
  89. {
  90. parent::tearDown();
  91. unset($this->user, $this->post);
  92. }
  93. /**
  94. * Testing simple counter caching when adding a record
  95. *
  96. * @return void
  97. */
  98. public function testAdd()
  99. {
  100. $this->post->belongsTo('Users');
  101. $this->post->addBehavior('CounterCache', [
  102. 'Users' => [
  103. 'post_count',
  104. ],
  105. ]);
  106. $before = $this->_getUser();
  107. $entity = $this->_getEntity();
  108. $this->post->save($entity);
  109. $after = $this->_getUser();
  110. $this->assertEquals(2, $before->get('post_count'));
  111. $this->assertEquals(3, $after->get('post_count'));
  112. }
  113. /**
  114. * Testing simple counter caching when adding a record
  115. *
  116. * @return void
  117. */
  118. public function testAddIgnore()
  119. {
  120. $this->post->belongsTo('Users');
  121. $this->post->addBehavior('CounterCache', [
  122. 'Users' => [
  123. 'post_count',
  124. ],
  125. ]);
  126. $before = $this->_getUser();
  127. $entity = $this->_getEntity();
  128. $this->post->save($entity, ['ignoreCounterCache' => true]);
  129. $after = $this->_getUser();
  130. $this->assertEquals(2, $before->get('post_count'));
  131. $this->assertEquals(2, $after->get('post_count'));
  132. }
  133. /**
  134. * Testing simple counter caching when adding a record
  135. *
  136. * @return void
  137. */
  138. public function testAddScope()
  139. {
  140. $this->post->belongsTo('Users');
  141. $this->post->addBehavior('CounterCache', [
  142. 'Users' => [
  143. 'posts_published' => [
  144. 'conditions' => [
  145. 'published' => true,
  146. ],
  147. ],
  148. ],
  149. ]);
  150. $before = $this->_getUser();
  151. $entity = $this->_getEntity()->set('published', true);
  152. $this->post->save($entity);
  153. $after = $this->_getUser();
  154. $this->assertEquals(1, $before->get('posts_published'));
  155. $this->assertEquals(2, $after->get('posts_published'));
  156. }
  157. /**
  158. * Testing simple counter caching when deleting a record
  159. *
  160. * @return void
  161. */
  162. public function testDelete()
  163. {
  164. $this->post->belongsTo('Users');
  165. $this->post->addBehavior('CounterCache', [
  166. 'Users' => [
  167. 'post_count',
  168. ],
  169. ]);
  170. $before = $this->_getUser();
  171. $post = $this->post->find('all')->first();
  172. $this->post->delete($post);
  173. $after = $this->_getUser();
  174. $this->assertEquals(2, $before->get('post_count'));
  175. $this->assertEquals(1, $after->get('post_count'));
  176. }
  177. /**
  178. * Testing simple counter caching when deleting a record
  179. *
  180. * @return void
  181. */
  182. public function testDeleteIgnore()
  183. {
  184. $this->post->belongsTo('Users');
  185. $this->post->addBehavior('CounterCache', [
  186. 'Users' => [
  187. 'post_count',
  188. ],
  189. ]);
  190. $before = $this->_getUser();
  191. $post = $this->post->find('all')
  192. ->first();
  193. $this->post->delete($post, ['ignoreCounterCache' => true]);
  194. $after = $this->_getUser();
  195. $this->assertEquals(2, $before->get('post_count'));
  196. $this->assertEquals(2, $after->get('post_count'));
  197. }
  198. /**
  199. * Testing update simple counter caching when updating a record association
  200. *
  201. * @return void
  202. */
  203. public function testUpdate()
  204. {
  205. $this->post->belongsTo('Users');
  206. $this->post->belongsTo('Categories');
  207. $this->post->addBehavior('CounterCache', [
  208. 'Users' => [
  209. 'post_count',
  210. ],
  211. 'Categories' => [
  212. 'post_count',
  213. ],
  214. ]);
  215. $user1 = $this->_getUser(1);
  216. $user2 = $this->_getUser(2);
  217. $category1 = $this->_getCategory(1);
  218. $category2 = $this->_getCategory(2);
  219. $post = $this->post->find('all')->first();
  220. $this->assertEquals(2, $user1->get('post_count'));
  221. $this->assertEquals(1, $user2->get('post_count'));
  222. $this->assertEquals(1, $category1->get('post_count'));
  223. $this->assertEquals(2, $category2->get('post_count'));
  224. $entity = $this->post->patchEntity($post, ['user_id' => 2, 'category_id' => 2]);
  225. $this->post->save($entity);
  226. $user1 = $this->_getUser(1);
  227. $user2 = $this->_getUser(2);
  228. $category1 = $this->_getCategory(1);
  229. $category2 = $this->_getCategory(2);
  230. $this->assertEquals(1, $user1->get('post_count'));
  231. $this->assertEquals(2, $user2->get('post_count'));
  232. $this->assertEquals(0, $category1->get('post_count'));
  233. $this->assertEquals(3, $category2->get('post_count'));
  234. }
  235. /**
  236. * Testing counter cache with custom find
  237. *
  238. * @return void
  239. */
  240. public function testCustomFind()
  241. {
  242. $this->post->belongsTo('Users');
  243. $this->post->addBehavior('CounterCache', [
  244. 'Users' => [
  245. 'posts_published' => [
  246. 'finder' => 'published',
  247. ],
  248. ],
  249. ]);
  250. $before = $this->_getUser();
  251. $entity = $this->_getEntity()->set('published', true);
  252. $this->post->save($entity);
  253. $after = $this->_getUser();
  254. $this->assertEquals(1, $before->get('posts_published'));
  255. $this->assertEquals(2, $after->get('posts_published'));
  256. }
  257. /**
  258. * Testing counter cache with lambda returning number
  259. *
  260. * @return void
  261. */
  262. public function testLambdaNumber()
  263. {
  264. $this->post->belongsTo('Users');
  265. $table = $this->post;
  266. $entity = $this->_getEntity();
  267. $this->post->addBehavior('CounterCache', [
  268. 'Users' => [
  269. 'posts_published' => function (Event $orgEvent, EntityInterface $orgEntity, Table $orgTable) use ($entity, $table) {
  270. $this->assertSame($orgTable, $table);
  271. $this->assertSame($orgEntity, $entity);
  272. return 2;
  273. },
  274. ],
  275. ]);
  276. $before = $this->_getUser();
  277. $this->post->save($entity);
  278. $after = $this->_getUser();
  279. $this->assertEquals(1, $before->get('posts_published'));
  280. $this->assertEquals(2, $after->get('posts_published'));
  281. }
  282. /**
  283. * Testing counter cache with lambda returning false
  284. *
  285. * @return void
  286. */
  287. public function testLambdaFalse()
  288. {
  289. $this->post->belongsTo('Users');
  290. $table = $this->post;
  291. $entity = $this->_getEntity();
  292. $this->post->addBehavior('CounterCache', [
  293. 'Users' => [
  294. 'posts_published' => function (Event $orgEvent, EntityInterface $orgEntity, Table $orgTable) use ($entity, $table) {
  295. $this->assertSame($orgTable, $table);
  296. $this->assertSame($orgEntity, $entity);
  297. return false;
  298. },
  299. ],
  300. ]);
  301. $before = $this->_getUser();
  302. $this->post->save($entity);
  303. $after = $this->_getUser();
  304. $this->assertEquals(1, $before->get('posts_published'));
  305. $this->assertEquals(1, $after->get('posts_published'));
  306. }
  307. /**
  308. * Testing counter cache with lambda returning number and changing of related ID
  309. *
  310. * @return void
  311. */
  312. public function testLambdaNumberUpdate()
  313. {
  314. $this->post->belongsTo('Users');
  315. $table = $this->post;
  316. $entity = $this->_getEntity();
  317. $this->post->addBehavior('CounterCache', [
  318. 'Users' => [
  319. 'posts_published' => function (Event $orgEvent, EntityInterface $orgEntity, Table $orgTable, $original) use ($entity, $table) {
  320. $this->assertSame($orgTable, $table);
  321. $this->assertSame($orgEntity, $entity);
  322. if (!$original) {
  323. return 2;
  324. }
  325. return 1;
  326. },
  327. ],
  328. ]);
  329. $this->post->save($entity);
  330. $between = $this->_getUser();
  331. $entity->user_id = 2;
  332. $this->post->save($entity);
  333. $afterUser1 = $this->_getUser(1);
  334. $afterUser2 = $this->_getUser(2);
  335. $this->assertEquals(2, $between->get('posts_published'));
  336. $this->assertEquals(1, $afterUser1->get('posts_published'));
  337. $this->assertEquals(2, $afterUser2->get('posts_published'));
  338. }
  339. /**
  340. * Testing counter cache with lambda returning a subquery
  341. *
  342. * @return void
  343. */
  344. public function testLambdaSubquery()
  345. {
  346. $this->post->belongsTo('Users');
  347. $this->post->addBehavior('CounterCache', [
  348. 'Users' => [
  349. 'posts_published' => function (Event $event, EntityInterface $entity, Table $table) {
  350. $query = new Query($this->connection);
  351. return $query->select(4);
  352. },
  353. ],
  354. ]);
  355. $before = $this->_getUser();
  356. $entity = $this->_getEntity();
  357. $this->post->save($entity);
  358. $after = $this->_getUser();
  359. $this->assertEquals(1, $before->get('posts_published'));
  360. $this->assertEquals(4, $after->get('posts_published'));
  361. }
  362. /**
  363. * Testing multiple counter cache when adding a record
  364. *
  365. * @return void
  366. */
  367. public function testMultiple()
  368. {
  369. $this->post->belongsTo('Users');
  370. $this->post->addBehavior('CounterCache', [
  371. 'Users' => [
  372. 'post_count',
  373. 'posts_published' => [
  374. 'conditions' => [
  375. 'published' => true,
  376. ],
  377. ],
  378. ],
  379. ]);
  380. $before = $this->_getUser();
  381. $entity = $this->_getEntity()->set('published', true);
  382. $this->post->save($entity);
  383. $after = $this->_getUser();
  384. $this->assertEquals(1, $before->get('posts_published'));
  385. $this->assertEquals(2, $after->get('posts_published'));
  386. $this->assertEquals(2, $before->get('post_count'));
  387. $this->assertEquals(3, $after->get('post_count'));
  388. }
  389. /**
  390. * Tests to see that the binding key configuration is respected.
  391. *
  392. * @return void
  393. */
  394. public function testBindingKey()
  395. {
  396. $this->post->hasMany('UserCategoryPosts', [
  397. 'bindingKey' => ['category_id', 'user_id'],
  398. 'foreignKey' => ['category_id', 'user_id'],
  399. ]);
  400. $this->post->getAssociation('UserCategoryPosts')->setTarget($this->userCategoryPosts);
  401. $this->post->addBehavior('CounterCache', [
  402. 'UserCategoryPosts' => ['post_count'],
  403. ]);
  404. $before = $this->userCategoryPosts->find()
  405. ->where(['user_id' => 1, 'category_id' => 2])
  406. ->first();
  407. $entity = $this->_getEntity()->set('category_id', 2);
  408. $this->post->save($entity);
  409. $after = $this->userCategoryPosts->find()
  410. ->where(['user_id' => 1, 'category_id' => 2])
  411. ->first();
  412. $this->assertEquals(1, $before->get('post_count'));
  413. $this->assertEquals(2, $after->get('post_count'));
  414. }
  415. /**
  416. * Testing the ignore if dirty option
  417. *
  418. * @return void
  419. */
  420. public function testIgnoreDirty()
  421. {
  422. $this->post->belongsTo('Users');
  423. $this->comment->belongsTo('Users');
  424. $this->post->addBehavior('CounterCache', [
  425. 'Users' => [
  426. 'post_count' => [
  427. 'ignoreDirty' => true,
  428. ],
  429. 'comment_count' => [
  430. 'ignoreDirty' => true,
  431. ],
  432. ],
  433. ]);
  434. $user = $this->_getUser(1);
  435. $this->assertSame(2, $user->get('post_count'));
  436. $this->assertSame(2, $user->get('comment_count'));
  437. $this->assertSame(1, $user->get('posts_published'));
  438. $post = $this->post->find('all')
  439. ->contain('Users')
  440. ->where(['title' => 'Rock and Roll'])
  441. ->first();
  442. $post = $this->post->patchEntity($post, [
  443. 'posts_published' => true,
  444. 'user' => [
  445. 'id' => 1,
  446. 'post_count' => 10,
  447. 'comment_count' => 10,
  448. ],
  449. ]);
  450. $save = $this->post->save($post);
  451. $user = $this->_getUser(1);
  452. $this->assertSame(10, $user->get('post_count'));
  453. $this->assertSame(10, $user->get('comment_count'));
  454. $this->assertSame(1, $user->get('posts_published'));
  455. }
  456. /**
  457. * Testing the ignore if dirty option with just one field set to ignoreDirty
  458. *
  459. * @return void
  460. */
  461. public function testIgnoreDirtyMixed()
  462. {
  463. $this->post->belongsTo('Users');
  464. $this->comment->belongsTo('Users');
  465. $this->post->addBehavior('CounterCache', [
  466. 'Users' => [
  467. 'post_count' => [
  468. 'ignoreDirty' => true,
  469. ],
  470. ],
  471. ]);
  472. $user = $this->_getUser(1);
  473. $this->assertSame(2, $user->get('post_count'));
  474. $this->assertSame(2, $user->get('comment_count'));
  475. $this->assertSame(1, $user->get('posts_published'));
  476. $post = $this->post->find('all')
  477. ->contain('Users')
  478. ->where(['title' => 'Rock and Roll'])
  479. ->first();
  480. $post = $this->post->patchEntity($post, [
  481. 'posts_published' => true,
  482. 'user' => [
  483. 'id' => 1,
  484. 'post_count' => 10,
  485. ],
  486. ]);
  487. $save = $this->post->save($post);
  488. $user = $this->_getUser(1);
  489. $this->assertSame(10, $user->get('post_count'));
  490. $this->assertSame(2, $user->get('comment_count'));
  491. $this->assertSame(1, $user->get('posts_published'));
  492. }
  493. /**
  494. * Get a new Entity
  495. *
  496. * @return Entity
  497. */
  498. protected function _getEntity()
  499. {
  500. return new Entity([
  501. 'title' => 'Test 123',
  502. 'user_id' => 1,
  503. ]);
  504. }
  505. /**
  506. * Returns entity for user
  507. *
  508. * @return Entity
  509. */
  510. protected function _getUser($id = 1)
  511. {
  512. return $this->user->get($id);
  513. }
  514. /**
  515. * Returns entity for category
  516. *
  517. * @return Entity
  518. */
  519. protected function _getCategory($id = 1)
  520. {
  521. return $this->category->find('all')->where(['id' => $id])->first();
  522. }
  523. }