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