close(); return array_keys(get_object_vars($this)); } /** * Returns a value indicating whether the DB connection is established. * @return boolean whether the DB connection is established */ public function getIsActive() { return $this->_socket !== null; } /** * Establishes a DB connection. * It does nothing if a DB connection has already been established. * @throws Exception if connection fails */ public function open() { if ($this->_socket === null) { if (empty($this->dsn)) { throw new InvalidConfigException('Connection.dsn cannot be empty.'); } // TODO parse DSN $host = 'localhost'; $port = 6379; try { \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); $this->_socket = stream_socket_client($host . ':' . $port); // TODO auth // TODO select database $this->initConnection(); } catch (\PDOException $e) { \Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __CLASS__); $message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.'; throw new Exception($message, (int)$e->getCode(), $e->errorInfo); } } } /** * Closes the currently active DB connection. * It does nothing if the connection is already closed. */ public function close() { if ($this->_socket !== null) { \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); $this->__call('CLOSE', array()); // TODO improve API stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); $this->_socket = null; $this->_transaction = null; } } /** * Initializes the DB connection. * This method is invoked right after the DB connection is established. * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES` * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty. * It then triggers an [[EVENT_AFTER_OPEN]] event. */ protected function initConnection() { $this->trigger(self::EVENT_AFTER_OPEN); } /** * Returns the currently active transaction. * @return Transaction the currently active transaction. Null if no active transaction. */ public function getTransaction() { return $this->_transaction && $this->_transaction->isActive ? $this->_transaction : null; } /** * Starts a transaction. * @return Transaction the transaction initiated */ public function beginTransaction() { $this->open(); $this->_transaction = new Transaction(array( 'db' => $this, )); $this->_transaction->begin(); return $this->_transaction; } /** * Returns the name of the DB driver for the current [[dsn]]. * @return string name of the DB driver */ public function getDriverName() { if (($pos = strpos($this->dsn, ':')) !== false) { return strtolower(substr($this->dsn, 0, $pos)); } else { return 'redis'; } } /** * 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); $command = '*' . count($params) . "\r\n"; foreach($params as $arg) { $command .= '$' . strlen($arg) . "\r\n" . $arg . "\r\n"; } \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 * the total time spent. * In order to use this method, [[enableProfiling]] has to be set true. * @return array the first element indicates the number of SQL statements executed, * and the second element the total time spent in SQL execution. * @see \yii\logging\Logger::getProfiling() */ public function getQuerySummary() { $logger = \Yii::getLogger(); $timings = $logger->getProfiling(array('yii\db\redis\Connection::command')); $count = count($timings); $time = 0; foreach ($timings as $timing) { $time += $timing[1]; } return array($count, $time); } }