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.

469 line
19KB

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