redis.php 6.1 KB

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