Browse Source

Port Cache::add() to 3.1

Port the changes in #7165 to add Cache::add() to 3.x
Mark Story 10 years ago
parent
commit
f66edff929

+ 28 - 0
src/Cache/Cache.php

@@ -540,4 +540,32 @@ class Cache
         self::write($key, $results, $config);
         return $results;
     }
+
+    /**
+     * Write data for key into a cache engine if it doesn't exist already.
+     *
+     * ### Usage:
+     *
+     * Writing to the active cache config:
+     *
+     * `Cache::add('cached_data', $data);`
+     *
+     * Writing to a specific cache config:
+     *
+     * `Cache::add('cached_data', $data, 'long_term');`
+     *
+     * @param string $key Identifier for the data.
+     * @param mixed $value Data to be cached - anything except a resource.
+     * @param string $config Optional string configuration name to write to. Defaults to 'default'.
+     * @return bool True if the data was successfully cached, false on failure.
+     *   Or if the key existed already.
+     */
+    public static function add($key, $value, $config = 'default')
+    {
+        $engine = static::engine($config);
+        if (is_resource($value)) {
+            return false;
+        }
+        return $engine->add($key, $value);
+    }
 }

+ 24 - 4
src/Cache/CacheEngine.php

@@ -165,6 +165,15 @@ abstract class CacheEngine
      */
     abstract public function delete($key);
 
+
+    /**
+     * Delete all keys from the cache
+     *
+     * @param bool $check if true will check expiration, otherwise delete all
+     * @return bool True if the cache was successfully cleared, false otherwise
+     */
+    abstract public function clear($check);
+
     /**
      * Deletes keys from the cache
      *
@@ -182,12 +191,23 @@ abstract class CacheEngine
     }
 
     /**
-     * Delete all keys from the cache
+     * Add a key to the cache if it does not already exist.
      *
-     * @param bool $check if true will check expiration, otherwise delete all
-     * @return bool True if the cache was successfully cleared, false otherwise
+     * Defaults to a non-atomic implementation. Subclasses should
+     * prefer atomic implementations.
+     *
+     * @param string $key Identifier for the data.
+     * @param mixed $value Data to be cached.
+     * @return bool True if the data was successfully cached, false on failure.
      */
-    abstract public function clear($check);
+    public function add($key, $value)
+    {
+        $cachedValue = $this->read($key);
+        if ($cachedValue === false) {
+            return $this->write($key, $value);
+        }
+        return false;
+    }
 
     /**
      * Clears all values belonging to a group. Is up to the implementing engine

+ 22 - 0
src/Cache/Engine/ApcEngine.php

@@ -161,6 +161,28 @@ class ApcEngine extends CacheEngine
     }
 
     /**
+     * Write data for key into cache if it doesn't exist already. 
+     * If it already exists, it fails and returns false.
+     *
+     * @param string $key Identifier for the data.
+     * @param mixed $value Data to be cached.
+     * @return bool True if the data was successfully cached, false on failure.
+     * @link http://php.net/manual/en/function.apc-add.php
+     */
+    public function add($key, $value)
+    {
+        $key = $this->_key($key);
+
+        $expires = 0;
+        $duration = $this->_config['duration'];
+        if ($duration) {
+            $expires = time() + $duration;
+        }
+        apc_add($key . '_expires', $expires, $duration);
+        return apc_add($key, $value, $duration);
+    }
+
+    /**
      * Returns the `group value` for each of the configured groups
      * If the group initial value was not found, then it initializes
      * the group accordingly.

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

@@ -422,6 +422,9 @@ class MemcachedEngine extends CacheEngine
         }
 
         $keys = $this->_Memcached->getAllKeys();
+        if ($keys === false) {
+            return false;
+        }
 
         foreach ($keys as $key) {
             if (strpos($key, $this->_config['prefix']) === 0) {
@@ -433,6 +436,24 @@ class MemcachedEngine extends CacheEngine
     }
 
     /**
+     * Add a key to the cache if it does not already exist.
+     *
+     * @param string $key Identifier for the data.
+     * @param mixed $value Data to be cached.
+     * @return bool True if the data was successfully cached, false on failure.
+     */
+    public function add($key, $value)
+    {
+        $duration = $this->_config['duration'];
+        if ($duration > 30 * DAY) {
+            $duration = 0;
+        }
+
+        $key = $this->_key($key);
+        return $this->_Memcached->add($key, $value, $duration);
+    }
+
+    /**
      * Returns the `group value` for each of the configured groups
      * If the group initial value was not found, then it initializes
      * the group accordingly.

+ 25 - 0
src/Cache/Engine/RedisEngine.php

@@ -215,6 +215,31 @@ class RedisEngine extends CacheEngine
     }
 
     /**
+     * Write data for key into cache if it doesn't exist already.
+     * If it already exists, it fails and returns false.
+     *
+     * @param string $key Identifier for the data.
+     * @param mixed $value Data to be cached.
+     * @return bool True if the data was successfully cached, false on failure.
+     * @link https://github.com/phpredis/phpredis#setnx
+     */
+    public function add($key, $value)
+    {
+        $duration = $this->_config['duration'];
+        $key = $this->_key($key);
+
+        if (!is_int($value)) {
+            $value = serialize($value);
+        }
+
+        // setnx() doesn't have an expiry option, so overwrite the key with one
+        if ($this->_Redis->setnx($key, $value)) {
+            return $this->_Redis->setex($key, $duration, $value);
+        }
+        return false;
+    }
+
+    /**
      * Returns the `group value` for each of the configured groups
      * If the group initial value was not found, then it initializes
      * the group accordingly.

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

@@ -559,4 +559,25 @@ class CacheTest extends TestCase
         $result = Cache::remember('test_key', $cacher, 'tests');
         $this->assertEquals($expected, $result);
     }
+
+    /**
+     * Test add method.
+     *
+     * @return void
+     */
+    public function testAdd()
+    {
+        $this->_configCache();
+        Cache::delete('test_add_key', 'tests');
+
+        $result = Cache::add('test_add_key', 'test data', 'tests');
+        $this->assertTrue($result);
+
+        $expected = 'test data';
+        $result = Cache::read('test_add_key', 'tests');
+        $this->assertEquals($expected, $result);
+
+        $result = Cache::add('test_add_key', 'test data 2', 'tests');
+        $this->assertFalse($result);
+    }
 }

+ 20 - 0
tests/TestCase/Cache/Engine/ApcEngineTest.php

@@ -286,4 +286,24 @@ class ApcEngineTest extends TestCase
         $this->assertTrue(Cache::clearGroup('group_b', 'apc_groups'));
         $this->assertFalse(Cache::read('test_groups', 'apc_groups'));
     }
+
+    /**
+     * Test that failed add write return false.
+     *
+     * @return void
+     */
+    public function testAdd()
+    {
+        Cache::delete('test_add_key', 'apc');
+
+        $result = Cache::add('test_add_key', 'test data', 'apc');
+        $this->assertTrue($result);
+
+        $expected = 'test data';
+        $result = Cache::read('test_add_key', 'apc');
+        $this->assertEquals($expected, $result);
+
+        $result = Cache::add('test_add_key', 'test data 2', 'apc');
+        $this->assertFalse($result);
+    }
 }

+ 20 - 0
tests/TestCase/Cache/Engine/FileEngineTest.php

@@ -621,4 +621,24 @@ class FileEngineTest extends TestCase
         $this->assertFalse(Cache::read('key_1', 'file_groups'), 'Did not delete');
         $this->assertFalse(Cache::read('key_2', 'file_groups'), 'Did not delete');
     }
+
+    /**
+     * Test that failed add write return false.
+     *
+     * @return void
+     */
+    public function testAdd()
+    {
+        Cache::delete('test_add_key', 'file_test');
+
+        $result = Cache::add('test_add_key', 'test data', 'file_test');
+        $this->assertTrue($result);
+
+        $expected = 'test data';
+        $result = Cache::read('test_add_key', 'file_test');
+        $this->assertEquals($expected, $result);
+
+        $result = Cache::add('test_add_key', 'test data 2', 'file_test');
+        $this->assertFalse($result);
+    }
 }

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

@@ -916,4 +916,24 @@ class MemcachedEngineTest extends TestCase
         $this->assertTrue(Cache::clearGroup('group_b', 'memcached_groups'));
         $this->assertFalse(Cache::read('test_groups', 'memcached_groups'));
     }
+
+    /**
+     * Test that failed add write return false.
+     *
+     * @return void
+     */
+    public function testAdd()
+    {
+        Cache::delete('test_add_key', 'memcached');
+
+        $result = Cache::add('test_add_key', 'test data', 'memcached');
+        $this->assertTrue($result);
+
+        $expected = 'test data';
+        $result = Cache::read('test_add_key', 'memcached');
+        $this->assertEquals($expected, $result);
+
+        $result = Cache::add('test_add_key', 'test data 2', 'memcached');
+        $this->assertFalse($result);
+    }
 }

+ 20 - 0
tests/TestCase/Cache/Engine/RedisEngineTest.php

@@ -410,4 +410,24 @@ class RedisEngineTest extends TestCase
         $this->assertTrue(Cache::clearGroup('group_b', 'redis_groups'));
         $this->assertFalse(Cache::read('test_groups', 'redis_groups'));
     }
+
+    /**
+     * Test that failed add write return false.
+     *
+     * @return void
+     */
+    public function testAdd()
+    {
+        Cache::delete('test_add_key', 'redis');
+
+        $result = Cache::add('test_add_key', 'test data', 'redis');
+        $this->assertTrue($result);
+
+        $expected = 'test data';
+        $result = Cache::read('test_add_key', 'redis');
+        $this->assertEquals($expected, $result);
+
+        $result = Cache::add('test_add_key', 'test data 2', 'redis');
+        $this->assertFalse($result);
+    }
 }