diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php index d31d66b..b64f000 100644 --- a/framework/yii/caching/RedisCache.php +++ b/framework/yii/caching/RedisCache.php @@ -10,7 +10,7 @@ namespace yii\caching; use yii\redis\Connection; /** - * RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6 or higher. + * RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6.12 or higher. * * RedisCache needs to be configured with [[hostname]], [[port]] and [[database]] of the server * to connect to. By default RedisCache assumes there is a redis server running on localhost at @@ -119,10 +119,7 @@ class RedisCache extends Cache } /** - * Retrieves a value from cache with a specified key. - * This is the implementation of the method declared in the parent class. - * @param string $key a unique key identifying the cached value - * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + * @inheritDocs */ protected function getValue($key) { @@ -130,9 +127,7 @@ class RedisCache extends Cache } /** - * Retrieves multiple values from cache with the specified keys. - * @param array $keys a list of keys identifying the cached values - * @return array a list of cached values indexed by the keys + * @inheritDocs */ protected function getValues($keys) { @@ -146,55 +141,67 @@ class RedisCache extends Cache } /** - * Stores a value identified by a key in cache. - * This is the implementation of the method declared in the parent class. - * - * @param string $key the key identifying the value to be cached - * @param string $value the value to be cached - * @param float $expire the number of seconds in which the cached value will expire. 0 means never expire. - * This can be a floating point number to specify the time in milliseconds. - * @return boolean true if the value is successfully stored into cache, false otherwise + * @inheritDocs */ - protected function setValue($key,$value,$expire) + protected function setValue($key, $value, $expire) { if ($expire == 0) { return (bool) $this->_connection->executeCommand('SET', [$key, $value]); } else { $expire = (int) ($expire * 1000); - return (bool) $this->_connection->executeCommand('PSETEX', [$key, $expire, $value]); + return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire]); } } /** - * Stores a value identified by a key into cache if the cache does not contain this key. - * This is the implementation of the method declared in the parent class. - * - * @param string $key the key identifying the value to be cached - * @param string $value the value to be cached - * @param float $expire the number of seconds in which the cached value will expire. 0 means never expire. - * This can be a floating point number to specify the time in milliseconds. - * @return boolean true if the value is successfully stored into cache, false otherwise + * @inheritDocs */ - protected function addValue($key,$value,$expire) + protected function setValues($data, $expire) { + $args = []; + foreach($data as $key => $value) { + $args[] = $key; + $args[] = $value; + } + + $failedKeys = []; if ($expire == 0) { - return (bool) $this->_connection->executeCommand('SETNX', [$key, $value]); + $this->_connection->executeCommand('MSET', $args); } else { - // TODO consider requiring redis version >= 2.6.12 that supports this in one command $expire = (int) ($expire * 1000); $this->_connection->executeCommand('MULTI'); - $this->_connection->executeCommand('SETNX', [$key, $value]); - $this->_connection->executeCommand('PEXPIRE', [$key, $expire]); - $response = $this->_connection->executeCommand('EXEC'); - return (bool) $response[0]; + $this->_connection->executeCommand('MSET', $args); + $index = []; + foreach ($data as $key => $value) { + $this->_connection->executeCommand('PEXPIRE', [$key, $expire]); + $index[] = $key; + } + $result = $this->_connection->executeCommand('EXEC'); + array_shift($result); + foreach($result as $i => $r) { + if ($r != 1) { + $failedKeys[] = $index[$i]; + } + } + } + return $failedKeys; + } + + /** + * @inheritDocs + */ + protected function addValue($key, $value, $expire) + { + if ($expire == 0) { + return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'NX']); + } else { + $expire = (int) ($expire * 1000); + return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']); } } /** - * Deletes a value with the specified key from cache - * This is the implementation of the method declared in the parent class. - * @param string $key the key of the value to be deleted - * @return boolean if no error happens during deletion + * @inheritDocs */ protected function deleteValue($key) { @@ -202,9 +209,7 @@ class RedisCache extends Cache } /** - * Deletes all values from cache. - * This is the implementation of the method declared in the parent class. - * @return boolean whether the flush operation was successful. + * @inheritDocs */ protected function flushValues() { diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php index adda151..b65ec41 100644 --- a/tests/unit/framework/caching/ApcCacheTest.php +++ b/tests/unit/framework/caching/ApcCacheTest.php @@ -37,4 +37,9 @@ class ApcCacheTest extends CacheTestCase { $this->markTestSkipped("APC keys are expiring only on the next request."); } + + public function testExpireAdd() + { + $this->markTestSkipped("APC keys are expiring only on the next request."); + } } diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 849cad0..afd514d 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -91,7 +91,18 @@ abstract class CacheTestCase extends TestCase $this->assertEquals('array_test', $array['array_test']); } - public function testMset() + /** + * @return array testing mset with and without expiry + */ + public function msetExpiry() + { + return [[0], [2]]; + } + + /** + * @dataProvider msetExpiry + */ + public function testMset($expiry) { $cache = $this->getCacheInstance(); $cache->flush(); @@ -100,7 +111,7 @@ abstract class CacheTestCase extends TestCase 'string_test' => 'string_test', 'number_test' => 42, 'array_test' => ['array_test' => 'array_test'], - ]); + ], $expiry); $this->assertEquals('string_test', $cache->get('string_test')); @@ -170,6 +181,17 @@ abstract class CacheTestCase extends TestCase $this->assertFalse($cache->get('expire_test')); } + public function testExpireAdd() + { + $cache = $this->getCacheInstance(); + + $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); + usleep(500000); + $this->assertEquals('expire_testa', $cache->get('expire_testa')); + usleep(2500000); + $this->assertFalse($cache->get('expire_testa')); + } + public function testAdd() { $cache = $this->prepare(); diff --git a/tests/unit/framework/caching/DbCacheTest.php b/tests/unit/framework/caching/DbCacheTest.php index c3c0233..c2d03e2 100644 --- a/tests/unit/framework/caching/DbCacheTest.php +++ b/tests/unit/framework/caching/DbCacheTest.php @@ -83,4 +83,16 @@ class DbCacheTest extends CacheTestCase static::$time++; $this->assertFalse($cache->get('expire_test')); } + + public function testExpireAdd() + { + $cache = $this->getCacheInstance(); + + static::$time = \time(); + $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); + static::$time++; + $this->assertEquals('expire_testa', $cache->get('expire_testa')); + static::$time++; + $this->assertFalse($cache->get('expire_testa')); + } } diff --git a/tests/unit/framework/caching/FileCacheTest.php b/tests/unit/framework/caching/FileCacheTest.php index 70271c9..f102614 100644 --- a/tests/unit/framework/caching/FileCacheTest.php +++ b/tests/unit/framework/caching/FileCacheTest.php @@ -33,4 +33,16 @@ class FileCacheTest extends CacheTestCase static::$time++; $this->assertFalse($cache->get('expire_test')); } + + public function testExpireAdd() + { + $cache = $this->getCacheInstance(); + + static::$time = \time(); + $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); + static::$time++; + $this->assertEquals('expire_testa', $cache->get('expire_testa')); + static::$time++; + $this->assertFalse($cache->get('expire_testa')); + } } diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index e489a39..63f8be1 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -34,4 +34,12 @@ class MemCacheTest extends CacheTestCase } parent::testExpire(); } + + public function testExpireAdd() + { + if (getenv('TRAVIS') == 'true') { + $this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.'); + } + parent::testExpireAdd(); + } } diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index 57ee110..3a9d415 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -34,4 +34,12 @@ class MemCachedTest extends CacheTestCase } parent::testExpire(); } + + public function testExpireAdd() + { + if (getenv('TRAVIS') == 'true') { + $this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.'); + } + parent::testExpireAdd(); + } } diff --git a/tests/unit/framework/caching/RedisCacheTest.php b/tests/unit/framework/caching/RedisCacheTest.php index b064f78..3201a49 100644 --- a/tests/unit/framework/caching/RedisCacheTest.php +++ b/tests/unit/framework/caching/RedisCacheTest.php @@ -45,6 +45,17 @@ class RedisCacheTest extends CacheTestCase $this->assertFalse($cache->get('expire_test_ms')); } + public function testExpireAddMilliseconds() + { + $cache = $this->getCacheInstance(); + + $this->assertTrue($cache->add('expire_testa_ms', 'expire_testa_ms', 0.2)); + usleep(100000); + $this->assertEquals('expire_testa_ms', $cache->get('expire_testa_ms')); + usleep(300000); + $this->assertFalse($cache->get('expire_testa_ms')); + } + /** * Store a value that is 2 times buffer size big * https://github.com/yiisoft/yii2/issues/743