Redis.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <?php
  2. /**
  3. * Pimf
  4. *
  5. * @copyright Copyright (c) Gjero Krsteski (http://krsteski.de)
  6. * @license http://krsteski.de/new-bsd-license New BSD License
  7. */
  8. namespace Pimf;
  9. /**
  10. * Redis usage
  11. *
  12. * <code>
  13. * // Get the default Redis database instance
  14. * $redis = Redis::db();
  15. *
  16. * // Get a specified Redis database instance
  17. * $reids = Redis::db('redis_2');
  18. *
  19. * // Execute the GET command for the "name" key
  20. * $name = Redis::db()->run('get', array('name'));
  21. *
  22. * // Execute the LRANGE command for the "list" key
  23. * $list = Redis::db()->run('lrange', array(0, 5));
  24. *
  25. * </code>
  26. *
  27. * @package Pimf
  28. * @author Gjero Krsteski <[email protected]>
  29. *
  30. * @method expire($key, $seconds)
  31. * @method set($key, $value)
  32. * @method del($key)
  33. * @method forget($key)
  34. * @method get($key)
  35. * @method select($database_id)
  36. * @method put($session_id, $session, $lifetime);
  37. */
  38. class Redis
  39. {
  40. /**
  41. * The address for the Redis host.
  42. *
  43. * @var string
  44. */
  45. protected $host;
  46. /**
  47. * The port on which Redis can be accessed on the host.
  48. *
  49. * @var int
  50. */
  51. protected $port;
  52. /**
  53. * The database number the connection selects on load.
  54. *
  55. * @var int
  56. */
  57. protected $database;
  58. /**
  59. * The connection to the Redis database.
  60. *
  61. * @var resource
  62. */
  63. protected $connection;
  64. /**
  65. * The active Redis database instances.
  66. *
  67. * @var array
  68. */
  69. protected static $databases = array();
  70. /**
  71. * Create a new Redis connection instance.
  72. *
  73. * @param string $host
  74. * @param int $port
  75. * @param int $database
  76. */
  77. public function __construct($host, $port, $database = 0)
  78. {
  79. $this->host = $host;
  80. $this->port = $port;
  81. $this->database = $database;
  82. }
  83. /**
  84. * Get a Redis database connection instance.
  85. *
  86. * The given name should correspond to a Redis database in the configuration file.
  87. *
  88. * @param string $name
  89. *
  90. * @return Redis
  91. * @throws \RuntimeException
  92. */
  93. public static function database($name = 'default')
  94. {
  95. if (!isset(static::$databases[$name])) {
  96. $conf = Registry::get('conf');
  97. if (!isset($conf['cache']['storage']) || $conf['cache']['storage'] != 'redis') {
  98. throw new \RuntimeException("Redis database [$name] is not defined.");
  99. }
  100. static::$databases[$name]
  101. = new static($conf['cache']['server']['host'], $conf['cache']['server']['port'], $conf['cache']['server']['database']);
  102. }
  103. return static::$databases[$name];
  104. }
  105. /**
  106. * Execute a command against the Redis database.
  107. *
  108. * @param string $method
  109. * @param array $parameters
  110. *
  111. * @return mixed
  112. */
  113. public function run($method, $parameters)
  114. {
  115. fwrite($this->connect(), $this->command($method, (array)$parameters));
  116. $response = trim(fgets($this->connection, 512));
  117. return $this->parse($response);
  118. }
  119. /**
  120. * Parse and return the response from the Redis database.
  121. *
  122. * @param string $response
  123. *
  124. * @return array|string
  125. * @throws \RuntimeException
  126. */
  127. protected function parse($response)
  128. {
  129. switch (substr($response, 0, 1)) {
  130. case '-':
  131. throw new \RuntimeException('Redis error: ' . substr(trim($response), 4));
  132. case '+':
  133. case ':':
  134. return $this->inline($response);
  135. case '$':
  136. return $this->bulk($response);
  137. case '*':
  138. return $this->multibulk($response);
  139. default:
  140. throw new \RuntimeException("Unknown Redis response: " . substr($response, 0, 1));
  141. }
  142. }
  143. /**
  144. * Establish the connection to the Redis database.
  145. *
  146. * @return resource
  147. * @throws \RuntimeException
  148. */
  149. protected function connect()
  150. {
  151. if (!is_null($this->connection)) {
  152. return $this->connection;
  153. }
  154. $this->connection = @fsockopen($this->host, $this->port, $error, $message);
  155. if ($this->connection === false) {
  156. throw new \RuntimeException("Error making Redis connection: {$error} - {$message}");
  157. }
  158. $this->select($this->database);
  159. return $this->connection;
  160. }
  161. /**
  162. * Build the Redis command based from a given method and parameters.
  163. *
  164. * Redis protocol states that a command should conform to the following format:
  165. *
  166. * *<number of arguments> CR LF
  167. * $<number of bytes of argument 1> CR LF
  168. * <argument data> CR LF
  169. * ...
  170. * $<number of bytes of argument N> CR LF
  171. * <argument data> CR LF
  172. *
  173. * More information regarding the Redis protocol: http://redis.io/topics/protocol
  174. *
  175. * @param string $method
  176. * @param $parameters
  177. *
  178. * @return string
  179. */
  180. protected function command($method, $parameters)
  181. {
  182. $CRLF = "\r\n";
  183. $command = '*' . (count($parameters) + 1) . $CRLF . '$' . strlen($method) . $CRLF . strtoupper($method) . $CRLF;
  184. foreach ($parameters as $parameter) {
  185. $command .= '$' . strlen($parameter) . $CRLF . $parameter . $CRLF;
  186. }
  187. return $command;
  188. }
  189. /**
  190. * Parse and handle an inline response from the Redis database.
  191. *
  192. * @param string $response
  193. *
  194. * @return string
  195. */
  196. protected function inline($response)
  197. {
  198. return substr(trim($response), 1);
  199. }
  200. /**
  201. * Parse and handle a bulk response from the Redis database.
  202. *
  203. * @param string $head
  204. *
  205. * @return string
  206. */
  207. protected function bulk($head)
  208. {
  209. if ($head == '$-1') {
  210. return null;
  211. }
  212. list($read, $response, $size) = array(0, '', substr($head, 1));
  213. if ($size > 0) {
  214. do {
  215. // Calculate and read the appropriate bytes off of the Redis response.
  216. $block = (($remaining = $size - $read) < 1024) ? $remaining : 1024;
  217. $response .= fread($this->connection, $block);
  218. $read += $block;
  219. } while ($read < $size);
  220. }
  221. // The response ends with a trailing CRLF.
  222. fread($this->connection, 2);
  223. return $response;
  224. }
  225. /**
  226. * Parse and handle a multi-bulk reply from the Redis database.
  227. *
  228. * @param string $head
  229. *
  230. * @return array
  231. */
  232. protected function multibulk($head)
  233. {
  234. if (($count = substr($head, 1)) == '-1') {
  235. return null;
  236. }
  237. $response = array();
  238. // Iterate through each bulk response in the multi-bulk and parse it out.
  239. for ($i = 0; $i < $count; $i++) {
  240. $response[] = $this->parse(trim(fgets($this->connection, 512)));
  241. }
  242. return $response;
  243. }
  244. /**
  245. * Dynamically make calls to the Redis database.
  246. *
  247. * @param string $method
  248. * @param array $parameters
  249. *
  250. * @return mixed
  251. */
  252. public function __call($method, $parameters)
  253. {
  254. return $this->run($method, $parameters);
  255. }
  256. /**
  257. * Dynamically pass static method calls to the Redis instance.
  258. *
  259. * @param $method
  260. * @param $parameters
  261. *
  262. * @return mixed
  263. */
  264. public static function __callStatic($method, $parameters)
  265. {
  266. return static::database()->run($method, $parameters);
  267. }
  268. /**
  269. * Close the connection to the Redis database.
  270. *
  271. * @return void
  272. */
  273. public function __destruct()
  274. {
  275. if ($this->connection) {
  276. fclose($this->connection);
  277. }
  278. }
  279. }