redis.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <?php namespace Laravel;
  2. define('CRLF', chr(13).chr(10));
  3. class Redis {
  4. /**
  5. * The address for the Redis host.
  6. *
  7. * @var string
  8. */
  9. protected $host;
  10. /**
  11. * The port on which Redis can be accessed on the host.
  12. *
  13. * @var int
  14. */
  15. protected $port;
  16. /**
  17. * The connection to the Redis database.
  18. *
  19. * @var resource
  20. */
  21. protected $connection;
  22. /**
  23. * The active Redis database instances.
  24. *
  25. * @var array
  26. */
  27. protected static $databases = array();
  28. /**
  29. * Create a new Redis connection instance.
  30. *
  31. * @param string $host
  32. * @param string $port
  33. * @return void
  34. */
  35. public function __construct($host, $port)
  36. {
  37. $this->host = $host;
  38. $this->port = $port;
  39. }
  40. /**
  41. * Get a Redis database connection instance.
  42. *
  43. * The given name should correspond to a Redis database in the configuration file.
  44. *
  45. * <code>
  46. * // Get the default Redis database instance
  47. * $redis = Redis::db();
  48. *
  49. * // Get a specified Redis database instance
  50. * $reids = Redis::db('redis_2');
  51. * </code>
  52. *
  53. * @param string $name
  54. * @return Redis
  55. */
  56. public static function db($name = 'default')
  57. {
  58. if ( ! isset(static::$databases[$name]))
  59. {
  60. if (is_null($config = Config::get("database.redis.{$name}")))
  61. {
  62. throw new \Exception("Redis database [$name] is not defined.");
  63. }
  64. static::$databases[$name] = new static($config['host'], $config['port']);
  65. }
  66. return static::$databases[$name];
  67. }
  68. /**
  69. * Execute a command against the Redis database.
  70. *
  71. * <code>
  72. * // Execute the GET command for the "name" key
  73. * $name = Redis::db()->run('get', array('name'));
  74. *
  75. * // Execute the LRANGE command for the "list" key
  76. * $list = Redis::db()->run('lrange', array(0, 5));
  77. * </code>
  78. *
  79. * @param string $method
  80. * @param array $parameters
  81. * @return mixed
  82. */
  83. public function run($method, $parameters)
  84. {
  85. fwrite($this->connect(), $this->command($method, (array) $parameters));
  86. $ersponse = trim(fgets($this->connection, 512));
  87. switch (substr($ersponse, 0, 1))
  88. {
  89. case '-':
  90. throw new \Exception('Redis error: '.substr(trim($ersponse), 4));
  91. case '+':
  92. case ':':
  93. return $this->inline($ersponse);
  94. case '$':
  95. return $this->bulk($ersponse);
  96. case '*':
  97. return $this->multibulk($ersponse);
  98. default:
  99. throw new \Exception("Unknown response from Redis server: ".substr($ersponse, 0, 1));
  100. }
  101. }
  102. /**
  103. * Establish the connection to the Redis database.
  104. *
  105. * @return resource
  106. */
  107. protected function connect()
  108. {
  109. if ( ! is_null($this->connection)) return $this->connection;
  110. $this->connection = @fsockopen($this->host, $this->port, $error, $message);
  111. if ($this->connection === false)
  112. {
  113. throw new \Exception("Error making Redis connection: {$error} - {$message}");
  114. }
  115. return $this->connection;
  116. }
  117. /**
  118. * Build the Redis command based from a given method and parameters.
  119. *
  120. * Redis protocol states that a command should conform to the following format:
  121. *
  122. * *<number of arguments> CR LF
  123. * $<number of bytes of argument 1> CR LF
  124. * <argument data> CR LF
  125. * ...
  126. * $<number of bytes of argument N> CR LF
  127. * <argument data> CR LF
  128. *
  129. * More information regarding the Redis protocol: http://redis.io/topics/protocol
  130. *
  131. * @param string $method
  132. * @param array $parameters
  133. * @return string
  134. */
  135. protected function command($method, $parameters)
  136. {
  137. $command = '*'.(count($parameters) + 1).CRLF;
  138. $command .= '$'.strlen($method).CRLF;
  139. $command .= strtoupper($method).CRLF;
  140. foreach ($parameters as $parameter)
  141. {
  142. $command .= '$'.strlen($parameter).CRLF.$parameter.CRLF;
  143. }
  144. return $command;
  145. }
  146. /**
  147. * Parse and handle an inline response from the Redis database.
  148. *
  149. * @param string $response
  150. * @return string
  151. */
  152. protected function inline($response)
  153. {
  154. return substr(trim($response), 1);
  155. }
  156. /**
  157. * Parse and handle a bulk response from the Redis database.
  158. *
  159. * @param string $head
  160. * @return string
  161. */
  162. protected function bulk($head)
  163. {
  164. if ($head == '$-1') return;
  165. list($read, $response, $size) = array(0, '', substr($head, 1));
  166. do
  167. {
  168. // Calculate and read the appropriate bytes off of the Redis response.
  169. // We'll read off the response in 1024 byte chunks until the entire
  170. // response has been read from the database.
  171. $block = (($remaining = $size - $read) < 1024) ? $remaining : 1024;
  172. $response .= fread($this->connection, $block);
  173. $read += $block;
  174. } while ($read < $size);
  175. // The response ends with a trailing CRLF. So, we need to read that off
  176. // of the end of the file stream to get it out of the way of the next
  177. // command that is issued to the database.
  178. fread($this->connection, 2);
  179. return $response;
  180. }
  181. /**
  182. * Parse and handle a multi-bulk reply from the Redis database.
  183. *
  184. * @param string $head
  185. * @return array
  186. */
  187. protected function multibulk($head)
  188. {
  189. if (($count = substr($head, 1)) == '-1') return;
  190. $response = array();
  191. // Iterate through each bulk response in the multi-bulk and parse it out
  192. // using the "bulk" method since a multi-bulk response is just a list of
  193. // plain old bulk responses.
  194. for ($i = 0; $i < $count; $i++)
  195. {
  196. $response[] = $this->bulk(trim(fgets($this->connection, 512)));
  197. }
  198. return $response;
  199. }
  200. /**
  201. * Dynamically make calls to the Redis database.
  202. */
  203. public function __call($method, $parameters)
  204. {
  205. return $this->run($method, $parameters);
  206. }
  207. /**
  208. * Dynamically pass static method calls to the Redis instance.
  209. */
  210. public static function __callStatic($method, $parameters)
  211. {
  212. return static::db()->run($method, $parameters);
  213. }
  214. /**
  215. * Close the connection to the Redis database.
  216. *
  217. * @return void
  218. */
  219. public function __destruct()
  220. {
  221. fclose($this->connection);
  222. }
  223. }