Browse Source

Add an option which enables ignoring counter cache fields from being updated

Michael Hoffmann 9 years ago
parent
commit
c24eba6d4e

+ 59 - 0
src/ORM/Behavior/CounterCacheBehavior.php

@@ -18,6 +18,8 @@ use Cake\Datasource\EntityInterface;
 use Cake\Event\Event;
 use Cake\ORM\Association;
 use Cake\ORM\Behavior;
+use Cake\Utility\Hash;
+use Cake\Utility\Inflector;
 
 /**
  * CounterCache behavior
@@ -74,11 +76,61 @@ use Cake\ORM\Behavior;
  *     ]
  * ]
  * ```
+ *
+ * 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.
      *
      * Makes sure to update counter cache when a new record is created or updated.
@@ -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 @@ class CounterCacheBehavior extends Behavior
                 $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 {

+ 37 - 0
tests/Fixture/CounterCacheCommentsFixture.php

@@ -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],
+    ];
+}

+ 3 - 2
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],
     ];
 }

+ 95 - 0
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 @@ class CounterCacheBehaviorTest extends TestCase
             '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',
@@ -403,6 +410,94 @@ class CounterCacheBehaviorTest extends TestCase
     }
 
     /**
+     * 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
      *
      * @return Entity