diff --git a/docs/guide-ru/caching-data.md b/docs/guide-ru/caching-data.md index 60ce0abcca..47c37edcf7 100644 --- a/docs/guide-ru/caching-data.md +++ b/docs/guide-ru/caching-data.md @@ -11,8 +11,8 @@ $data = $cache->get($key); if ($data === false) { - - // $data нет в кэше, считаем с нуля. + // $data нет в кэше, вычисляем заново + $data = $this->calculateSomething(); // Сохраняем значение $data в кэше. Данные можно получить в следующий раз. $cache->set($key, $data); @@ -21,6 +21,33 @@ if ($data === false) { // Значение $data доступно здесь. ``` +Начиная с версии 2.0.11, [компонент кэширования](#cache-components) предоставляет метод +[[yii\caching\Cache::getOrSet()|getOrSet()]], который упрощает код при получении, вычислении и сохранении данных. +Приведённый ниже код делает в точности то же самое, что и код в предыдущем примере: + +```php +$data = $cache->getOrSet($key, function () { + return $this->calculateSomething(); +}); +``` + +Если в кэше есть данные по ключу `$key`, они будут сразу возвращены. +Иначе, будет вызвана переданная анонимная функция, вычисляющаяя значение, которое будет сохранено в кэш и возвращено +из метода. + +В случае, когда анонимной функции требуются данные из внешней области видимости, можно передать их с помощью +оператора `use`. Например: + +```php +$user_id = 42; +$data = $cache->getOrSet($key, function () use ($user_id) { + return $this->calculateSomething($user_id); +}); +``` + +> Note: В [[yii\caching\Cache::getOrSet()|getOrSet()]] можно передать срока действия и зависимости кэша. + Прочтите [Срок действия кэша](#cache-expiration) и [Зависимости кеша](#cache-dependencies) чтобы узнать больше. + ## Компоненты кэширования @@ -161,8 +188,8 @@ if ($data === false) { } ``` -Начиная с версии 2.0.11 вы можете изменить значение по умолчанию (бесконечность) для длительности кеширования задав -[[yii\caching\Cache::$defaultDuration|defaultDuration]] в конфигурации компонента кеша. Таким образом, можно будет +Начиная с версии 2.0.11 вы можете изменить значение по умолчанию (бесконечность) для длительности кэширования задав +[[yii\caching\Cache::$defaultDuration|defaultDuration]] в конфигурации компонента кэша. Таким образом, можно будет не передавать значение `duration` в [[yii\caching\Cache::set()|set()]] каждый раз. ### Зависимости кэша @@ -231,7 +258,7 @@ $result = Customer::getDb()->cache(function ($db) { - `yii cache`: отображает список доступных кэширующих компонентов приложения - `yii cache/flush cache1 cache2`: очищает кэш в компонентах `cache1`, `cache2` (можно передать несколько названий компонентов кэширования, разделяя их пробелом) - - `yii cache/flush-all`: очищает кэш во всех кеширующих компонентах приложения + - `yii cache/flush-all`: очищает кэш во всех кэширующих компонентах приложения > Info: Консольное приложение использует отдельный конфигурационный файл по умолчанию. Для получения должного результата, убедитесь, что в конфигурациях консольного и веб-приложения у вас одинаковые компоненты кэширования. @@ -311,4 +338,4 @@ $result = $db->cache(function ($db) { Кэширование запросов не работает с результатами запросов, которые содержат обработчики ресурсов. Например, при использовании типа столбца `BLOB` в некоторых СУБД, в качестве результата запроса будет выведен ресурс обработчик данных столбца. -Некоторые кэш хранилища имеют ограничение в размере данных. Например, Memcache ограничивает максимальный размер каждой записи до 1 Мб. Таким образом, если результат запроса превышает этот предел, данные не будут закешированы. +Некоторые кэш хранилища имеют ограничение в размере данных. Например, Memcache ограничивает максимальный размер каждой записи до 1 Мб. Таким образом, если результат запроса превышает этот предел, данные не будут закэшированы. diff --git a/docs/guide/caching-data.md b/docs/guide/caching-data.md index 72b36bee9d..d0b4ab72b9 100644 --- a/docs/guide/caching-data.md +++ b/docs/guide/caching-data.md @@ -13,8 +13,8 @@ a [cache component](#cache-components): $data = $cache->get($key); if ($data === false) { - // $data is not found in cache, calculate it from scratch + $data = $this->calculateSomething(); // store $data in cache so that it can be retrieved next time $cache->set($key, $data); @@ -23,6 +23,32 @@ if ($data === false) { // $data is available here ``` +Since version 2.0.11, [cache component](#cache-components) provides [[yii\caching\Cache::getOrSet()|getOrSet()]] method +that simplifies code for data getting, calculating and storing. The following code does exactly the same as the +previous example: + +```php +$data = $cache->getOrSet($key, function () { + return $this->calculateSomething(); +}); +``` + +When cache has data associated with the `$key`, the cached value will be returned. +Otherwise, the passed anonymous function will be executed to calculate the value that will be cached and returned. + +If the anonymous function requires some data from the outer scope, you can pass it with the `use` statement. +For example: + +```php +$user_id = 42; +$data = $cache->getOrSet($key, function () use ($user_id) { + return $this->calculateSomething($user_id); +}); +``` + +> Note: [[yii\caching\Cache::getOrSet()|getOrSet()]] method supports duration and dependencies as well. + See [Cache Expiration](#cache-expiration) and [Cache Dependencies](#cache-dependencies) to know more. + ## Cache Components @@ -118,6 +144,8 @@ All cache components have the same base class [[yii\caching\Cache]] and thus sup value will be returned if the data item is not found in the cache or is expired/invalidated. * [[yii\caching\Cache::set()|set()]]: stores a data item identified by a key in cache. * [[yii\caching\Cache::add()|add()]]: stores a data item identified by a key in cache if the key is not found in the cache. +* [[yii\caching\Cache::getOrSet()|getOrSet()]]: retrieves a data item from cache with a specified key or executes passed + callback, stores return of the callback in a cache by a key and returns that data. * [[yii\caching\Cache::multiGet()|multiGet()]]: retrieves multiple data items from cache with the specified keys. * [[yii\caching\Cache::multiSet()|multiSet()]]: stores multiple data items in cache. Each item is identified by a key. * [[yii\caching\Cache::multiAdd()|multiAdd()]]: stores multiple data items in cache. Each item is identified by a key. @@ -128,7 +156,7 @@ All cache components have the same base class [[yii\caching\Cache]] and thus sup > Note: Do not cache a `false` boolean value directly because the [[yii\caching\Cache::get()|get()]] method uses `false` return value to indicate the data item is not found in the cache. You may put `false` in an array and cache -this array instead to avoid this problem. +this array instead to avoid this problem. Some cache storage, such as MemCache, APC, support retrieving multiple cached values in a batch mode, which may reduce the overhead involved in retrieving cached data. The APIs [[yii\caching\Cache::multiGet()|multiGet()]] diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 159d03a608..877a567853 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -85,9 +85,11 @@ Yii Framework 2 Change Log - Enh #13074: Improved `yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options (timbeks) - Bug: #12969: Improved unique ID generation for `yii\widgets\Pjax` widgets (dynasource, samdark, rob006) - Enh #13122: Optimized query for information about foreign keys in `yii\db\oci` (zlakomanoff) +- Enh #11959: Added `yii\caching\Cache::getOrSet()` method (silverfire) - Enh #13202: Refactor validateAttribute method in UniqueValidator (developeruz) - Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe) +- Enh #11959: Added `yii\caching\Cache::closure()` method to cache closures (silverfire) 2.0.10 October 20, 2016 ----------------------- diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php index 4cc32d0ceb..4693d185b7 100644 --- a/framework/caching/Cache.php +++ b/framework/caching/Cache.php @@ -7,6 +7,7 @@ namespace yii\caching; +use Yii; use yii\base\Component; use yii\helpers\StringHelper; @@ -537,4 +538,45 @@ abstract class Cache extends Component implements \ArrayAccess { $this->delete($key); } + + /** + * Method combines both [[set()]] and [[get()]] methods to retrieve value identified by a $key, + * or to store the result of $closure execution if there is no cache available for the $key. + * + * Usage example: + * + * ```php + * public function getTopProducts($count = 10) { + * $cache = $this->cache; // Could be Yii::$app->cache + * return $cache->getOrSet(['top-n-products', 'n' => $count], function ($cache) use ($count) { + * return Products::find()->mostPopular()->limit(10)->all(); + * }, 1000); + * } + * ``` + * + * @param mixed $key a key identifying the value to be cached. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @param \Closure $closure the closure that will be used to generate a value to be cached. + * In case $closure returns `false`, the value will not be cached. + * @param int $duration default duration in seconds before the cache will expire. If not set, + * [[defaultDuration]] value will be used. + * @param Dependency $dependency dependency of the cached item. If the dependency changes, + * the corresponding value in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is `false`. + * @return mixed result of $closure execution + * @since 2.0.11 + */ + public function getOrSet($key, \Closure $closure, $duration = null, $dependency = null) + { + if (($value = $this->get($key)) !== false) { + return $value; + } + + $value = call_user_func($closure, $this); + if (!$this->set($key, $value, $duration, $dependency)) { + Yii::warning('Failed to set cache value for key ' . json_encode($value), __METHOD__); + } + + return $value; + } } diff --git a/tests/framework/caching/CacheTestCase.php b/tests/framework/caching/CacheTestCase.php index f4ce73b82b..a9b3004562 100644 --- a/tests/framework/caching/CacheTestCase.php +++ b/tests/framework/caching/CacheTestCase.php @@ -24,6 +24,7 @@ function microtime($float = false) namespace yiiunit\framework\caching; use yii\caching\Cache; +use yii\caching\TagDependency; use yiiunit\TestCase; /** @@ -99,22 +100,22 @@ abstract class CacheTestCase extends TestCase } /** - * @return array testing mset with and without expiry + * @return array testing multiSet with and without expiry */ - public function msetExpiry() + public function multiSetExpiry() { return [[0], [2]]; } /** - * @dataProvider msetExpiry + * @dataProvider multiSetExpiry */ - public function testMset($expiry) + public function testMultiset($expiry) { $cache = $this->getCacheInstance(); $cache->flush(); - $cache->mset([ + $cache->multiSet([ 'string_test' => 'string_test', 'number_test' => 42, 'array_test' => ['array_test' => 'array_test'], @@ -167,14 +168,14 @@ abstract class CacheTestCase extends TestCase $this->assertTrue($cache->get('bool_value')); } - public function testMget() + public function testMultiGet() { $cache = $this->prepare(); - $this->assertEquals(['string_test' => 'string_test', 'number_test' => 42], $cache->mget(['string_test', 'number_test'])); + $this->assertEquals(['string_test' => 'string_test', 'number_test' => 42], $cache->multiGet(['string_test', 'number_test'])); // ensure that order does not matter - $this->assertEquals(['number_test' => 42, 'string_test' => 'string_test'], $cache->mget(['number_test', 'string_test'])); - $this->assertEquals(['number_test' => 42, 'non_existent_key' => null], $cache->mget(['number_test', 'non_existent_key'])); + $this->assertEquals(['number_test' => 42, 'string_test' => 'string_test'], $cache->multiGet(['number_test', 'string_test'])); + $this->assertEquals(['number_test' => 42, 'non_existent_key' => null], $cache->multiGet(['number_test', 'non_existent_key'])); } public function testDefaultTtl() @@ -220,13 +221,13 @@ abstract class CacheTestCase extends TestCase $this->assertEquals(13, $cache->get('add_test')); } - public function testMadd() + public function testMultiAdd() { $cache = $this->prepare(); $this->assertFalse($cache->get('add_test')); - $cache->madd([ + $cache->multiAdd([ 'number_test' => 13, 'add_test' => 13, ]); @@ -250,4 +251,22 @@ abstract class CacheTestCase extends TestCase $this->assertTrue($cache->flush()); $this->assertFalse($cache->get('number_test')); } + + public function testGetOrSet() + { + $cache = $this->prepare(); + $dependency = new TagDependency(['tags' => 'test']); + + $expected = 'SilverFire'; + $loginClosure = function ($cache) use (&$login) { return 'SilverFire'; }; + $this->assertEquals($expected, $cache->getOrSet('some-login', $loginClosure, null, $dependency)); + + // Call again with another login to make sure that value is cached + $loginClosure = function ($cache) use (&$login) { return 'SamDark'; }; + $this->assertEquals($expected, $cache->getOrSet('some-login', $loginClosure, null, $dependency)); + + $dependency->invalidate($cache, 'test'); + $expected = 'SamDark'; + $this->assertEquals($expected, $cache->getOrSet('some-login', $loginClosure, null, $dependency)); + } }