123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- <?php
- /**
- * Part of the Fuel framework.
- *
- * @package Fuel
- * @version 1.5
- * @author Fuel Development Team
- * @license MIT License
- * @copyright 2010 - 2013 Fuel Development Team
- * @link http://fuelphp.com
- */
- /**
- * This code is based on Redisent, a Redis interface for the modest.
- *
- * It has been modified to work with Fuel and to improve the code slightly.
- *
- * @author Justin Poliey <[email protected]>
- * @copyright 2009-2012 Justin Poliey <[email protected]>
- * @license http://www.opensource.org/licenses/ISC The ISC License
- */
- namespace Fuel\Core;
- class RedisException extends \FuelException {}
- /**
- * Redisent, a Redis interface for the modest among us
- */
- class Redis
- {
- /**
- * Multiton pattern, keep track of all created instances
- */
- protected static $instances = array();
- /**
- * Get an instance of the Redis class
- */
- public static function instance($name = 'default')
- {
- if ( ! array_key_exists($name, static::$instances))
- {
- // @deprecated since 1.4
- // call forge() if a new instance needs to be created, this should throw an error
- return static::forge($name);
- }
- return static::$instances[$name];
- }
- /**
- * create an instance of the Redis class
- */
- public static function forge($name = 'default', $config = array())
- {
- empty(static::$instances) and \Config::load('db', true);
- if ( ! ($conf = \Config::get('db.redis.'.$name)))
- {
- throw new \RedisException('Invalid instance name given.');
- }
- $config = \Arr::merge($conf, $config);
- static::$instances[$name] = new static($config);
- return static::$instances[$name];
- }
- /**
- * @var resource
- */
- protected $connection = false;
- /**
- * Flag indicating whether or not commands are being pipelined
- *
- * @var boolean
- */
- protected $pipelined = false;
- /**
- * The queue of commands to be sent to the Redis server
- *
- * @var array
- */
- protected $queue = array();
- /**
- * Create a new Redis instance using the configuration values supplied
- */
- public function __construct(array $config = array())
- {
- empty($config['timeout']) and $config['timeout'] = ini_get("default_socket_timeout");
- $this->connection = @fsockopen($config['hostname'], $config['port'], $errno, $errstr, $config['timeout']);
- if ( ! $this->connection)
- {
- throw new \RedisException($errstr, $errno);
- }
- else
- {
- // execute the auth command if a password is present in config
- empty($config['password']) or $this->auth($config['password']);
- }
- }
- /**
- * Close the open connection on class destruction
- */
- public function __destruct()
- {
- $this->connection and fclose($this->connection);
- }
- /**
- * Returns the Redisent instance ready for pipelining.
- *
- * Redis commands can now be chained, and the array of the responses will be
- * returned when {@link execute} is called.
- * @see execute
- *
- */
- public function pipeline()
- {
- $this->pipelined = true;
- return $this;
- }
- /**
- * Flushes the commands in the pipeline queue to Redis and returns the responses.
- * @see pipeline
- */
- public function execute()
- {
- // open a Redis connection and execute the queued commands
- foreach ($this->queue as $command)
- {
- for ($written = 0; $written < strlen($command); $written += $fwrite)
- {
- $fwrite = fwrite($this->connection, substr($command, $written));
- if ($fwrite === false)
- {
- throw new \RedisException('Failed to write entire command to stream');
- }
- }
- }
- // Read in the results from the pipelined commands
- $responses = array();
- for ($i = 0; $i < count($this->queue); $i++)
- {
- $responses[] = $this->readResponse();
- }
- // Clear the queue and return the response
- $this->queue = array();
- if ($this->pipelined)
- {
- $this->pipelined = false;
- return $responses;
- }
- else
- {
- return $responses[0];
- }
- }
- /**
- */
- public function __call($name, $args)
- {
- // build the Redis unified protocol command
- array_unshift($args, strtoupper($name));
- $command = sprintf('*%d%s%s%s', count($args), CRLF, implode(array_map(function($arg) {
- return sprintf('$%d%s%s', strlen($arg), CRLF, $arg);
- }, $args), CRLF), CRLF);
- // add it to the pipeline queue
- $this->queue[] = $command;
- if ($this->pipelined)
- {
- return $this;
- }
- else
- {
- return $this->execute();
- }
- }
- protected function readResponse()
- {
- // parse the response based on the reply identifier
- $reply = trim(fgets($this->connection, 512));
- switch (substr($reply, 0, 1))
- {
- // error reply
- case '-':
- throw new \RedisException(trim(substr($reply, 4)));
- break;
- // inline reply
- case '+':
- $response = substr(trim($reply), 1);
- if ($response === 'OK')
- {
- $response = true;
- }
- break;
- // bulk reply
- case '$':
- $response = null;
- if ($reply == '$-1')
- {
- break;
- }
- $read = 0;
- $size = intval(substr($reply, 1));
- if ($size > 0)
- {
- do
- {
- $block_size = ($size - $read) > 1024 ? 1024 : ($size - $read);
- $r = fread($this->connection, $block_size);
- if ($r === false)
- {
- throw new \RedisException('Failed to read response from stream');
- }
- else
- {
- $read += strlen($r);
- $response .= $r;
- }
- }
- while ($read < $size);
- }
- // discard the crlf
- fread($this->connection, 2);
- break;
- // multi-bulk reply
- case '*':
- $count = intval(substr($reply, 1));
- if ($count == '-1')
- {
- return null;
- }
- $response = array();
- for ($i = 0; $i < $count; $i++)
- {
- $response[] = $this->readResponse();
- }
- break;
- // integer reply
- case ':':
- $response = intval(substr(trim($reply), 1));
- break;
- default:
- throw new \RedisException("Unknown response: {$reply}");
- break;
- }
- // party on...
- return $response;
- }
- }
|