CounterCacheBehaviorTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  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. $this->getTableLocator()->clear();
  93. }
  94. /**
  95. * Testing simple counter caching when adding a record
  96. *
  97. * @return void
  98. */
  99. public function testAdd()
  100. {
  101. $this->post->belongsTo('Users');
  102. $this->post->addBehavior('CounterCache', [
  103. 'Users' => [
  104. 'post_count',
  105. ],
  106. ]);
  107. $before = $this->_getUser();
  108. $entity = $this->_getEntity();
  109. $this->post->save($entity);
  110. $after = $this->_getUser();
  111. $this->assertEquals(2, $before->get('post_count'));
  112. $this->assertEquals(3, $after->get('post_count'));
  113. }
  114. /**
  115. * Testing simple counter caching when adding a record
  116. *
  117. * @return void
  118. */
  119. public function testAddIgnore()
  120. {
  121. $this->post->belongsTo('Users');
  122. $this->post->addBehavior('CounterCache', [
  123. 'Users' => [
  124. 'post_count',
  125. ],
  126. ]);
  127. $before = $this->_getUser();
  128. $entity = $this->_getEntity();
  129. $this->post->save($entity, ['ignoreCounterCache' => true]);
  130. $after = $this->_getUser();
  131. $this->assertEquals(2, $before->get('post_count'));
  132. $this->assertEquals(2, $after->get('post_count'));
  133. }
  134. /**
  135. * Testing simple counter caching when adding a record
  136. *
  137. * @return void
  138. */
  139. public function testAddScope()
  140. {
  141. $this->post->belongsTo('Users');
  142. $this->post->addBehavior('CounterCache', [
  143. 'Users' => [
  144. 'posts_published' => [
  145. 'conditions' => [
  146. 'published' => true,
  147. ],
  148. ],
  149. ],
  150. ]);
  151. $before = $this->_getUser();
  152. $entity = $this->_getEntity()->set('published', true);
  153. $this->post->save($entity);
  154. $after = $this->_getUser();
  155. $this->assertEquals(1, $before->get('posts_published'));
  156. $this->assertEquals(2, $after->get('posts_published'));
  157. }
  158. /**
  159. * Testing simple counter caching when deleting a record
  160. *
  161. * @return void
  162. */
  163. public function testDelete()
  164. {
  165. $this->post->belongsTo('Users');
  166. $this->post->addBehavior('CounterCache', [
  167. 'Users' => [
  168. 'post_count',
  169. ],
  170. ]);
  171. $before = $this->_getUser();
  172. $post = $this->post->find('all')->first();
  173. $this->post->delete($post);
  174. $after = $this->_getUser();
  175. $this->assertEquals(2, $before->get('post_count'));
  176. $this->assertEquals(1, $after->get('post_count'));
  177. }
  178. /**
  179. * Testing simple counter caching when deleting a record
  180. *
  181. * @return void
  182. */
  183. public function testDeleteIgnore()
  184. {
  185. $this->post->belongsTo('Users');
  186. $this->post->addBehavior('CounterCache', [
  187. 'Users' => [
  188. 'post_count',
  189. ],
  190. ]);
  191. $before = $this->_getUser();
  192. $post = $this->post->find('all')
  193. ->first();
  194. $this->post->delete($post, ['ignoreCounterCache' => true]);
  195. $after = $this->_getUser();
  196. $this->assertEquals(2, $before->get('post_count'));
  197. $this->assertEquals(2, $after->get('post_count'));
  198. }
  199. /**
  200. * Testing update simple counter caching when updating a record association
  201. *
  202. * @return void
  203. */
  204. public function testUpdate()
  205. {
  206. $this->post->belongsTo('Users');
  207. $this->post->belongsTo('Categories');
  208. $this->post->addBehavior('CounterCache', [
  209. 'Users' => [
  210. 'post_count',
  211. ],
  212. 'Categories' => [
  213. 'post_count',
  214. ],
  215. ]);
  216. $user1 = $this->_getUser(1);
  217. $user2 = $this->_getUser(2);
  218. $category1 = $this->_getCategory(1);
  219. $category2 = $this->_getCategory(2);
  220. $post = $this->post->find('all')->first();
  221. $this->assertEquals(2, $user1->get('post_count'));
  222. $this->assertEquals(1, $user2->get('post_count'));
  223. $this->assertEquals(1, $category1->get('post_count'));
  224. $this->assertEquals(2, $category2->get('post_count'));
  225. $entity = $this->post->patchEntity($post, ['user_id' => 2, 'category_id' => 2]);
  226. $this->post->save($entity);
  227. $user1 = $this->_getUser(1);
  228. $user2 = $this->_getUser(2);
  229. $category1 = $this->_getCategory(1);
  230. $category2 = $this->_getCategory(2);
  231. $this->assertEquals(1, $user1->get('post_count'));
  232. $this->assertEquals(2, $user2->get('post_count'));
  233. $this->assertEquals(0, $category1->get('post_count'));
  234. $this->assertEquals(3, $category2->get('post_count'));
  235. }
  236. /**
  237. * Testing counter cache with custom find
  238. *
  239. * @return void
  240. */
  241. public function testCustomFind()
  242. {
  243. $this->post->belongsTo('Users');
  244. $this->post->addBehavior('CounterCache', [
  245. 'Users' => [
  246. 'posts_published' => [
  247. 'finder' => 'published',
  248. ],
  249. ],
  250. ]);
  251. $before = $this->_getUser();
  252. $entity = $this->_getEntity()->set('published', true);
  253. $this->post->save($entity);
  254. $after = $this->_getUser();
  255. $this->assertEquals(1, $before->get('posts_published'));
  256. $this->assertEquals(2, $after->get('posts_published'));
  257. }
  258. /**
  259. * Testing counter cache with lambda returning number
  260. *
  261. * @return void
  262. */
  263. public function testLambdaNumber()
  264. {
  265. $this->post->belongsTo('Users');
  266. $table = $this->post;
  267. $entity = $this->_getEntity();
  268. $this->post->addBehavior('CounterCache', [
  269. 'Users' => [
  270. 'posts_published' => function (Event $orgEvent, EntityInterface $orgEntity, Table $orgTable) use ($entity, $table) {
  271. $this->assertSame($orgTable, $table);
  272. $this->assertSame($orgEntity, $entity);
  273. return 2;
  274. },
  275. ],
  276. ]);
  277. $before = $this->_getUser();
  278. $this->post->save($entity);
  279. $after = $this->_getUser();
  280. $this->assertEquals(1, $before->get('posts_published'));
  281. $this->assertEquals(2, $after->get('posts_published'));
  282. }
  283. /**
  284. * Testing counter cache with lambda returning false
  285. *
  286. * @return void
  287. */
  288. public function testLambdaFalse()
  289. {
  290. $this->post->belongsTo('Users');
  291. $table = $this->post;
  292. $entity = $this->_getEntity();
  293. $this->post->addBehavior('CounterCache', [
  294. 'Users' => [
  295. 'posts_published' => function (Event $orgEvent, EntityInterface $orgEntity, Table $orgTable) use ($entity, $table) {
  296. $this->assertSame($orgTable, $table);
  297. $this->assertSame($orgEntity, $entity);
  298. return false;
  299. },
  300. ],
  301. ]);
  302. $before = $this->_getUser();
  303. $this->post->save($entity);
  304. $after = $this->_getUser();
  305. $this->assertEquals(1, $before->get('posts_published'));
  306. $this->assertEquals(1, $after->get('posts_published'));
  307. }
  308. /**
  309. * Testing counter cache with lambda returning number and changing of related ID
  310. *
  311. * @return void
  312. */
  313. public function testLambdaNumberUpdate()
  314. {
  315. $this->post->belongsTo('Users');
  316. $table = $this->post;
  317. $entity = $this->_getEntity();
  318. $this->post->addBehavior('CounterCache', [
  319. 'Users' => [
  320. 'posts_published' => function (Event $orgEvent, EntityInterface $orgEntity, Table $orgTable, $original) use ($entity, $table) {
  321. $this->assertSame($orgTable, $table);
  322. $this->assertSame($orgEntity, $entity);
  323. if (!$original) {
  324. return 2;
  325. }
  326. return 1;
  327. },
  328. ],
  329. ]);
  330. $this->post->save($entity);
  331. $between = $this->_getUser();
  332. $entity->user_id = 2;
  333. $this->post->save($entity);
  334. $afterUser1 = $this->_getUser(1);
  335. $afterUser2 = $this->_getUser(2);
  336. $this->assertEquals(2, $between->get('posts_published'));
  337. $this->assertEquals(1, $afterUser1->get('posts_published'));
  338. $this->assertEquals(2, $afterUser2->get('posts_published'));
  339. }
  340. /**
  341. * Testing counter cache with lambda returning a subquery
  342. *
  343. * @return void
  344. */
  345. public function testLambdaSubquery()
  346. {
  347. $this->post->belongsTo('Users');
  348. $this->post->addBehavior('CounterCache', [
  349. 'Users' => [
  350. 'posts_published' => function (Event $event, EntityInterface $entity, Table $table) {
  351. $query = new Query($this->connection);
  352. return $query->select(4);
  353. },
  354. ],
  355. ]);
  356. $before = $this->_getUser();
  357. $entity = $this->_getEntity();
  358. $this->post->save($entity);
  359. $after = $this->_getUser();
  360. $this->assertEquals(1, $before->get('posts_published'));
  361. $this->assertEquals(4, $after->get('posts_published'));
  362. }
  363. /**
  364. * Testing multiple counter cache when adding a record
  365. *
  366. * @return void
  367. */
  368. public function testMultiple()
  369. {
  370. $this->post->belongsTo('Users');
  371. $this->post->addBehavior('CounterCache', [
  372. 'Users' => [
  373. 'post_count',
  374. 'posts_published' => [
  375. 'conditions' => [
  376. 'published' => true,
  377. ],
  378. ],
  379. ],
  380. ]);
  381. $before = $this->_getUser();
  382. $entity = $this->_getEntity()->set('published', true);
  383. $this->post->save($entity);
  384. $after = $this->_getUser();
  385. $this->assertEquals(1, $before->get('posts_published'));
  386. $this->assertEquals(2, $after->get('posts_published'));
  387. $this->assertEquals(2, $before->get('post_count'));
  388. $this->assertEquals(3, $after->get('post_count'));
  389. }
  390. /**
  391. * Tests to see that the binding key configuration is respected.
  392. *
  393. * @return void
  394. */
  395. public function testBindingKey()
  396. {
  397. $this->post->hasMany('UserCategoryPosts', [
  398. 'bindingKey' => ['category_id', 'user_id'],
  399. 'foreignKey' => ['category_id', 'user_id'],
  400. ]);
  401. $this->post->getAssociation('UserCategoryPosts')->setTarget($this->userCategoryPosts);
  402. $this->post->addBehavior('CounterCache', [
  403. 'UserCategoryPosts' => ['post_count'],
  404. ]);
  405. $before = $this->userCategoryPosts->find()
  406. ->where(['user_id' => 1, 'category_id' => 2])
  407. ->first();
  408. $entity = $this->_getEntity()->set('category_id', 2);
  409. $this->post->save($entity);
  410. $after = $this->userCategoryPosts->find()
  411. ->where(['user_id' => 1, 'category_id' => 2])
  412. ->first();
  413. $this->assertEquals(1, $before->get('post_count'));
  414. $this->assertEquals(2, $after->get('post_count'));
  415. }
  416. /**
  417. * Testing the ignore if dirty option
  418. *
  419. * @return void
  420. */
  421. public function testIgnoreDirty()
  422. {
  423. $this->post->belongsTo('Users');
  424. $this->comment->belongsTo('Users');
  425. $this->post->addBehavior('CounterCache', [
  426. 'Users' => [
  427. 'post_count' => [
  428. 'ignoreDirty' => true,
  429. ],
  430. 'comment_count' => [
  431. 'ignoreDirty' => true,
  432. ],
  433. ],
  434. ]);
  435. $user = $this->_getUser(1);
  436. $this->assertSame(2, $user->get('post_count'));
  437. $this->assertSame(2, $user->get('comment_count'));
  438. $this->assertSame(1, $user->get('posts_published'));
  439. $post = $this->post->find('all')
  440. ->contain('Users')
  441. ->where(['title' => 'Rock and Roll'])
  442. ->first();
  443. $post = $this->post->patchEntity($post, [
  444. 'posts_published' => true,
  445. 'user' => [
  446. 'id' => 1,
  447. 'post_count' => 10,
  448. 'comment_count' => 10,
  449. ],
  450. ]);
  451. $save = $this->post->save($post);
  452. $user = $this->_getUser(1);
  453. $this->assertSame(10, $user->get('post_count'));
  454. $this->assertSame(10, $user->get('comment_count'));
  455. $this->assertSame(1, $user->get('posts_published'));
  456. }
  457. /**
  458. * Testing the ignore if dirty option with just one field set to ignoreDirty
  459. *
  460. * @return void
  461. */
  462. public function testIgnoreDirtyMixed()
  463. {
  464. $this->post->belongsTo('Users');
  465. $this->comment->belongsTo('Users');
  466. $this->post->addBehavior('CounterCache', [
  467. 'Users' => [
  468. 'post_count' => [
  469. 'ignoreDirty' => true,
  470. ],
  471. ],
  472. ]);
  473. $user = $this->_getUser(1);
  474. $this->assertSame(2, $user->get('post_count'));
  475. $this->assertSame(2, $user->get('comment_count'));
  476. $this->assertSame(1, $user->get('posts_published'));
  477. $post = $this->post->find('all')
  478. ->contain('Users')
  479. ->where(['title' => 'Rock and Roll'])
  480. ->first();
  481. $post = $this->post->patchEntity($post, [
  482. 'posts_published' => true,
  483. 'user' => [
  484. 'id' => 1,
  485. 'post_count' => 10,
  486. ],
  487. ]);
  488. $save = $this->post->save($post);
  489. $user = $this->_getUser(1);
  490. $this->assertSame(10, $user->get('post_count'));
  491. $this->assertSame(2, $user->get('comment_count'));
  492. $this->assertSame(1, $user->get('posts_published'));
  493. }
  494. /**
  495. * Get a new Entity
  496. *
  497. * @return Entity
  498. */
  499. protected function _getEntity()
  500. {
  501. return new Entity([
  502. 'title' => 'Test 123',
  503. 'user_id' => 1,
  504. ]);
  505. }
  506. /**
  507. * Returns entity for user
  508. *
  509. * @return Entity
  510. */
  511. protected function _getUser($id = 1)
  512. {
  513. return $this->user->get($id);
  514. }
  515. /**
  516. * Returns entity for category
  517. *
  518. * @return Entity
  519. */
  520. protected function _getCategory($id = 1)
  521. {
  522. return $this->category->find('all')->where(['id' => $id])->first();
  523. }
  524. }