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));
+ }
}