Browse Source

Merge pull request #3313 from Ali1/feature-atomic-caching3

Cache: Implement writeMany, readMany, deleteMany
Mark Story 12 years ago
parent
commit
bb4703bcc5

+ 91 - 0
src/Cache/Cache.php

@@ -203,6 +203,44 @@ class Cache {
 	}
 
 /**
+ *  Write data for many keys into cache.
+ *
+ * ### Usage:
+ *
+ * Writing to the active cache config:
+ *
+ * `Cache::writeMany(array('cached_data_1' => 'data 1', 'cached_data_2' => 'data 2'));`
+ *
+ * Writing to a specific cache config:
+ *
+ * `Cache::writeMany(array('cached_data_1' => 'data 1', 'cached_data_2' => 'data 2'), 'long_term');`
+ *
+ * @param array $data An array of data to be stored in the cache
+ * @param string $config Optional string configuration name to write to. Defaults to 'default'
+ * @return array of bools for each key provided, indicating true for success or false for fail
+ * @throws \Cake\Error\Exception
+ */
+	public static function writeMany($data, $config = 'default') {
+		$engine = static::engine($config);
+		if (!$engine) {
+			return false;
+		}
+
+		$return = $engine->writeMany($data);
+		foreach ($return as $key => $success) {
+			if ($success === false && !empty($data[$key])) {
+				throw new Error\Exception(sprintf(
+					'%s cache was unable to write \'%s\' to %s cache',
+					$config,
+					$key,
+					get_class($engine)
+				));
+			}
+		}
+		return $return;
+	}
+
+/**
  * Read a key from the cache.
  *
  * ### Usage:
@@ -229,6 +267,32 @@ class Cache {
 	}
 
 /**
+ * Read multiple keys from the cache.
+ *
+ * ### Usage:
+ *
+ * Reading multiple keys from the active cache configuration.
+ *
+ * `Cache::readMany(array('my_data_1', 'my_data_2)));`
+ *
+ * Reading from a specific cache configuration.
+ *
+ * `Cache::readMany(array('my_data_1', 'my_data_2), 'long_term');`
+ *
+ * @param array $keys an array of keys to fetch from the cache
+ * @param string $config optional name of the configuration to use. Defaults to 'default'
+ * @return array An array containing, for each of the given $keys, the cached data or false if cached data could not be
+ * retreived
+ */
+	public static function readMany($keys, $config = 'default') {
+		$engine = static::engine($config);
+		if (!$engine) {
+			return false;
+		}
+
+		return $engine->readMany($keys);
+	}
+/**
  * Increment a number under the key and return incremented value.
  *
  * @param string $key Identifier for the data
@@ -291,6 +355,33 @@ class Cache {
 	}
 
 /**
+ * Delete many keys from the cache.
+ *
+ * ### Usage:
+ *
+ * Deleting multiple keys from the active cache configuration.
+ *
+ * `Cache::deleteMany(array('my_data_1', 'my_data_2'));`
+ *
+ * Deleting from a specific cache configuration.
+ *
+ * `Cache::deleteMany(array('my_data_1', 'my_data_2), 'long_term');`
+ *
+ * @param array $keys Array of cache keys to be deleted
+ * @param string $config name of the configuration to use. Defaults to 'default'
+ * @return array of boolean values that are true if the value was successfully deleted, false if it didn't exist or
+ * couldn't be removed
+ */
+	public static function deleteMany($keys, $config = 'default') {
+		$engine = static::engine($config);
+		if (!$engine) {
+			return false;
+		}
+
+		return $engine->deleteMany($keys);
+	}
+
+/**
  * Delete all keys from the cache.
  *
  * @param bool $check if true will check expiration, otherwise delete all

+ 44 - 0
src/Cache/CacheEngine.php

@@ -98,6 +98,20 @@ abstract class CacheEngine {
 	abstract public function write($key, $value);
 
 /**
+ * Write data for many keys into cache
+ *
+ * @param array $data An array of data to be stored in the cache
+ * @return array of bools for each key provided, true if the data was successfully cached, false on failure
+ */
+	public function writeMany($data) {
+		$return = array();
+		foreach ($data as $key => $value) {
+			$return[$key] = $this->write($key, $value);
+		}
+		return $return;
+	}
+
+/**
  * Read a key from the cache
  *
  * @param string $key Identifier for the data
@@ -106,6 +120,21 @@ abstract class CacheEngine {
 	abstract public function read($key);
 
 /**
+ * Read multiple keys from the cache
+ *
+ * @param array $keys An array of identifiers for the data
+ * @return array For each cache key (given as the array key) the cache data associated or false if the data doesn't
+ * exist, has expired, or if there was an error fetching it
+ */
+	public function readMany($keys) {
+		$return = array();
+		foreach ($keys as $key) {
+			$return[$key] = $this->read($key);
+		}
+		return $return;
+	}
+
+/**
  * Increment a number under the key and return incremented value
  *
  * @param string $key Identifier for the data
@@ -132,6 +161,21 @@ abstract class CacheEngine {
 	abstract public function delete($key);
 
 /**
+ * Deletes keys from the cache
+ *
+ * @param array $keys An array of identifiers for the data
+ * @return array For each provided cache key (given back as the array key) true if the value was successfully deleted,
+ * false if it didn't exist or couldn't be removed
+ */
+	public function deleteMany($keys) {
+		$return = array();
+		foreach ($keys as $key) {
+			$return[$key] = $this->delete($key);
+		}
+		return $return;
+	}
+
+/**
  * Delete all keys from the cache
  *
  * @param bool $check if true will check expiration, otherwise delete all

+ 64 - 0
src/Cache/Engine/MemcachedEngine.php

@@ -233,6 +233,27 @@ class MemcachedEngine extends CacheEngine {
 	}
 
 /**
+ * Write many cache entries to the cache at once
+ *
+ * @param array $data An array of data to be stored in the cache
+ * @return array of bools for each key provided, true if the data was successfully cached, false on failure
+ */
+	public function writeMany($data) {
+		$cacheData = array();
+		foreach ($data as $key => $value) {
+			$cacheData[$this->_key($key)] = $value;
+		}
+
+		$success = $this->_Memcached->setMulti($cacheData);
+
+		$return = array();
+		foreach (array_keys($data) as $key) {
+			$return[$key] = $success;
+		}
+		return $return;
+	}
+
+/**
  * Read a key from the cache
  *
  * @param string $key Identifier for the data
@@ -245,6 +266,27 @@ class MemcachedEngine extends CacheEngine {
 	}
 
 /**
+ * Read many keys from the cache at once
+ *
+ * @param array $keys An array of identifiers for the data
+ * @return An array containing, for each of the given $keys, the cached data or false if cached data could not be
+ * retreived
+ */
+	public function readMany($keys) {
+		$cacheKeys = array();
+		foreach ($keys as $key) {
+			$cacheKeys[] = $this->_key($key);
+		}
+
+		$values = $this->_Memcached->getMulti($cacheKeys);
+		$return = array();
+		foreach ($keys as &$key) {
+			$return[$key] = array_key_exists($this->_key($key), $values) ? $values[$this->_key($key)] : false;
+		}
+		return $return;
+	}
+
+/**
  * Increments the value of an integer cached key
  *
  * @param string $key Identifier for the data
@@ -285,6 +327,28 @@ class MemcachedEngine extends CacheEngine {
 	}
 
 /**
+ * Delete many keys from the cache at once
+ *
+ * @param array $keys An array of identifiers for the data
+ * @return array of boolean values that are true if the key was successfully deleted, false if it didn't exist or
+ * couldn't be removed
+ */
+	public function deleteMany($keys) {
+		$cacheKeys = array();
+		foreach ($keys as $key) {
+			$cacheKeys[] = $this->_key($key);
+		}
+
+		$success = $this->_Memcached->deleteMulti($cacheKeys);
+
+		$return = array();
+		foreach ($keys as $key) {
+			$return[$key] = $success;
+		}
+		return $return;
+	}
+
+/**
  * Delete all keys from the cache
  *
  * @param bool $check

+ 52 - 0
tests/TestCase/Cache/CacheTest.php

@@ -344,6 +344,58 @@ class CacheTest extends TestCase {
 	}
 
 /**
+ * testReadWriteMany method
+ *
+ * @return void
+ */
+	public function testReadWriteMany() {
+		$this->_configCache();
+		$data = array(
+			'App.falseTest' => false,
+			'App.trueTest' => true,
+			'App.nullTest' => null,
+			'App.zeroTest' => 0,
+			'App.zeroTest2' => '0'
+		);
+		Cache::writeMany($data, 'tests');
+
+		$read = Cache::readMany(array_keys($data), 'tests');
+
+		$this->assertSame($read['App.falseTest'], false);
+		$this->assertSame($read['App.trueTest'], true);
+		$this->assertSame($read['App.nullTest'], null);
+		$this->assertSame($read['App.zeroTest'], 0);
+		$this->assertSame($read['App.zeroTest2'], '0');
+	}
+
+/**
+ * testDeleteMany method
+ *
+ * @return void
+ */
+	public function testDeleteMany() {
+		$this->_configCache();
+		$data = array(
+			'App.falseTest' => false,
+			'App.trueTest' => true,
+			'App.nullTest' => null,
+			'App.zeroTest' => 0,
+			'App.zeroTest2' => '0'
+		);
+		Cache::writeMany(array_merge($data, array('App.keepTest' => 'keepMe')), 'tests');
+
+		Cache::deleteMany(array_keys($data), 'tests');
+		$read = Cache::readMany(array_merge(array_keys($data), array('App.keepTest')), 'tests');
+
+		$this->assertSame($read['App.falseTest'], false);
+		$this->assertSame($read['App.trueTest'], false);
+		$this->assertSame($read['App.nullTest'], false);
+		$this->assertSame($read['App.zeroTest'], false);
+		$this->assertSame($read['App.zeroTest2'], false);
+		$this->assertSame($read['App.keepTest'], 'keepMe');
+	}
+
+/**
  * Test that failed writes cause errors to be triggered.
  *
  * @return void

+ 80 - 0
tests/TestCase/Cache/Engine/MemcachedEngineTest.php

@@ -451,6 +451,57 @@ class MemcachedEngineTest extends TestCase {
 	}
 
 /**
+ * testReadMany method
+ *
+ * @return void
+ */
+	public function testReadMany() {
+		$this->_configCache(['duration' => 2]);
+		$data = array(
+			'App.falseTest' => false,
+			'App.trueTest' => true,
+			'App.nullTest' => null,
+			'App.zeroTest' => 0,
+			'App.zeroTest2' => '0'
+		);
+		foreach ($data as $key => $value) {
+			Cache::write($key, $value, 'memcached');
+		}
+
+		$read = Cache::readMany(array_merge(array_keys($data), ['App.doesNotExist']), 'memcached');
+
+		$this->assertSame($read['App.falseTest'], false);
+		$this->assertSame($read['App.trueTest'], true);
+		$this->assertSame($read['App.nullTest'], null);
+		$this->assertSame($read['App.zeroTest'], 0);
+		$this->assertSame($read['App.zeroTest2'], '0');
+		$this->assertSame($read['App.doesNotExist'], false);
+	}
+
+/**
+ * testWriteMany method
+ *
+ * @return void
+ */
+	public function testWriteMany() {
+		$this->_configCache(['duration' => 2]);
+		$data = array(
+			'App.falseTest' => false,
+			'App.trueTest' => true,
+			'App.nullTest' => null,
+			'App.zeroTest' => 0,
+			'App.zeroTest2' => '0'
+		);
+		Cache::writeMany($data, 'memcached');
+
+		$this->assertSame(Cache::read('App.falseTest', 'memcached'), false);
+		$this->assertSame(Cache::read('App.trueTest', 'memcached'), true);
+		$this->assertSame(Cache::read('App.nullTest', 'memcached'), null);
+		$this->assertSame(Cache::read('App.zeroTest', 'memcached'), 0);
+		$this->assertSame(Cache::read('App.zeroTest2', 'memcached'), '0');
+	}
+
+/**
  * testExpiry method
  *
  * @return void
@@ -508,6 +559,35 @@ class MemcachedEngineTest extends TestCase {
 	}
 
 /**
+ * testDeleteMany method
+ *
+ * @return void
+ */
+	public function testDeleteMany() {
+		$this->_configCache();
+		$data = array(
+			'App.falseTest' => false,
+			'App.trueTest' => true,
+			'App.nullTest' => null,
+			'App.zeroTest' => 0,
+			'App.zeroTest2' => '0',
+		);
+		foreach ($data as $key => $value) {
+			Cache::write($key, $value, 'memcached');
+		}
+		Cache::write('App.keepTest', 'keepMe', 'memcached');
+
+		Cache::deleteMany(array_merge(array_keys($data), ['App.doesNotExist']), 'memcached');
+
+		$this->assertSame(Cache::read('App.falseTest', 'memcached'), false);
+		$this->assertSame(Cache::read('App.trueTest', 'memcached'), false);
+		$this->assertSame(Cache::read('App.nullTest', 'memcached'), false);
+		$this->assertSame(Cache::read('App.zeroTest', 'memcached'), false);
+		$this->assertSame(Cache::read('App.zeroTest2', 'memcached'), false);
+		$this->assertSame(Cache::read('App.keepTest', 'memcached'), 'keepMe');
+	}
+
+/**
  * testDecrement method
  *
  * @return void