From 8e74add1e79030360560d676c4704744313f51c5 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 28 Mar 2013 15:57:05 +0100 Subject: [PATCH] Redis connection implementation --- framework/db/redis/Connection.php | 63 +++++++++++++++++++++--- tests/unit/framework/db/redis/ConnectionTest.php | 41 +++++++++++++++ tests/unit/framework/db/redis/RedisTestCase.php | 45 +++++++++++++++++ 3 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 tests/unit/framework/db/redis/ConnectionTest.php create mode 100644 tests/unit/framework/db/redis/RedisTestCase.php diff --git a/framework/db/redis/Connection.php b/framework/db/redis/Connection.php index f4b4e4a..00525d9 100644 --- a/framework/db/redis/Connection.php +++ b/framework/db/redis/Connection.php @@ -60,7 +60,7 @@ class Connection extends Component public $keyPrefix; /** - * @var array http://redis.io/commands + * @var array List of available redis commands http://redis.io/commands */ public $redisCommands = array( 'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available @@ -268,7 +268,7 @@ class Connection extends Component { if ($this->_socket !== null) { \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); - // TODO send CLOSE to the server + $this->__call('CLOSE', array()); // TODO improve API stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); $this->_socket = null; $this->_transaction = null; @@ -323,23 +323,70 @@ class Connection extends Component } } + /** + * http://redis.io/topics/protocol + * https://github.com/ptrofimov/tinyredisclient/blob/master/src/TinyRedisClient.php + * + * @param string $name + * @param array $params + * @return mixed + */ public function __call($name, $params) { + // TODO set active to true? if (in_array($name, $this->redisCommands)) { array_unshift($params, $name); - $cmd = '*' . count($params) . "\r\n"; + $command = '*' . count($params) . "\r\n"; foreach($params as $arg) { - $cmd .= '$' . strlen( $item ) . "\r\n" . $item . "\r\n"; + $command .= '$' . strlen($arg) . "\r\n" . $arg . "\r\n"; } - fwrite( $this->_socket, $cmd ); - return $this->_parseResponse(); + \Yii::trace("Executing Redis Command: {$command}", __CLASS__); + fwrite($this->_socket, $command); + return $this->parseResponse($command); } else { return parent::__call($name, $params); } } + private function parseResponse($command) + { + if(($line = fgets($this->_socket))===false) { + throw new Exception("Failed to read from socket.\nRedis command was: " . $command); + } + $type = $line[0]; + $line = substr($line, 1, -2); + switch($type) + { + case '+': // Status reply + return true; + case '-': // Error reply + throw new Exception("Redis error: " . $line . "\nRedis command was: " . $command); + case ':': // Integer reply + // no cast to integer as it is in the range of a signed 64 bit integer + return $line; + case '$': // Bulk replies + if ($line == '-1') { + return null; + } + $data = fread($this->_socket, $line + 2); + if($data===false) { + throw new Exception("Failed to read from socket.\nRedis command was: " . $command); + } + return substr($data, 0, -2); + case '*': // Multi-bulk replies + $count = (int) $line; + $data = array(); + for($i = 0; $i < $count; $i++) { + $data[] = $this->parseResponse($command); + } + return $data; + default: + throw new Exception('Received illegal data from redis: ' . substr($line, 0, -2) . "\nRedis command was: " . $command); + } + } + /** * Returns the statistical results of SQL queries. * The results returned include the number of SQL statements executed and @@ -350,9 +397,9 @@ class Connection extends Component * @see \yii\logging\Logger::getProfiling() */ public function getQuerySummary() - {// TODO implement + { $logger = \Yii::getLogger(); - $timings = $logger->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); + $timings = $logger->getProfiling(array('yii\db\redis\Connection::command')); $count = count($timings); $time = 0; foreach ($timings as $timing) { diff --git a/tests/unit/framework/db/redis/ConnectionTest.php b/tests/unit/framework/db/redis/ConnectionTest.php new file mode 100644 index 0000000..904f1e6 --- /dev/null +++ b/tests/unit/framework/db/redis/ConnectionTest.php @@ -0,0 +1,41 @@ + + */ +class ConnectionTest extends RedisTestCase +{ + public function testConstruct() + { + $db = new Connection(); + } + + public function storeGetData() + { + return array( + array(123), + array(-123), + array(0), + array('test'), + array("test\r\ntest"), + array(json_encode($this)), + ); + } + + /** + * @dataProvider storeGetData + */ + public function testStoreGet($data) + { + $db = $this->getConnection(true); + + $db->SET('hi', $data); + $this->assertEquals($data, $db->GET('hi')); + } +} \ No newline at end of file diff --git a/tests/unit/framework/db/redis/RedisTestCase.php b/tests/unit/framework/db/redis/RedisTestCase.php new file mode 100644 index 0000000..eef8c84 --- /dev/null +++ b/tests/unit/framework/db/redis/RedisTestCase.php @@ -0,0 +1,45 @@ + + */ + +namespace yiiunit\framework\db\redis; + + +use yii\db\redis\Connection; +use yiiunit\TestCase; + +class RedisTestCase extends TestCase +{ + function __construct() + { + // TODO check if a redis server is running + //$this->markTestSkipped('No redis server running at port ...'); + } + + /** + * @param bool $reset whether to clean up the test database + * @return Connection + */ + function getConnection($reset = true) + { + $params = $this->getParam('redis'); + $db = new \yii\db\redis\Connection; + $db->dsn = $params['dsn']; + $db->username = $params['username']; + $db->password = $params['password']; + if ($reset) { + // TODO implement +/* $db->open(); + $lines = explode(';', file_get_contents($params['fixture'])); + foreach ($lines as $line) { + if (trim($line) !== '') { + $db->pdo->exec($line); + } + }*/ + } + return $db; + } +} \ No newline at end of file