Browse Source

Merge pull request #4809 from cakephp/3.0-countercache

3.0 countercache
Mark Story 11 years ago
parent
commit
0bfca67b5e

+ 50 - 0
src/Datasource/EntityTrait.php

@@ -32,6 +32,13 @@ trait EntityTrait {
 	protected $_properties = [];
 
 /**
+ * Holds all properties that have been changed and their original values for this entity
+ *
+ * @var array
+ */
+	protected $_original = [];
+
+/**
  * List of property names that should **not** be included in JSON or Array
  * representations of this Entity.
  *
@@ -223,6 +230,13 @@ trait EntityTrait {
 
 			$this->dirty($p, true);
 
+			if (!isset($this->_original[$p]) &&
+				isset($this->_properties[$p]) &&
+				$this->_properties[$p] !== $value
+			) {
+				$this->_original[$p] = $this->_properties[$p];
+			}
+
 			if (!$options['setter']) {
 				$this->_properties[$p] = $value;
 				continue;
@@ -264,6 +278,23 @@ trait EntityTrait {
 	}
 
 /**
+ * Returns the value of an original property by name
+ *
+ * @param string $property the name of the property for which original value is retrieved.
+ * @return mixed
+ * @throws \InvalidArgumentException if an empty property name is passed.
+ */
+	public function getOriginal($property) {
+		if (!strlen((string)$property)) {
+			throw new \InvalidArgumentException('Cannot get an empty property');
+		}
+		if (isset($this->_original[$property])) {
+			return $this->_original[$property];
+		}
+		return $this->get($property);
+	}
+
+/**
  * Returns whether this entity contains a property named $property
  * regardless of if it is empty.
  *
@@ -472,6 +503,24 @@ trait EntityTrait {
 	}
 
 /**
+ * Returns an array with the requested original properties
+ * stored in this entity, indexed by property name
+ *
+ * @param array $properties List of properties to be returned
+ * @return array
+ */
+	public function extractOriginal(array $properties) {
+		$result = [];
+		foreach ($properties as $property) {
+			$original = $this->getOriginal($property);
+			if ($original !== null && $original !== $this->get($property)) {
+				$result[$property] = $original;
+			}
+		}
+		return $result;
+	}
+
+/**
  * Sets the dirty status of a single property. If called with no second
  * argument, it will return whether the property was modified or not
  * after the object creation.
@@ -764,6 +813,7 @@ trait EntityTrait {
 			'accessible' => array_filter($this->_accessible),
 			'properties' => $this->_properties,
 			'dirty' => $this->_dirty,
+			'original' => $this->_original,
 			'virtual' => $this->_virtual,
 			'errors' => $this->_errors,
 			'repository' => $this->_repositoryAlias

+ 10 - 0
src/Model/Behavior/CounterCacheBehavior.php

@@ -153,6 +153,11 @@ class CounterCacheBehavior extends Behavior {
 		$countConditions = $entity->extract($foreignKeys);
 		$updateConditions = array_combine($primaryKeys, $countConditions);
 
+		$countOriginalConditions = $entity->extractOriginal($foreignKeys);
+		if ($countOriginalConditions !== []) {
+			$updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions);
+		}
+
 		foreach ($settings as $field => $config) {
 			if (is_int($field)) {
 				$field = $config;
@@ -166,6 +171,11 @@ class CounterCacheBehavior extends Behavior {
 			}
 
 			$assoc->target()->updateAll([$field => $count], $updateConditions);
+
+			if (isset($updateOriginalConditions)) {
+				$count = $this->_getCount($config, $countOriginalConditions);
+				$assoc->target()->updateAll([$field => $count], $updateOriginalConditions);
+			}
 		}
 	}
 

+ 36 - 0
tests/Fixture/CounterCacheCategoriesFixture.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
+ * 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://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\Fixture;
+
+use Cake\TestSuite\Fixture\TestFixture;
+
+/**
+ * Short description for class.
+ *
+ */
+class CounterCacheCategoriesFixture extends TestFixture {
+
+	public $fields = array(
+		'id' => ['type' => 'integer'],
+		'name' => ['type' => 'string', 'length' => 255, 'null' => false],
+		'post_count' => ['type' => 'integer', 'null' => true],
+		'_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
+	);
+
+	public $records = array(
+		array('name' => 'Sport', 'post_count' => 1),
+		array('name' => 'Music', 'post_count' => 2),
+	);
+}

+ 4 - 3
tests/Fixture/CounterCachePostsFixture.php

@@ -26,13 +26,14 @@ class CounterCachePostsFixture extends TestFixture {
 		'id' => ['type' => 'integer'],
 		'title' => ['type' => 'string', 'length' => 255],
 		'user_id' => ['type' => 'integer', 'null' => true],
+		'category_id' => ['type' => 'integer', 'null' => true],
 		'published' => ['type' => 'boolean', 'null' => false, 'default' => false],
 		'_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
 	);
 
 	public $records = array(
-		array('title' => 'Rock and Roll', 'user_id' => 1, 'published' => 0),
-		array('title' => 'Music', 'user_id' => 1, 'published' => 1),
-		array('title' => 'Food', 'user_id' => 2, 'published' => 1),
+		array('title' => 'Rock and Roll', 'user_id' => 1, 'category_id' => 1, 'published' => 0),
+		array('title' => 'Music', 'user_id' => 1, 'category_id' => 2, 'published' => 1),
+		array('title' => 'Food', 'user_id' => 2, 'category_id' => 2, 'published' => 1),
 	);
 }

+ 61 - 4
tests/TestCase/Model/Behavior/CounterCacheBehaviorTest.php

@@ -46,7 +46,8 @@ class CounterCacheBehaviorTest extends TestCase {
  */
 	public $fixtures = [
 		'core.counter_cache_users',
-		'core.counter_cache_posts'
+		'core.counter_cache_posts',
+		'core.counter_cache_categories',
 	];
 
 /**
@@ -63,6 +64,11 @@ class CounterCacheBehaviorTest extends TestCase {
 			'connection' => $this->connection
 		]);
 
+		$this->category = TableRegistry::get('Categories', [
+			'table' => 'counter_cache_categories',
+			'connection' => $this->connection
+		]);
+
 		$this->post = new PostTable([
 			'alias' => 'Post',
 			'table' => 'counter_cache_posts',
@@ -156,6 +162,47 @@ class CounterCacheBehaviorTest extends TestCase {
 	}
 
 /**
+ * Testing update simple counter caching when updating a record association
+ *
+ * @return void
+ */
+	public function testUpdate() {
+		$this->post->belongsTo('Users');
+		$this->post->belongsTo('Categories');
+
+		$this->post->addBehavior('CounterCache', [
+			'Users' => [
+				'post_count'
+			],
+			'Categories' => [
+				'post_count'
+			],
+		]);
+
+		$user1 = $this->_getUser(1);
+		$user2 = $this->_getUser(2);
+		$category1 = $this->_getCategory(1);
+		$category2 = $this->_getCategory(2);
+		$post = $this->post->find('all')->first();
+		$this->assertEquals(2, $user1->get('post_count'));
+		$this->assertEquals(1, $user2->get('post_count'));
+		$this->assertEquals(1, $category1->get('post_count'));
+		$this->assertEquals(2, $category2->get('post_count'));
+
+		$entity = $this->post->patchEntity($post, ['user_id' => 2, 'category_id' => 2]);
+		$this->post->save($entity);
+
+		$user1 = $this->_getUser(1);
+		$user2 = $this->_getUser(2);
+		$category1 = $this->_getCategory(1);
+		$category2 = $this->_getCategory(2);
+		$this->assertEquals(1, $user1->get('post_count'));
+		$this->assertEquals(2, $user2->get('post_count'));
+		$this->assertEquals(0, $category1->get('post_count'));
+		$this->assertEquals(3, $category2->get('post_count'));
+	}
+
+/**
  * Testing counter cache with custom find
  *
  * @return void
@@ -280,11 +327,21 @@ class CounterCacheBehaviorTest extends TestCase {
 	}
 
 /**
- * Returns entity for user 1
+ * Returns entity for user
  *
  * @return Entity
  */
-	protected function _getUser() {
-		return $this->user->find('all')->where(['id' => 1])->first();
+	protected function _getUser($id = 1) {
+		return $this->user->find('all')->where(['id' => $id])->first();
 	}
+
+/**
+ * Returns entity for category
+ *
+ * @return Entity
+ */
+	protected function _getCategory($id = 1) {
+		return $this->category->find('all')->where(['id' => $id])->first();
+	}
+
 }

+ 11 - 2
tests/TestCase/ORM/EntityTest.php

@@ -30,14 +30,19 @@ class EntityTest extends TestCase {
  */
 	public function testSetOneParamNoSetters() {
 		$entity = new Entity;
+		$this->assertNull($entity->getOriginal('foo'));
 		$entity->set('foo', 'bar');
 		$this->assertEquals('bar', $entity->foo);
+		$this->assertEquals('bar', $entity->getOriginal('foo'));
 
 		$entity->set('foo', 'baz');
 		$this->assertEquals('baz', $entity->foo);
+		$this->assertEquals('bar', $entity->getOriginal('foo'));
 
 		$entity->set('id', 1);
 		$this->assertSame(1, $entity->id);
+		$this->assertEquals(1, $entity->getOriginal('id'));
+		$this->assertEquals('bar', $entity->getOriginal('foo'));
 	}
 
 /**
@@ -57,6 +62,8 @@ class EntityTest extends TestCase {
 		$this->assertEquals('baz', $entity->foo);
 		$this->assertSame(2, $entity->id);
 		$this->assertSame(3, $entity->thing);
+		$this->assertEquals('bar', $entity->getOriginal('foo'));
+		$this->assertEquals(1, $entity->getOriginal('id'));
 	}
 
 /**
@@ -570,11 +577,12 @@ class EntityTest extends TestCase {
  * @return void
  */
 	public function testIsNew() {
-		$entity = new Entity([
+		$data = [
 			'id' => 1,
 			'title' => 'Foo',
 			'author_id' => 3
-		]);
+		];
+		$entity = new Entity($data);
 		$this->assertTrue($entity->isNew());
 
 		$entity->isNew(true);
@@ -1062,6 +1070,7 @@ class EntityTest extends TestCase {
 			'accessible' => ['*' => true, 'name' => true],
 			'properties' => ['foo' => 'bar'],
 			'dirty' => ['foo' => true],
+			'original' => [],
 			'virtual' => ['baz'],
 			'errors' => ['foo' => ['An error']],
 			'repository' => 'foos'

+ 0 - 3
tests/TestCase/ORM/TableTest.php

@@ -3005,9 +3005,6 @@ class TableTest extends \Cake\TestSuite\TestCase {
 		$this->assertEquals(4, $tags[2]->id);
 		$this->assertEquals(1, $tags[2]->_joinData->article_id);
 		$this->assertEquals(4, $tags[2]->_joinData->tag_id);
-
-		$article = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
-		$this->assertEquals($tags, $article->tags);
 	}
 
 /**