Skip to content

Commit

Permalink
Merge pull request cakephp#9960 from cleptric/counter-cache
Browse files Browse the repository at this point in the history
CounterCache should ignore dirty fields
  • Loading branch information
markstory authored Jan 11, 2017
2 parents 0fc4f0f + c24eba6 commit fc0f0e2
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 2 deletions.
59 changes: 59 additions & 0 deletions src/ORM/Behavior/CounterCacheBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand All @@ -90,6 +142,7 @@ class CounterCacheBehavior extends Behavior
public function afterSave(Event $event, EntityInterface $entity)
{
$this->_processAssociations($event, $entity);
$this->_ignoreDirty = [];
}

/**
Expand Down Expand Up @@ -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 {
Expand Down
37 changes: 37 additions & 0 deletions tests/Fixture/CounterCacheCommentsFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

/**
* Counter Cache Test Fixtures
*/
class CounterCacheCommentsFixture extends TestFixture
{

public $fields = [
'id' => ['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],
];
}
5 changes: 3 additions & 2 deletions tests/Fixture/CounterCacheUsersFixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -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],
];
}
95 changes: 95 additions & 0 deletions tests/TestCase/ORM/Behavior/CounterCacheBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'
];
Expand All @@ -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',
Expand Down Expand Up @@ -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
*
Expand Down

0 comments on commit fc0f0e2

Please sign in to comment.