redis.php 6.14 KB
Newer Older
1 2 3 4 5
<?php namespace Laravel;

class Redis {

	/**
6
	 * The address for the Redis host.
7 8 9
	 *
	 * @var string
	 */
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

	/**
Taylor Otwell committed
20 21 22 23 24 25 26
	 * The databse number the connection selects on load.
	 *
	 * @var int
	 */
	protected $database;

	/**
27 28 29 30
	 * The connection to the Redis database.
	 *
	 * @var resource
	 */
31
	protected $connection;
32 33

	/**
34
	 * The active Redis database instances.
35
	 *
36
	 * @var array
37
	 */
38
	protected static $databases = array();
39 40 41 42

	/**
	 * Create a new Redis connection instance.
	 *
43 44
	 * @param  string  $host
	 * @param  string  $port
Taylor Otwell committed
45
	 * @param  int     $database
46
	 * @return void
47
	 */
Taylor Otwell committed
48
	public function __construct($host, $port, $database = 0)
49
	{
50 51
		$this->host = $host;
		$this->port = $port;
Taylor Otwell committed
52
		$this->database = $database;
53 54 55
	}

	/**
56
	 * Get a Redis database connection instance.
57
	 *
58
	 * The given name should correspond to a Redis database in the configuration file.
59
	 *
60 61 62 63 64 65 66 67 68
	 * <code>
	 *		// Get the default Redis database instance
	 *		$redis = Redis::db();
	 *
	 *		// Get a specified Redis database instance
	 *		$reids = Redis::db('redis_2');
	 * </code>
	 *
	 * @param  string  $name
69 70
	 * @return Redis
	 */
71
	public static function db($name = 'default')
72
	{
73
		if ( ! isset(static::$databases[$name]))
74
		{
75 76
			if (is_null($config = Config::get("database.redis.{$name}")))
			{
77
				throw new \Exception("Redis database [$name] is not defined.");
78 79
			}

Taylor Otwell committed
80 81 82
			extract($config);

			static::$databases[$name] = new static($host, $port, $database);
83 84
		}

85
		return static::$databases[$name];
86 87 88
	}

	/**
89
	 * Execute a command against the Redis database.
90
	 *
91 92 93
	 * <code>
	 *		// Execute the GET command for the "name" key
	 *		$name = Redis::db()->run('get', array('name'));
94
	 *
95 96 97 98 99 100 101
	 *		// Execute the LRANGE command for the "list" key
	 *		$list = Redis::db()->run('lrange', array(0, 5));
	 * </code>
	 *
	 * @param  string  $method
	 * @param  array   $parameters
	 * @return mixed
102
	 */
103
	public function run($method, $parameters)
104
	{
105 106
		fwrite($this->connect(), $this->command($method, (array) $parameters));

107
		$response = trim(fgets($this->connection, 512));
108

Taylor Otwell committed
109 110 111 112 113 114 115 116 117 118 119
		return $this->parse($response);
	}

	/**
	 * Parse and return the response from the Redis database.
	 *
	 * @param  string  $response
	 * @return mixed
	 */
	protected function parse($response)
	{
120
		switch (substr($response, 0, 1))
121
		{
122
			case '-':
123
				throw new \Exception('Redis error: '.substr(trim($response), 4));
124 125 126
			
			case '+':
			case ':':
127
				return $this->inline($response);
128 129
			
			case '$':
130
				return $this->bulk($response);
131 132
			
			case '*':
133
				return $this->multibulk($response);
134 135
			
			default:
136
				throw new \Exception("Unknown Redis response: ".substr($response, 0, 1));
137 138 139 140
		}
	}

	/**
141
	 * Establish the connection to the Redis database.
142
	 *
143
	 * @return resource
144
	 */
145
	protected function connect()
146
	{
147 148 149 150 151 152
		if ( ! is_null($this->connection)) return $this->connection;

		$this->connection = @fsockopen($this->host, $this->port, $error, $message);		

		if ($this->connection === false)
		{
153
			throw new \Exception("Error making Redis connection: {$error} - {$message}");
154
		}
155

Taylor Otwell committed
156 157
		$this->select($this->database);

158
		return $this->connection;
159 160 161 162 163
	}

	/**
	 * Build the Redis command based from a given method and parameters.
	 *
164 165 166 167 168 169 170 171 172 173 174
	 * Redis protocol states that a command should conform to the following format:
	 *
	 *     *<number of arguments> CR LF
	 *     $<number of bytes of argument 1> CR LF
	 *     <argument data> CR LF
	 *     ...
	 *     $<number of bytes of argument N> CR LF
	 *     <argument data> CR LF
	 *
	 * More information regarding the Redis protocol: http://redis.io/topics/protocol
	 *
175 176 177 178 179 180
	 * @param  string  $method
	 * @param  array   $parameters
	 * @return string
	 */
	protected function command($method, $parameters)
	{
181 182 183 184 185
		$command  = '*'.(count($parameters) + 1).CRLF;

		$command .= '$'.strlen($method).CRLF;

		$command .= strtoupper($method).CRLF;
186 187 188 189 190 191 192 193 194 195

		foreach ($parameters as $parameter)
		{
			$command .= '$'.strlen($parameter).CRLF.$parameter.CRLF;
		}

		return $command;
	}

	/**
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	 * Parse and handle an inline response from the Redis database.
	 *
	 * @param  string  $response
	 * @return string
	 */
	protected function inline($response)
	{
		return substr(trim($response), 1);
	}

	/**
	 * Parse and handle a bulk response from the Redis database.
	 *
	 * @param  string  $head
	 * @return string
	 */
	protected function bulk($head)
	{
		if ($head == '$-1') return;

		list($read, $response, $size) = array(0, '', substr($head, 1));

Taylor Otwell committed
218
		if ($size > 0)
219
		{
Taylor Otwell committed
220 221 222 223 224 225
			do
			{
				// Calculate and read the appropriate bytes off of the Redis response.
				// We'll read off the response in 1024 byte chunks until the entire
				// response has been read from the database.
				$block = (($remaining = $size - $read) < 1024) ? $remaining : 1024;
226

Taylor Otwell committed
227
				$response .= fread($this->connection, $block);
228

Taylor Otwell committed
229
				$read += $block;
230

Taylor Otwell committed
231 232
			} while ($read < $size);
		}
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

		// The response ends with a trailing CRLF. So, we need to read that off
		// of the end of the file stream to get it out of the way of the next
		// command that is issued to the database.
		fread($this->connection, 2);

		return $response;
	}

	/**
	 * Parse and handle a multi-bulk reply from the Redis database.
	 *
	 * @param  string  $head
	 * @return array
	 */
	protected function multibulk($head)
	{
		if (($count = substr($head, 1)) == '-1') return;

		$response = array();

		// Iterate through each bulk response in the multi-bulk and parse it out
Taylor Otwell committed
255 256
		// using the "parse" method since a multi-bulk response is just a list
		// of plain old Redis database responses.
257 258
		for ($i = 0; $i < $count; $i++)
		{
Taylor Otwell committed
259
			$response[] = $this->parse(trim(fgets($this->connection, 512)));
260 261 262 263 264 265
		}

		return $response;
	}

	/**
266 267 268 269
	 * Dynamically make calls to the Redis database.
	 */
	public function __call($method, $parameters)
	{
270
		return $this->run($method, $parameters);
271 272 273
	}

	/**
274 275 276 277 278 279 280 281
	 * Dynamically pass static method calls to the Redis instance.
	 */
	public static function __callStatic($method, $parameters)
	{
		return static::db()->run($method, $parameters);
	}

	/**
282 283 284 285 286 287
	 * Close the connection to the Redis database.
	 *
	 * @return void
	 */
	public function __destruct()
	{
288 289 290 291
		if ($this->connection)
		{
			fclose($this->connection);
		}
292 293
	}

294
}