redis.php 5.67 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 20 21 22 23

	/**
	 * The connection to the Redis database.
	 *
	 * @var resource
	 */
24
	protected $connection;
25 26

	/**
27
	 * The active Redis database instances.
28
	 *
29
	 * @var array
30
	 */
31
	protected static $databases = array();
32 33 34 35

	/**
	 * Create a new Redis connection instance.
	 *
36 37 38
	 * @param  string  $host
	 * @param  string  $port
	 * @return void
39
	 */
40
	public function __construct($host, $port)
41
	{
42 43
		$this->host = $host;
		$this->port = $port;
44 45 46
	}

	/**
47
	 * Get a Redis database connection instance.
48
	 *
49
	 * The given name should correspond to a Redis database in the configuration file.
50
	 *
51 52 53 54 55 56 57 58 59
	 * <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
60 61
	 * @return Redis
	 */
62
	public static function db($name = 'default')
63
	{
64
		if ( ! isset(static::$databases[$name]))
65
		{
66 67
			if (is_null($config = Config::get("database.redis.{$name}")))
			{
68
				throw new \Exception("Redis database [$name] is not defined.");
69 70 71
			}

			static::$databases[$name] = new static($config['host'], $config['port']);
72 73
		}

74
		return static::$databases[$name];
75 76 77
	}

	/**
78
	 * Execute a command against the Redis database.
79
	 *
80 81 82
	 * <code>
	 *		// Execute the GET command for the "name" key
	 *		$name = Redis::db()->run('get', array('name'));
83
	 *
84 85 86 87 88 89 90
	 *		// 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
91
	 */
92
	public function run($method, $parameters)
93
	{
94 95
		fwrite($this->connect(), $this->command($method, (array) $parameters));

96
		$response = trim(fgets($this->connection, 512));
97

98
		switch (substr($response, 0, 1))
99
		{
100
			case '-':
101
				throw new \Exception('Redis error: '.substr(trim($response), 4));
102 103 104
			
			case '+':
			case ':':
105
				return $this->inline($response);
106 107
			
			case '$':
108
				return $this->bulk($response);
109 110
			
			case '*':
111
				return $this->multibulk($response);
112 113
			
			default:
114
				throw new \Exception("Unknown Redis response: ".substr($response, 0, 1));
115 116 117 118
		}
	}

	/**
119
	 * Establish the connection to the Redis database.
120
	 *
121
	 * @return resource
122
	 */
123
	protected function connect()
124
	{
125 126 127 128 129 130
		if ( ! is_null($this->connection)) return $this->connection;

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

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

134
		return $this->connection;
135 136 137 138 139
	}

	/**
	 * Build the Redis command based from a given method and parameters.
	 *
140 141 142 143 144 145 146 147 148 149 150
	 * 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
	 *
151 152 153 154 155 156
	 * @param  string  $method
	 * @param  array   $parameters
	 * @return string
	 */
	protected function command($method, $parameters)
	{
157 158 159 160 161
		$command  = '*'.(count($parameters) + 1).CRLF;

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

		$command .= strtoupper($method).CRLF;
162 163 164 165 166 167 168 169 170 171

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

		return $command;
	}

	/**
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
	 * 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));

		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;

			$response .= fread($this->connection, $block);

			$read += $block;

		} while ($read < $size);

		// 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
		// using the "bulk" method since a multi-bulk response is just a list of
		// plain old bulk responses.
		for ($i = 0; $i < $count; $i++)
		{
			$response[] = $this->bulk(trim(fgets($this->connection, 512)));
		}

		return $response;
	}

	/**
239 240 241 242
	 * Dynamically make calls to the Redis database.
	 */
	public function __call($method, $parameters)
	{
243
		return $this->run($method, $parameters);
244 245 246
	}

	/**
247 248 249 250 251 252 253 254
	 * Dynamically pass static method calls to the Redis instance.
	 */
	public static function __callStatic($method, $parameters)
	{
		return static::db()->run($method, $parameters);
	}

	/**
255 256 257 258 259 260
	 * Close the connection to the Redis database.
	 *
	 * @return void
	 */
	public function __destruct()
	{
261 262 263 264
		if ($this->connection)
		{
			fclose($this->connection);
		}
265 266
	}

267
}