Redis.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\storage\cache\adapter;
  9. use Redis as RedisCore;
  10. /**
  11. * A Redis (phpredis) cache adapter implementation.
  12. *
  13. * This adapter uses the `phpredis` PHP extension, which can be found here:
  14. * https://github.com/nicolasff/phpredis
  15. *
  16. * The Redis cache adapter is meant to be used through the `Cache` interface,
  17. * which abstracts away key generation, adapter instantiation and filter
  18. * implementation. This adapter does not aim to provide a full implementation of the
  19. * Redis API, but rather only a subset of its features that are useful in the context of a
  20. * semi-persistent cache.
  21. *
  22. * A simple configuration of this adapter can be accomplished in `config/bootstrap/cache.php`
  23. * as follows:
  24. *
  25. * {{{
  26. * Cache::config(array(
  27. * 'cache-config-name' => array(
  28. * 'adapter' => 'Redis',
  29. * 'host' => '127.0.0.1:6379'
  30. * )
  31. * ));
  32. * }}}
  33. *
  34. * The 'host' key accepts a string argument in the format of ip:port where the Redis
  35. * server can be found.
  36. *
  37. * This Redis adapter provides basic support for `write`, `read`, `delete`
  38. * and `clear` cache functionality, as well as allowing the first four
  39. * methods to be filtered as per the Lithium filtering system.
  40. *
  41. * @see lithium\storage\Cache::key()
  42. * @see lithium\storage\Cache::adapter()
  43. * @link https://github.com/nicolasff/phpredis GitHub: PhpRedis Extension
  44. *
  45. */
  46. class Redis extends \lithium\core\Object {
  47. /**
  48. * Redis object instance used by this adapter.
  49. *
  50. * @var object Redis object
  51. */
  52. public $connection;
  53. /**
  54. * Object constructor
  55. *
  56. * Instantiates the `Redis` object and connects it to the configured server.
  57. *
  58. * @todo Implement configurable & optional authentication
  59. * @see lithium\storage\Cache::config()
  60. * @see lithium\storage\cache\adapter\Redis::_ttl()
  61. * @param array $config Configuration parameters for this cache adapter.
  62. * These settings are indexed by name and queryable through `Cache::config('name')`. The
  63. * available settings for this adapter are as follows:
  64. * - `'host'` _string_: A string in the form of `'host:port'` indicating the Redis server
  65. * to connect to. Defaults to `'127.0.0.1:6379'`.
  66. * - `'expiry'` _mixed_: Default expiration for cache values written through this
  67. * adapter. Defaults to `'+1 hour'`. For acceptable values, see the `$expiry` parameter
  68. * of `Redis::_ttl()`.
  69. * - `'persistent'` _boolean_: Indicates whether the adapter should use a persistent
  70. * connection when attempting to connect to the Redis server. If `true`, it will
  71. * attempt to reuse an existing connection when connecting, and the connection will
  72. * not close when the request is terminated. Defaults to `false`.
  73. */
  74. public function __construct(array $config = array()) {
  75. $defaults = array(
  76. 'host' => '127.0.0.1:6379',
  77. 'expiry' => '+1 hour',
  78. 'persistent' => false
  79. );
  80. parent::__construct($config + $defaults);
  81. }
  82. /**
  83. * Initialize the Redis connection object and connect to the Redis server.
  84. *
  85. * @return void
  86. */
  87. protected function _init() {
  88. if (!$this->connection) {
  89. $this->connection = new RedisCore();
  90. }
  91. list($ip, $port) = explode(':', $this->_config['host']);
  92. $method = $this->_config['persistent'] ? 'pconnect' : 'connect';
  93. $this->connection->{$method}($ip, $port);
  94. }
  95. /**
  96. * Dispatches a not-found method to the Redis connection object.
  97. *
  98. * That way, one can easily use a custom method on that redis adapter like that:
  99. *
  100. * {{{Cache::adapter('named-of-redis-config')->methodName($argument);}}}
  101. *
  102. * If you want to know, what methods are available, have a look at the readme of phprdis.
  103. * One use-case might be to query possible keys, e.g.
  104. *
  105. * {{{Cache::adapter('redis')->keys('*');}}}
  106. *
  107. * @link https://github.com/nicolasff/phpredis GitHub: PhpRedis Extension
  108. * @param string $method Name of the method to call
  109. * @param array $params Parameter list to use when calling $method
  110. * @return mixed Returns the result of the method call
  111. */
  112. public function __call($method, $params = array()) {
  113. return call_user_func_array(array(&$this->connection, $method), $params);
  114. }
  115. /**
  116. * Custom check to determine if our given magic methods can be responded to.
  117. *
  118. * @param string $method Method name.
  119. * @param bool $internal Interal call or not.
  120. * @return bool
  121. */
  122. public function respondsTo($method, $internal = 0) {
  123. $parentRespondsTo = parent::respondsTo($method, $internal);
  124. return $parentRespondsTo || is_callable(array($this->connection, $method));
  125. }
  126. /**
  127. * Sets expiration time for cache keys
  128. *
  129. * @param string $key The key to uniquely identify the cached item
  130. * @param mixed $expiry A `strtotime()`-compatible string indicating when the cached item
  131. * should expire, or a Unix timestamp.
  132. * @return boolean Returns `true` if expiry could be set for the given key, `false` otherwise.
  133. */
  134. protected function _ttl($key, $expiry) {
  135. return $this->connection->expireAt($key, is_int($expiry) ? $expiry : strtotime($expiry));
  136. }
  137. /**
  138. * Write value(s) to the cache
  139. *
  140. * @param string $key The key to uniquely identify the cached item
  141. * @param mixed $value The value to be cached
  142. * @param null|string $expiry A strtotime() compatible cache time. If no expiry time is set,
  143. * then the default cache expiration time set with the cache configuration will be used.
  144. * @return closure Function returning boolean `true` on successful write, `false` otherwise.
  145. */
  146. public function write($key, $value = null, $expiry = null) {
  147. $connection =& $this->connection;
  148. $expiry = ($expiry) ?: $this->_config['expiry'];
  149. $_self =& $this;
  150. return function($self, $params) use (&$_self, &$connection, $expiry) {
  151. if (is_array($params['key'])) {
  152. $expiry = $params['data'];
  153. if ($connection->mset($params['key'])) {
  154. $ttl = array();
  155. if ($expiry) {
  156. foreach ($params['key'] as $k => $v) {
  157. $ttl[$k] = $_self->invokeMethod('_ttl', array($k, $expiry));
  158. }
  159. }
  160. return $ttl;
  161. }
  162. }
  163. if ($result = $connection->set($params['key'], $params['data'])) {
  164. if ($expiry) {
  165. return $_self->invokeMethod('_ttl', array($params['key'], $expiry));
  166. }
  167. return $result;
  168. }
  169. };
  170. }
  171. /**
  172. * Read value(s) from the cache
  173. *
  174. * @param string $key The key to uniquely identify the cached item
  175. * @return closure Function returning cached value if successful, `false` otherwise
  176. */
  177. public function read($key) {
  178. $connection =& $this->connection;
  179. return function($self, $params) use (&$connection) {
  180. $key = $params['key'];
  181. if (is_array($key)) {
  182. return $connection->getMultiple($key);
  183. }
  184. return $connection->get($key);
  185. };
  186. }
  187. /**
  188. * Delete value from the cache
  189. *
  190. * @param string $key The key to uniquely identify the cached item
  191. * @return closure Function returning boolean `true` on successful delete, `false` otherwise
  192. */
  193. public function delete($key) {
  194. $connection =& $this->connection;
  195. return function($self, $params) use (&$connection) {
  196. return (boolean) $connection->delete($params['key']);
  197. };
  198. }
  199. /**
  200. * Performs an atomic decrement operation on specified numeric cache item.
  201. *
  202. * Note that if the value of the specified key is *not* an integer, the decrement
  203. * operation will have no effect whatsoever. Redis chooses to not typecast values
  204. * to integers when performing an atomic decrement operation.
  205. *
  206. * @param string $key Key of numeric cache item to decrement
  207. * @param integer $offset Offset to decrement - defaults to 1.
  208. * @return closure Function returning item's new value on successful decrement, else `false`
  209. */
  210. public function decrement($key, $offset = 1) {
  211. $connection =& $this->connection;
  212. return function($self, $params) use (&$connection, $offset) {
  213. return $connection->decr($params['key'], $offset);
  214. };
  215. }
  216. /**
  217. * Performs an atomic increment operation on specified numeric cache item.
  218. *
  219. * Note that if the value of the specified key is *not* an integer, the increment
  220. * operation will have no effect whatsoever. Redis chooses to not typecast values
  221. * to integers when performing an atomic increment operation.
  222. *
  223. * @param string $key Key of numeric cache item to increment
  224. * @param integer $offset Offset to increment - defaults to 1.
  225. * @return closure Function returning item's new value on successful increment, else `false`
  226. */
  227. public function increment($key, $offset = 1) {
  228. $connection =& $this->connection;
  229. return function($self, $params) use (&$connection, $offset) {
  230. return $connection->incr($params['key'], $offset);
  231. };
  232. }
  233. /**
  234. * Clears user-space cache
  235. *
  236. * @return mixed True on successful clear, false otherwise
  237. */
  238. public function clear() {
  239. return $this->connection->flushdb();
  240. }
  241. /**
  242. * Determines if the Redis extension has been installed and
  243. * that there is a redis-server available
  244. *
  245. * @return boolean Returns `true` if the Redis extension is enabled, `false` otherwise.
  246. */
  247. public static function enabled() {
  248. return extension_loaded('redis');
  249. }
  250. }
  251. ?>