You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
15 KiB
415 lines
15 KiB
<?php |
|
/** |
|
* @link http://www.yiiframework.com/ |
|
* @copyright Copyright (c) 2008 Yii Software LLC |
|
* @license http://www.yiiframework.com/license/ |
|
*/ |
|
|
|
namespace yii\caching; |
|
|
|
use Yii; |
|
use yii\base\Component; |
|
use yii\di\Instance; |
|
use yii\helpers\StringHelper; |
|
|
|
/** |
|
* Cache provides support for the data caching, including cache key composition and dependencies. |
|
* The actual data caching is performed via [[handler]], which should be configured to be [[\Psr\SimpleCache\CacheInterface]] |
|
* instance. |
|
* |
|
* Application configuration example: |
|
* |
|
* ```php |
|
* return [ |
|
* 'components' => [ |
|
* 'cache' => [ |
|
* '__class' => yii\caching\Cache::class, |
|
* 'handler' => [ |
|
* '__class' => yii\caching\FileCache::class, |
|
* 'cachePath' => '@runtime/cache', |
|
* ], |
|
* ], |
|
* // ... |
|
* ], |
|
* // ... |
|
* ]; |
|
* ``` |
|
* |
|
* A data item can be stored in the cache by calling [[set()]] and be retrieved back |
|
* later (in the same or different request) by [[get()]]. In both operations, |
|
* a key identifying the data item is required. An expiration time and/or a [[Dependency|dependency]] |
|
* can also be specified when calling [[set()]]. If the data item expires or the dependency |
|
* changes at the time of calling [[get()]], the cache will return no data. |
|
* |
|
* A typical usage pattern of cache is like the following: |
|
* |
|
* ```php |
|
* $key = 'demo'; |
|
* $data = $cache->get($key); |
|
* if ($data === null) { |
|
* // ...generate $data here... |
|
* $cache->set($key, $data, $duration, $dependency); |
|
* } |
|
* ``` |
|
* |
|
* Because Cache implements the [[\ArrayAccess]] interface, it can be used like an array. For example, |
|
* |
|
* ```php |
|
* $cache['foo'] = 'some data'; |
|
* echo $cache['foo']; |
|
* ``` |
|
* |
|
* For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview) |
|
* and [PSR-16 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md). |
|
* |
|
* @author Qiang Xue <qiang.xue@gmail.com> |
|
* @since 2.0 |
|
*/ |
|
class Cache extends Component implements CacheInterface |
|
{ |
|
/** |
|
* @var \Psr\SimpleCache\CacheInterface|array|\Closure|string actual cache handler or its DI compatible configuration. |
|
* After the Cache object is created, if you want to change this property, you should only assign it |
|
* with a [[\Psr\SimpleCache\CacheInterface]] instance. |
|
* @since 3.0.0 |
|
*/ |
|
public $handler; |
|
|
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function init() |
|
{ |
|
parent::init(); |
|
$this->handler = Instance::ensure($this->handler instanceof \Closure ? call_user_func($this->handler) : $this->handler, \Psr\SimpleCache\CacheInterface::class); |
|
} |
|
|
|
/** |
|
* Builds a normalized cache key from a given key. |
|
* |
|
* If the given key is a string containing alphanumeric characters only and no more than 32 characters, |
|
* then the key will be returned back as it is. Otherwise, a normalized key is generated by serializing |
|
* the given key and applying MD5 hashing. |
|
* |
|
* @param mixed $key the key to be normalized |
|
* @return string the generated cache key |
|
*/ |
|
protected function buildKey($key) |
|
{ |
|
if (is_string($key)) { |
|
return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key); |
|
} |
|
return md5(json_encode($key)); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function get($key, $default = null) |
|
{ |
|
$key = $this->buildKey($key); |
|
$value = $this->handler->get($key); |
|
|
|
if ($value === null) { |
|
return $default; |
|
} |
|
|
|
if (is_array($value) && isset($value[1]) && $value[1] instanceof Dependency) { |
|
if ($value[1]->isChanged($this)) { |
|
return $default; |
|
} |
|
return $value[0]; |
|
} |
|
|
|
return $value; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function has($key) |
|
{ |
|
$key = $this->buildKey($key); |
|
return $this->handler->has($key); |
|
} |
|
|
|
/** |
|
* Retrieves multiple values from cache with the specified keys. |
|
* Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time, |
|
* which may improve the performance. In case a cache does not support this feature natively, |
|
* this method will try to simulate it. |
|
* @param string[] $keys list of string keys identifying the cached values |
|
* @param mixed $default Default value to return for keys that do not exist. |
|
* @return array list of cached values corresponding to the specified keys. The array |
|
* is returned in terms of (key, value) pairs. |
|
* If a value is not cached or expired, the corresponding array value will be false. |
|
* @since 2.0.7 |
|
*/ |
|
public function getMultiple($keys, $default = null) |
|
{ |
|
$keyMap = []; |
|
foreach ($keys as $key) { |
|
$keyMap[$key] = $this->buildKey($key); |
|
} |
|
$values = $this->handler->getMultiple(array_values($keyMap)); |
|
$results = []; |
|
foreach ($keyMap as $key => $newKey) { |
|
$results[$key] = $default; |
|
if (isset($values[$newKey])) { |
|
$value = $values[$newKey]; |
|
if (is_array($value) && isset($value[1]) && $value[1] instanceof Dependency) { |
|
if ($value[1]->isChanged($this)) { |
|
continue; |
|
} else { |
|
$value = $value[0]; |
|
} |
|
} |
|
$results[$key] = $value; |
|
} |
|
} |
|
|
|
return $results; |
|
} |
|
|
|
/** |
|
* Stores a value identified by a key into cache. |
|
* If the cache already contains such a key, the existing value and |
|
* expiration time will be replaced with the new ones, respectively. |
|
* |
|
* @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 mixed $value the value to be cached |
|
* @param null|int|\DateInterval $ttl the TTL value of this item. If not set, default value is 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 bool whether the value is successfully stored into cache |
|
*/ |
|
public function set($key, $value, $ttl = null, $dependency = null) |
|
{ |
|
if ($dependency !== null) { |
|
$dependency->evaluateDependency($this); |
|
$value = [$value, $dependency]; |
|
} |
|
$key = $this->buildKey($key); |
|
return $this->handler->set($key, $value, $ttl); |
|
} |
|
|
|
/** |
|
* Stores multiple items in cache. Each item contains a value identified by a key. |
|
* If the cache already contains such a key, the existing value and |
|
* expiration time will be replaced with the new ones, respectively. |
|
* |
|
* @param array $items the items to be cached, as key-value pairs. |
|
* @param null|int|\DateInterval $ttl the TTL value of this item. If not set, default value is used. |
|
* @param Dependency $dependency dependency of the cached items. If the dependency changes, |
|
* the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. |
|
* This parameter is ignored if [[serializer]] is false. |
|
* @return array array of failed keys |
|
* @since 2.0.7 |
|
*/ |
|
public function setMultiple($items, $ttl = 0, $dependency = null) |
|
{ |
|
if ($dependency !== null) { |
|
$dependency->evaluateDependency($this); |
|
} |
|
|
|
$data = []; |
|
foreach ($items as $key => $value) { |
|
if ($dependency !== null) { |
|
$value = [$value, $dependency]; |
|
} |
|
$key = $this->buildKey($key); |
|
$data[$key] = $value; |
|
} |
|
|
|
return $this->handler->setMultiple($data, $ttl); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
* @since 3.0.0 |
|
*/ |
|
public function deleteMultiple($keys) |
|
{ |
|
$actualKeys = []; |
|
foreach ($keys as $key) { |
|
$actualKeys[] = $this->buildKey($key); |
|
} |
|
return $this->handler->deleteMultiple($actualKeys); |
|
} |
|
|
|
/** |
|
* Stores multiple items in cache. Each item contains a value identified by a key. |
|
* If the cache already contains such a key, the existing value and expiration time will be preserved. |
|
* |
|
* @param array $values the items to be cached, as key-value pairs. |
|
* @param null|int|\DateInterval $ttl the TTL value of this item. If not set, default value is used. |
|
* @param Dependency $dependency dependency of the cached items. If the dependency changes, |
|
* the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. |
|
* This parameter is ignored if [[serializer]] is false. |
|
* @return array array of failed keys |
|
* @since 2.0.7 |
|
*/ |
|
public function addMultiple($values, $ttl = 0, $dependency = null) |
|
{ |
|
if ($dependency !== null) { |
|
$dependency->evaluateDependency($this); |
|
} |
|
|
|
$data = []; |
|
foreach ($values as $key => $value) { |
|
if ($dependency !== null) { |
|
$value = [$value, $dependency]; |
|
} |
|
|
|
$key = $this->buildKey($key); |
|
$data[$key] = $value; |
|
} |
|
|
|
$existingValues = $this->handler->getMultiple(array_keys($data)); |
|
foreach ($existingValues as $key => $value) { |
|
if ($value !== null) { |
|
unset($data[$key]); |
|
} |
|
} |
|
return $this->handler->setMultiple($data, $ttl); |
|
} |
|
|
|
/** |
|
* Stores a value identified by a key into cache if the cache does not contain this key. |
|
* Nothing will be done if the cache already contains the key. |
|
* @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 mixed $value the value to be cached |
|
* @param null|int|\DateInterval $ttl the TTL value of this item. If not set, default value is 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 bool whether the value is successfully stored into cache |
|
*/ |
|
public function add($key, $value, $ttl = null, $dependency = null) |
|
{ |
|
if ($dependency !== null) { |
|
$dependency->evaluateDependency($this); |
|
$value = [$value, $dependency]; |
|
} |
|
|
|
$key = $this->buildKey($key); |
|
|
|
if ($this->handler->has($key)) { |
|
return false; |
|
} |
|
|
|
return $this->handler->set($key, $value, $ttl); |
|
} |
|
|
|
/** |
|
* Deletes a value with the specified key from cache. |
|
* @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or |
|
* a complex data structure consisting of factors representing the key. |
|
* @return bool if no error happens during deletion |
|
*/ |
|
public function delete($key) |
|
{ |
|
$key = $this->buildKey($key); |
|
|
|
return $this->handler->delete($key); |
|
} |
|
|
|
/** |
|
* Deletes all values from cache. |
|
* Be careful of performing this operation if the cache is shared among multiple applications. |
|
* @return bool whether the flush operation was successful. |
|
*/ |
|
public function clear() |
|
{ |
|
return $this->handler->clear(); |
|
} |
|
|
|
/** |
|
* Returns whether there is a cache entry with a specified key. |
|
* This method is required by the interface [[\ArrayAccess]]. |
|
* @param string $key a key identifying the cached value |
|
* @return bool |
|
*/ |
|
public function offsetExists($key) |
|
{ |
|
return $this->get($key) !== false; |
|
} |
|
|
|
/** |
|
* Retrieves the value from cache with a specified key. |
|
* This method is required by the interface [[\ArrayAccess]]. |
|
* @param string $key a key identifying the cached value |
|
* @return mixed the value stored in cache, false if the value is not in the cache or expired. |
|
*/ |
|
public function offsetGet($key) |
|
{ |
|
return $this->get($key); |
|
} |
|
|
|
/** |
|
* Stores the value identified by a key into cache. |
|
* If the cache already contains such a key, the existing value will be |
|
* replaced with the new ones. To add expiration and dependencies, use the [[set()]] method. |
|
* This method is required by the interface [[\ArrayAccess]]. |
|
* @param string $key the key identifying the value to be cached |
|
* @param mixed $value the value to be cached |
|
*/ |
|
public function offsetSet($key, $value) |
|
{ |
|
$this->set($key, $value); |
|
} |
|
|
|
/** |
|
* Deletes the value with the specified key from cache |
|
* This method is required by the interface [[\ArrayAccess]]. |
|
* @param string $key the key of the value to be deleted |
|
*/ |
|
public function offsetUnset($key) |
|
{ |
|
$this->delete($key); |
|
} |
|
|
|
/** |
|
* Method combines both [[set()]] and [[get()]] methods to retrieve value identified by a $key, |
|
* or to store the result of $callable 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 callable|\Closure $callable the callable or closure that will be used to generate a value to be cached. |
|
* In case $callable returns `false`, the value will not be cached. |
|
* @param null|int|\DateInterval $ttl the TTL value of this item. If not set, default value is 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 $callable execution |
|
* @since 2.0.11 |
|
*/ |
|
public function getOrSet($key, $callable, $ttl = null, $dependency = null) |
|
{ |
|
if (($value = $this->get($key)) !== null) { |
|
return $value; |
|
} |
|
|
|
$value = call_user_func($callable, $this); |
|
if (!$this->set($key, $value, $ttl, $dependency)) { |
|
Yii::warning('Failed to set cache value for key ' . json_encode($key), __METHOD__); |
|
} |
|
|
|
return $value; |
|
} |
|
}
|
|
|