redis.php 6.4 KB

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