Cache.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\caching;
  8. use Yii;
  9. use yii\base\Component;
  10. use yii\helpers\StringHelper;
  11. /**
  12. * Cache is the base class for cache classes supporting different cache storage implementation.
  13. *
  14. * A data item can be stored in cache by calling [[set()]] and be retrieved back
  15. * later (in the same or different request) by [[get()]]. In both operations,
  16. * a key identifying the data item is required. An expiration time and/or a [[Dependency|dependency]]
  17. * can also be specified when calling [[set()]]. If the data item expires or the dependency
  18. * changes at the time of calling [[get()]], the cache will return no data.
  19. *
  20. * A typical usage pattern of cache is like the following:
  21. *
  22. * ~~~
  23. * $key = 'demo';
  24. * $data = $cache->get($key);
  25. * if ($data === false) {
  26. * // ...generate $data here...
  27. * $cache->set($key, $data, $expire, $dependency);
  28. * }
  29. * ~~~
  30. *
  31. * Because Cache implements the ArrayAccess interface, it can be used like an array. For example,
  32. *
  33. * ~~~
  34. * $cache['foo'] = 'some data';
  35. * echo $cache['foo'];
  36. * ~~~
  37. *
  38. * Derived classes should implement the following methods:
  39. *
  40. * - [[getValue()]]: retrieve the value with a key (if any) from cache
  41. * - [[setValue()]]: store the value with a key into cache
  42. * - [[addValue()]]: store the value only if the cache does not have this key before
  43. * - [[deleteValue()]]: delete the value with the specified key from cache
  44. * - [[flushValues()]]: delete all values from cache
  45. *
  46. * @author Qiang Xue <[email protected]>
  47. * @since 2.0
  48. */
  49. abstract class Cache extends Component implements \ArrayAccess
  50. {
  51. /**
  52. * @var string a string prefixed to every cache key so that it is unique. If not set,
  53. * it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
  54. * if you don't want to use key prefix. It is recommended that you explicitly set this property to some
  55. * static value if the cached data needs to be shared among multiple applications.
  56. *
  57. * To ensure interoperability, only alphanumeric characters should be used.
  58. */
  59. public $keyPrefix;
  60. /**
  61. * @var array|boolean the functions used to serialize and unserialize cached data. Defaults to null, meaning
  62. * using the default PHP `serialize()` and `unserialize()` functions. If you want to use some more efficient
  63. * serializer (e.g. [igbinary](http://pecl.php.net/package/igbinary)), you may configure this property with
  64. * a two-element array. The first element specifies the serialization function, and the second the deserialization
  65. * function. If this property is set false, data will be directly sent to and retrieved from the underlying
  66. * cache component without any serialization or deserialization. You should not turn off serialization if
  67. * you are using [[Dependency|cache dependency]], because it relies on data serialization.
  68. */
  69. public $serializer;
  70. /**
  71. * Initializes the application component.
  72. * This method overrides the parent implementation by setting default cache key prefix.
  73. */
  74. public function init()
  75. {
  76. parent::init();
  77. if ($this->keyPrefix === null) {
  78. $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
  79. }
  80. }
  81. /**
  82. * Builds a normalized cache key from a given key.
  83. *
  84. * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
  85. * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key
  86. * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]].
  87. *
  88. * @param mixed $key the key to be normalized
  89. * @return string the generated cache key
  90. */
  91. protected function buildKey($key)
  92. {
  93. if (is_string($key)) {
  94. $key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
  95. } else {
  96. $key = md5(json_encode($key));
  97. }
  98. return $this->keyPrefix . $key;
  99. }
  100. /**
  101. * Retrieves a value from cache with a specified key.
  102. * @param mixed $key a key identifying the cached value. This can be a simple string or
  103. * a complex data structure consisting of factors representing the key.
  104. * @return mixed the value stored in cache, false if the value is not in the cache, expired,
  105. * or the dependency associated with the cached data has changed.
  106. */
  107. public function get($key)
  108. {
  109. $key = $this->buildKey($key);
  110. $value = $this->getValue($key);
  111. if ($value === false || $this->serializer === false) {
  112. return $value;
  113. } elseif ($this->serializer === null) {
  114. $value = unserialize($value);
  115. } else {
  116. $value = call_user_func($this->serializer[1], $value);
  117. }
  118. if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
  119. return $value[0];
  120. } else {
  121. return false;
  122. }
  123. }
  124. /**
  125. * Checks whether a specified key exists in the cache.
  126. * This can be faster than getting the value from the cache if the data is big.
  127. * In case a cache does not support this feature natively, this method will try to simulate it
  128. * but has no performance improvement over getting it.
  129. * Note that this method does not check whether the dependency associated
  130. * with the cached data, if there is any, has changed. So a call to [[get]]
  131. * may return false while exists returns true.
  132. * @param mixed $key a key identifying the cached value. This can be a simple string or
  133. * a complex data structure consisting of factors representing the key.
  134. * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
  135. */
  136. public function exists($key)
  137. {
  138. $key = $this->buildKey($key);
  139. $value = $this->getValue($key);
  140. return $value !== false;
  141. }
  142. /**
  143. * Retrieves multiple values from cache with the specified keys.
  144. * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time,
  145. * which may improve the performance. In case a cache does not support this feature natively,
  146. * this method will try to simulate it.
  147. * @param array $keys list of keys identifying the cached values
  148. * @return array list of cached values corresponding to the specified keys. The array
  149. * is returned in terms of (key, value) pairs.
  150. * If a value is not cached or expired, the corresponding array value will be false.
  151. */
  152. public function mget($keys)
  153. {
  154. $keyMap = [];
  155. foreach ($keys as $key) {
  156. $keyMap[$key] = $this->buildKey($key);
  157. }
  158. $values = $this->getValues(array_values($keyMap));
  159. $results = [];
  160. foreach ($keyMap as $key => $newKey) {
  161. $results[$key] = false;
  162. if (isset($values[$newKey])) {
  163. if ($this->serializer === false) {
  164. $results[$key] = $values[$newKey];
  165. } else {
  166. $value = $this->serializer === null ? unserialize($values[$newKey])
  167. : call_user_func($this->serializer[1], $values[$newKey]);
  168. if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
  169. $results[$key] = $value[0];
  170. }
  171. }
  172. }
  173. }
  174. return $results;
  175. }
  176. /**
  177. * Stores a value identified by a key into cache.
  178. * If the cache already contains such a key, the existing value and
  179. * expiration time will be replaced with the new ones, respectively.
  180. *
  181. * @param mixed $key a key identifying the value to be cached. This can be a simple string or
  182. * a complex data structure consisting of factors representing the key.
  183. * @param mixed $value the value to be cached
  184. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  185. * @param Dependency $dependency dependency of the cached item. If the dependency changes,
  186. * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
  187. * This parameter is ignored if [[serializer]] is false.
  188. * @return boolean whether the value is successfully stored into cache
  189. */
  190. public function set($key, $value, $expire = 0, $dependency = null)
  191. {
  192. if ($dependency !== null && $this->serializer !== false) {
  193. $dependency->evaluateDependency($this);
  194. }
  195. if ($this->serializer === null) {
  196. $value = serialize([$value, $dependency]);
  197. } elseif ($this->serializer !== false) {
  198. $value = call_user_func($this->serializer[0], [$value, $dependency]);
  199. }
  200. $key = $this->buildKey($key);
  201. return $this->setValue($key, $value, $expire);
  202. }
  203. /**
  204. * Stores multiple items in cache. Each item contains a value identified by a key.
  205. * If the cache already contains such a key, the existing value and
  206. * expiration time will be replaced with the new ones, respectively.
  207. *
  208. * @param array $items the items to be cached, as key-value pairs.
  209. * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire.
  210. * @param Dependency $dependency dependency of the cached items. If the dependency changes,
  211. * the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
  212. * This parameter is ignored if [[serializer]] is false.
  213. * @return boolean whether the items are successfully stored into cache
  214. */
  215. public function mset($items, $expire = 0, $dependency = null)
  216. {
  217. if ($dependency !== null && $this->serializer !== false) {
  218. $dependency->evaluateDependency($this);
  219. }
  220. $data = [];
  221. foreach ($items as $key => $value) {
  222. $itemKey = $this->buildKey($key);
  223. if ($this->serializer === null) {
  224. $itemValue = serialize([$value, $dependency]);
  225. } elseif ($this->serializer !== false) {
  226. $itemValue = call_user_func($this->serializer[0], [$value, $dependency]);
  227. }
  228. $data[$itemKey] = $itemValue;
  229. }
  230. return $this->setValues($data, $expire);
  231. }
  232. /**
  233. * Stores multiple items in cache. Each item contains a value identified by a key.
  234. * If the cache already contains such a key, the existing value and expiration time will be preserved.
  235. *
  236. * @param array $items the items to be cached, as key-value pairs.
  237. * @param integer $expire default number of seconds in which the cached values will expire. 0 means never expire.
  238. * @param Dependency $dependency dependency of the cached items. If the dependency changes,
  239. * the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
  240. * This parameter is ignored if [[serializer]] is false.
  241. * @return boolean whether the items are successfully stored into cache
  242. */
  243. public function madd($items, $expire = 0, $dependency = null)
  244. {
  245. if ($dependency !== null && $this->serializer !== false) {
  246. $dependency->evaluateDependency($this);
  247. }
  248. $data = [];
  249. foreach ($items as $key => $value) {
  250. $itemKey = $this->buildKey($key);
  251. if ($this->serializer === null) {
  252. $itemValue = serialize([$value, $dependency]);
  253. } elseif ($this->serializer !== false) {
  254. $itemValue = call_user_func($this->serializer[0], [$value, $dependency]);
  255. }
  256. $data[$itemKey] = $itemValue;
  257. }
  258. return $this->addValues($data, $expire);
  259. }
  260. /**
  261. * Stores a value identified by a key into cache if the cache does not contain this key.
  262. * Nothing will be done if the cache already contains the key.
  263. * @param mixed $key a key identifying the value to be cached. This can be a simple string or
  264. * a complex data structure consisting of factors representing the key.
  265. * @param mixed $value the value to be cached
  266. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  267. * @param Dependency $dependency dependency of the cached item. If the dependency changes,
  268. * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
  269. * This parameter is ignored if [[serializer]] is false.
  270. * @return boolean whether the value is successfully stored into cache
  271. */
  272. public function add($key, $value, $expire = 0, $dependency = null)
  273. {
  274. if ($dependency !== null && $this->serializer !== false) {
  275. $dependency->evaluateDependency($this);
  276. }
  277. if ($this->serializer === null) {
  278. $value = serialize([$value, $dependency]);
  279. } elseif ($this->serializer !== false) {
  280. $value = call_user_func($this->serializer[0], [$value, $dependency]);
  281. }
  282. $key = $this->buildKey($key);
  283. return $this->addValue($key, $value, $expire);
  284. }
  285. /**
  286. * Deletes a value with the specified key from cache
  287. * @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or
  288. * a complex data structure consisting of factors representing the key.
  289. * @return boolean if no error happens during deletion
  290. */
  291. public function delete($key)
  292. {
  293. $key = $this->buildKey($key);
  294. return $this->deleteValue($key);
  295. }
  296. /**
  297. * Deletes all values from cache.
  298. * Be careful of performing this operation if the cache is shared among multiple applications.
  299. * @return boolean whether the flush operation was successful.
  300. */
  301. public function flush()
  302. {
  303. return $this->flushValues();
  304. }
  305. /**
  306. * Retrieves a value from cache with a specified key.
  307. * This method should be implemented by child classes to retrieve the data
  308. * from specific cache storage.
  309. * @param string $key a unique key identifying the cached value
  310. * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
  311. */
  312. abstract protected function getValue($key);
  313. /**
  314. * Stores a value identified by a key in cache.
  315. * This method should be implemented by child classes to store the data
  316. * in specific cache storage.
  317. * @param string $key the key identifying the value to be cached
  318. * @param string $value the value to be cached
  319. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  320. * @return boolean true if the value is successfully stored into cache, false otherwise
  321. */
  322. abstract protected function setValue($key, $value, $expire);
  323. /**
  324. * Stores a value identified by a key into cache if the cache does not contain this key.
  325. * This method should be implemented by child classes to store the data
  326. * in specific cache storage.
  327. * @param string $key the key identifying the value to be cached
  328. * @param string $value the value to be cached
  329. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  330. * @return boolean true if the value is successfully stored into cache, false otherwise
  331. */
  332. abstract protected function addValue($key, $value, $expire);
  333. /**
  334. * Deletes a value with the specified key from cache
  335. * This method should be implemented by child classes to delete the data from actual cache storage.
  336. * @param string $key the key of the value to be deleted
  337. * @return boolean if no error happens during deletion
  338. */
  339. abstract protected function deleteValue($key);
  340. /**
  341. * Deletes all values from cache.
  342. * Child classes may implement this method to realize the flush operation.
  343. * @return boolean whether the flush operation was successful.
  344. */
  345. abstract protected function flushValues();
  346. /**
  347. * Retrieves multiple values from cache with the specified keys.
  348. * The default implementation calls [[getValue()]] multiple times to retrieve
  349. * the cached values one by one. If the underlying cache storage supports multiget,
  350. * this method should be overridden to exploit that feature.
  351. * @param array $keys a list of keys identifying the cached values
  352. * @return array a list of cached values indexed by the keys
  353. */
  354. protected function getValues($keys)
  355. {
  356. $results = [];
  357. foreach ($keys as $key) {
  358. $results[$key] = $this->getValue($key);
  359. }
  360. return $results;
  361. }
  362. /**
  363. * Stores multiple key-value pairs in cache.
  364. * The default implementation calls [[setValue()]] multiple times store values one by one. If the underlying cache
  365. * storage supports multiset, this method should be overridden to exploit that feature.
  366. * @param array $data array where key corresponds to cache key while value is the value stored
  367. * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
  368. * @return array array of failed keys
  369. */
  370. protected function setValues($data, $expire)
  371. {
  372. $failedKeys = [];
  373. foreach ($data as $key => $value)
  374. {
  375. if ($this->setValue($key, $value, $expire) === false) {
  376. $failedKeys[] = $key;
  377. }
  378. }
  379. return $failedKeys;
  380. }
  381. /**
  382. * Adds multiple key-value pairs to cache.
  383. * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache
  384. * storage supports multiadd, this method should be overridden to exploit that feature.
  385. * @param array $data array where key corresponds to cache key while value is the value stored
  386. * @param integer $expire the number of seconds in which the cached values will expire. 0 means never expire.
  387. * @return array array of failed keys
  388. */
  389. protected function addValues($data, $expire)
  390. {
  391. $failedKeys = [];
  392. foreach ($data as $key => $value)
  393. {
  394. if ($this->addValue($key, $value, $expire) === false) {
  395. $failedKeys[] = $key;
  396. }
  397. }
  398. return $failedKeys;
  399. }
  400. /**
  401. * Returns whether there is a cache entry with a specified key.
  402. * This method is required by the interface ArrayAccess.
  403. * @param string $key a key identifying the cached value
  404. * @return boolean
  405. */
  406. public function offsetExists($key)
  407. {
  408. return $this->get($key) !== false;
  409. }
  410. /**
  411. * Retrieves the value from cache with a specified key.
  412. * This method is required by the interface ArrayAccess.
  413. * @param string $key a key identifying the cached value
  414. * @return mixed the value stored in cache, false if the value is not in the cache or expired.
  415. */
  416. public function offsetGet($key)
  417. {
  418. return $this->get($key);
  419. }
  420. /**
  421. * Stores the value identified by a key into cache.
  422. * If the cache already contains such a key, the existing value will be
  423. * replaced with the new ones. To add expiration and dependencies, use the [[set()]] method.
  424. * This method is required by the interface ArrayAccess.
  425. * @param string $key the key identifying the value to be cached
  426. * @param mixed $value the value to be cached
  427. */
  428. public function offsetSet($key, $value)
  429. {
  430. $this->set($key, $value);
  431. }
  432. /**
  433. * Deletes the value with the specified key from cache
  434. * This method is required by the interface ArrayAccess.
  435. * @param string $key the key of the value to be deleted
  436. */
  437. public function offsetUnset($key)
  438. {
  439. $this->delete($key);
  440. }
  441. }