CRedisCache.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <?php
  2. /**
  3. * CRedisCache class file
  4. *
  5. * @author Carsten Brandt <mail@cebe.cc>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CRedisCache implements a cache application component based on {@link http://redis.io/ redis}.
  12. *
  13. * CRedisCache needs to be configured with {@link hostname}, {@link port} and {@link database} of the server
  14. * to connect to. By default CRedisCache assumes there is a redis server running on localhost at
  15. * port 6379 and uses the database number 0.
  16. *
  17. * CRedisCache also supports {@link http://redis.io/commands/auth the AUTH command} of redis.
  18. * When the server needs authentication, you can set the {@link password} property to
  19. * authenticate with the server after connect.
  20. *
  21. * See {@link CCache} manual for common cache operations that are supported by CRedisCache.
  22. *
  23. * To use CRedisCache as the cache application component, configure the application as follows,
  24. * <pre>
  25. * array(
  26. * 'components'=>array(
  27. * 'cache'=>array(
  28. * 'class'=>'CRedisCache',
  29. * 'hostname'=>'localhost',
  30. * 'port'=>6379,
  31. * 'database'=>0,
  32. * ),
  33. * ),
  34. * )
  35. * </pre>
  36. *
  37. * The minimum required redis version is 2.0.0.
  38. *
  39. * @author Carsten Brandt <mail@cebe.cc>
  40. * @package system.caching
  41. * @since 1.1.14
  42. */
  43. class CRedisCache extends CCache
  44. {
  45. /**
  46. * @var string hostname to use for connecting to the redis server. Defaults to 'localhost'.
  47. */
  48. public $hostname='localhost';
  49. /**
  50. * @var int the port to use for connecting to the redis server. Default port is 6379.
  51. */
  52. public $port=6379;
  53. /**
  54. * @var string the password to use to authenticate with the redis server. If not set, no AUTH command will be sent.
  55. */
  56. public $password;
  57. /**
  58. * @var int the redis database to use. This is an integer value starting from 0. Defaults to 0.
  59. */
  60. public $database=0;
  61. /**
  62. * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
  63. */
  64. public $timeout=null;
  65. /**
  66. * @var resource redis socket connection
  67. */
  68. private $_socket;
  69. /**
  70. * Establishes a connection to the redis server.
  71. * It does nothing if the connection has already been established.
  72. * @throws CException if connecting fails
  73. */
  74. protected function connect()
  75. {
  76. $this->_socket=@stream_socket_client(
  77. $this->hostname.':'.$this->port,
  78. $errorNumber,
  79. $errorDescription,
  80. $this->timeout ? $this->timeout : ini_get("default_socket_timeout")
  81. );
  82. if ($this->_socket)
  83. {
  84. if($this->password!==null)
  85. $this->executeCommand('AUTH',array($this->password));
  86. $this->executeCommand('SELECT',array($this->database));
  87. }
  88. else
  89. throw new CException('Failed to connect to redis: '.$errorDescription,(int)$errorNumber);
  90. }
  91. /**
  92. * Executes a redis command.
  93. * For a list of available commands and their parameters see {@link http://redis.io/commands}.
  94. *
  95. * @param string $name the name of the command
  96. * @param array $params list of parameters for the command
  97. * @return array|bool|null|string Dependend on the executed command this method
  98. * will return different data types:
  99. * <ul>
  100. * <li><code>true</code> for commands that return "status reply".</li>
  101. * <li><code>string</code> for commands that return "integer reply"
  102. * as the value is in the range of a signed 64 bit integer.</li>
  103. * <li><code>string</code> or <code>null</code> for commands that return "bulk reply".</li>
  104. * <li><code>array</code> for commands that return "Multi-bulk replies".</li>
  105. * </ul>
  106. * See {@link http://redis.io/topics/protocol redis protocol description}
  107. * for details on the mentioned reply types.
  108. * @throws CException for commands that return {@link http://redis.io/topics/protocol#error-reply error reply}.
  109. */
  110. public function executeCommand($name,$params=array())
  111. {
  112. if($this->_socket===null)
  113. $this->connect();
  114. array_unshift($params,$name);
  115. $command='*'.count($params)."\r\n";
  116. foreach($params as $arg)
  117. $command.='$'.strlen($arg)."\r\n".$arg."\r\n";
  118. fwrite($this->_socket,$command);
  119. return $this->parseResponse(implode(' ',$params));
  120. }
  121. /**
  122. * Reads the result from socket and parses it
  123. * @return array|bool|null|string
  124. * @throws CException socket or data problems
  125. */
  126. private function parseResponse()
  127. {
  128. if(($line=fgets($this->_socket))===false)
  129. throw new CException('Failed reading data from redis connection socket.');
  130. $type=$line[0];
  131. $line=substr($line,1,-2);
  132. switch($type)
  133. {
  134. case '+': // Status reply
  135. return true;
  136. case '-': // Error reply
  137. throw new CException('Redis error: '.$line);
  138. case ':': // Integer reply
  139. // no cast to int as it is in the range of a signed 64 bit integer
  140. return $line;
  141. case '$': // Bulk replies
  142. if($line=='-1')
  143. return null;
  144. $length=$line+2;
  145. $data='';
  146. while($length>0)
  147. {
  148. if(($block=fread($this->_socket,$length))===false)
  149. throw new CException('Failed reading data from redis connection socket.');
  150. $data.=$block;
  151. $length-=(function_exists('mb_strlen') ? mb_strlen($block,'8bit') : strlen($block));
  152. }
  153. return substr($data,0,-2);
  154. case '*': // Multi-bulk replies
  155. $count=(int)$line;
  156. $data=array();
  157. for($i=0;$i<$count;$i++)
  158. $data[]=$this->parseResponse();
  159. return $data;
  160. default:
  161. throw new CException('Unable to parse data received from redis.');
  162. }
  163. }
  164. /**
  165. * Retrieves a value from cache with a specified key.
  166. * This is the implementation of the method declared in the parent class.
  167. * @param string $key a unique key identifying the cached value
  168. * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
  169. */
  170. protected function getValue($key)
  171. {
  172. return $this->executeCommand('GET',array($key));
  173. }
  174. /**
  175. * Retrieves multiple values from cache with the specified keys.
  176. * @param array $keys a list of keys identifying the cached values
  177. * @return array a list of cached values indexed by the keys
  178. */
  179. protected function getValues($keys)
  180. {
  181. $response=$this->executeCommand('MGET',$keys);
  182. $result=array();
  183. $i=0;
  184. foreach($keys as $key)
  185. $result[$key]=$response[$i++];
  186. return $result;
  187. }
  188. /**
  189. * Stores a value identified by a key in cache.
  190. * This is the implementation of the method declared in the parent class.
  191. *
  192. * @param string $key the key identifying the value to be cached
  193. * @param string $value the value to be cached
  194. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  195. * @return boolean true if the value is successfully stored into cache, false otherwise
  196. */
  197. protected function setValue($key,$value,$expire)
  198. {
  199. if ($expire==0)
  200. return (bool)$this->executeCommand('SET',array($key,$value));
  201. return (bool)$this->executeCommand('SETEX',array($key,$expire,$value));
  202. }
  203. /**
  204. * Stores a value identified by a key into cache if the cache does not contain this key.
  205. * This is the implementation of the method declared in the parent class.
  206. *
  207. * @param string $key the key identifying the value to be cached
  208. * @param string $value the value to be cached
  209. * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
  210. * @return boolean true if the value is successfully stored into cache, false otherwise
  211. */
  212. protected function addValue($key,$value,$expire)
  213. {
  214. if ($expire == 0)
  215. return (bool)$this->executeCommand('SETNX',array($key,$value));
  216. if($this->executeCommand('SETNX',array($key,$value)))
  217. {
  218. $this->executeCommand('EXPIRE',array($key,$expire));
  219. return true;
  220. }
  221. else
  222. return false;
  223. }
  224. /**
  225. * Deletes a value with the specified key from cache
  226. * This is the implementation of the method declared in the parent class.
  227. * @param string $key the key of the value to be deleted
  228. * @return boolean if no error happens during deletion
  229. */
  230. protected function deleteValue($key)
  231. {
  232. return (bool)$this->executeCommand('DEL',array($key));
  233. }
  234. /**
  235. * Deletes all values from cache.
  236. * This is the implementation of the method declared in the parent class.
  237. * @return boolean whether the flush operation was successful.
  238. */
  239. protected function flushValues()
  240. {
  241. return $this->executeCommand('FLUSHDB');
  242. }
  243. }