From c24eba6d4e7ba518f40aa48343ba345c451726e2 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Tue, 3 Jan 2017 01:51:07 +0100 Subject: [PATCH] Add an option which enables ignoring counter cache fields from being updated --- src/ORM/Behavior/CounterCacheBehavior.php | 59 ++++++++++++ tests/Fixture/CounterCacheCommentsFixture.php | 37 ++++++++ tests/Fixture/CounterCacheUsersFixture.php | 5 +- .../ORM/Behavior/CounterCacheBehaviorTest.php | 95 +++++++++++++++++++ 4 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 tests/Fixture/CounterCacheCommentsFixture.php diff --git a/src/ORM/Behavior/CounterCacheBehavior.php b/src/ORM/Behavior/CounterCacheBehavior.php index 12011319c7c..064764a61c8 100644 --- a/src/ORM/Behavior/CounterCacheBehavior.php +++ b/src/ORM/Behavior/CounterCacheBehavior.php @@ -18,6 +18,8 @@ use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Behavior; +use Cake\Utility\Hash; +use Cake\Utility\Inflector; /** * CounterCache behavior @@ -74,10 +76,60 @@ * ] * ] * ``` + * + * Ignore updating the field if it is dirty + * ``` + * [ + * 'Users' => [ + * 'posts_published' => [ + * 'ignoreDirty' => true + * ] + * ] + * ] + * ``` */ class CounterCacheBehavior extends Behavior { + /** + * Store the fields which should be ignored + * + * @var array + */ + protected $_ignoreDirty = []; + + /** + * beforeSave callback. + * + * Check if a field, which should be ignored, is dirty + * + * @param \Cake\Event\Event $event The beforeSave event that was fired + * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved + * @return void + */ + public function beforeSave(Event $event, EntityInterface $entity) + { + foreach ($this->_config as $assoc => $settings) { + $assoc = $this->_table->association($assoc); + foreach ($settings as $field => $config) { + if (is_int($field)) { + continue; + } + + $registryAlias = $assoc->target()->registryAlias(); + $entityAlias = $assoc->property(); + + if (!is_callable($config) && + isset($config['ignoreDirty']) && + $config['ignoreDirty'] === true && + $entity->$entityAlias->dirty($field) + ) { + $this->_ignoreDirty[$registryAlias][$field] = true; + } + } + } + } + /** * afterSave callback. * @@ -90,6 +142,7 @@ class CounterCacheBehavior extends Behavior public function afterSave(Event $event, EntityInterface $entity) { $this->_processAssociations($event, $entity); + $this->_ignoreDirty = []; } /** @@ -148,6 +201,12 @@ protected function _processAssociation(Event $event, EntityInterface $entity, As $config = []; } + if (isset($this->_ignoreDirty[$assoc->target()->registryAlias()][$field]) && + $this->_ignoreDirty[$assoc->target()->registryAlias()][$field] === true + ) { + continue; + } + if (!is_string($config) && is_callable($config)) { $count = $config($event, $entity, $this->_table, false); } else { diff --git a/tests/Fixture/CounterCacheCommentsFixture.php b/tests/Fixture/CounterCacheCommentsFixture.php new file mode 100644 index 00000000000..1a8f84ba5fd --- /dev/null +++ b/tests/Fixture/CounterCacheCommentsFixture.php @@ -0,0 +1,37 @@ + ['type' => 'integer'], + 'title' => ['type' => 'string', 'length' => 255], + 'user_id' => ['type' => 'integer', 'null' => true], + '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]] + ]; + + public $records = [ + ['title' => 'First Comment', 'user_id' => 1], + ['title' => 'Second Comment', 'user_id' => 1], + ['title' => 'Third Comment', 'user_id' => 2], + ]; +} diff --git a/tests/Fixture/CounterCacheUsersFixture.php b/tests/Fixture/CounterCacheUsersFixture.php index e40472ab8c8..ddff816b1a8 100644 --- a/tests/Fixture/CounterCacheUsersFixture.php +++ b/tests/Fixture/CounterCacheUsersFixture.php @@ -26,12 +26,13 @@ class CounterCacheUsersFixture extends TestFixture 'id' => ['type' => 'integer'], 'name' => ['type' => 'string', 'length' => 255, 'null' => false], 'post_count' => ['type' => 'integer', 'null' => true], + 'comment_count' => ['type' => 'integer', 'null' => true], 'posts_published' => ['type' => 'integer', 'null' => true], '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]] ]; public $records = [ - ['name' => 'Alexander', 'post_count' => 2, 'posts_published' => 1], - ['name' => 'Steven', 'post_count' => 1, 'posts_published' => 1], + ['name' => 'Alexander', 'post_count' => 2, 'comment_count' => 2, 'posts_published' => 1], + ['name' => 'Steven', 'post_count' => 1, 'comment_count' => 1, 'posts_published' => 1], ]; } diff --git a/tests/TestCase/ORM/Behavior/CounterCacheBehaviorTest.php b/tests/TestCase/ORM/Behavior/CounterCacheBehaviorTest.php index 2a1422070e5..0c348e56f90 100644 --- a/tests/TestCase/ORM/Behavior/CounterCacheBehaviorTest.php +++ b/tests/TestCase/ORM/Behavior/CounterCacheBehaviorTest.php @@ -49,6 +49,7 @@ class CounterCacheBehaviorTest extends TestCase public $fixtures = [ 'core.counter_cache_categories', 'core.counter_cache_posts', + 'core.counter_cache_comments', 'core.counter_cache_users', 'core.counter_cache_user_category_posts' ]; @@ -73,6 +74,12 @@ public function setUp() 'connection' => $this->connection ]); + $this->comment = TableRegistry::get('Comments', [ + 'alias' => 'Comment', + 'table' => 'counter_cache_comments', + 'connection' => $this->connection + ]); + $this->post = new PostTable([ 'alias' => 'Post', 'table' => 'counter_cache_posts', @@ -402,6 +409,94 @@ public function testBindingKey() $this->assertEquals(2, $after->get('post_count')); } + /** + * Testing the ignore if dirty option + * + * @return void + */ + public function testIgnoreDirty() + { + $this->post->belongsTo('Users'); + $this->comment->belongsTo('Users'); + + $this->post->addBehavior('CounterCache', [ + 'Users' => [ + 'post_count' => [ + 'ignoreDirty' => true + ], + 'comment_count' => [ + 'ignoreDirty' => true + ], + ] + ]); + + $user = $this->_getUser(1); + $this->assertSame(2, $user->get('post_count')); + $this->assertSame(2, $user->get('comment_count')); + $this->assertSame(1, $user->get('posts_published')); + + $post = $this->post->find('all') + ->contain('Users') + ->where(['title' => 'Rock and Roll']) + ->first(); + $post = $this->post->patchEntity($post, [ + 'posts_published' => true, + 'user' => [ + 'id' => 1, + 'post_count' => 10, + 'comment_count' => 10 + ] + ]); + $save = $this->post->save($post); + + $user = $this->_getUser(1); + $this->assertSame(10, $user->get('post_count')); + $this->assertSame(10, $user->get('comment_count')); + $this->assertSame(1, $user->get('posts_published')); + } + + /** + * Testing the ignore if dirty option with just one field set to ignoreDirty + * + * @return void + */ + public function testIgnoreDirtyMixed() + { + $this->post->belongsTo('Users'); + $this->comment->belongsTo('Users'); + + $this->post->addBehavior('CounterCache', [ + 'Users' => [ + 'post_count' => [ + 'ignoreDirty' => true + ] + ] + ]); + + $user = $this->_getUser(1); + $this->assertSame(2, $user->get('post_count')); + $this->assertSame(2, $user->get('comment_count')); + $this->assertSame(1, $user->get('posts_published')); + + $post = $this->post->find('all') + ->contain('Users') + ->where(['title' => 'Rock and Roll']) + ->first(); + $post = $this->post->patchEntity($post, [ + 'posts_published' => true, + 'user' => [ + 'id' => 1, + 'post_count' => 10 + ] + ]); + $save = $this->post->save($post); + + $user = $this->_getUser(1); + $this->assertSame(10, $user->get('post_count')); + $this->assertSame(2, $user->get('comment_count')); + $this->assertSame(1, $user->get('posts_published')); + } + /** * Get a new Entity *