<?php

// Require Rediska
require_once dirname(__FILE__) . '/../Rediska.php';

/**
 * Rediska monitor
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Monitor extends Rediska_Options_RediskaInstance implements Iterator
{
    /**
     * Connections
     *
     * @var Rediska_Monitor_Connections
     */
    protected $_connections;

    /**
     * Timeout in seconds
     *
     * @var int
     */
    protected $_timeout;

    /**
     * The unix timestamp when started getting a message
     *
     * @var int
     */
    protected $_timeStart;

    /**
     * Need start time?
     *
     * @var boolean
     */
    protected $_needStart = true;

    /**
     * Server alias or connection object
     *
     * @var string|Rediska_Connection
     */
    protected $_serverAlias;

    /**
     * Current timestamp for iterator
     *
     * @var string
     */
    protected $_currentTimestamp;

    /**
     * Current command for iterator
     *
     * @var string
     */
    protected $_currentCommand;

    /**
     * Exception class name for options
     *
     * @var string
     */
    protected $_optionsException = 'Rediska_Monitor_Exception';

    /**
     * Constructor
     *
     * @param array[optional] $options Options
     */
    public function __construct($options = array())
    {
        $this->setOptions($options);

        $this->_connections = new Rediska_Monitor_Connections($this);
    }

    /**
     * Get command
     *
     * @return array
     */
    public function getCommand($timeout = null)
    {
        if (!$timeout && $this->getTimeout()) {
            $timeout = $this->getTimeout();
        }

        // Get message from connections
        while (true) {
            /* @var $connection Rediska_Connection */
            foreach ($this->_connections as $connection) {
                if ($timeout) {
                    $timeLeft = ($this->_timeStart + $timeout) - time();

                    if ($timeLeft <= 0) {
                        // Reset timeStart if time started from this method
                        if ($this->_needStart) {
                            $this->_timeStart = 0;
                        }

                        return null;
                    }

                    $connection->setReadTimeout($timeLeft);
                }

                try {
                    $response = $this->_getResponseFromConnection($connection);

                    if ($response === null) {
                        // Sleep before next connection check
                        usleep(10000); // 0.01 sec
                        continue;
                    }

                    // Reset timeStart if time started from this method
                    if ($this->_needStart) {
                        $this->_timeStart = 0;
                    }

                    return $response;
                } catch (Rediska_Connection_TimeoutException $e) {
                    if (!$timeout) {
                        throw $e;
                    }

                    // Reset timeStart if time started from this method
                    if ($this->_needStart) {
                        $this->_timeStart = 0;
                    }

                    return null;
                }
            }
        }
    }

    /**
     * Set server alias
     *
     * @param $serverAlias
     * @return Rediska_PubSub_Channel
     */
    public function setServerAlias($serverAlias)
    {
        $this->_serverAlias = $serverAlias;

        return $this;
    }

    /**
     * Get server alias
     *
     * @return Rediska_Connection|string
     */
    public function getServerAlias()
    {
        return $this->_serverAlias;
    }

    /**
     * Set timeout in seconds
     *
     * @return Rediska_PubSub_Channel
     */
    public function setTimeout($timeout)
    {
        $this->_timeout = (int)$timeout;

        return $this;
    }

    /**
     * Get timeout
     *
     * @return int
     */
    public function getTimeout()
    {
        return $this->_timeout;
    }

    /* Iterator implementation */

    public function rewind()
    {
        if ($this->_timeout) {
            $this->_timeStart = time();
        }
    }

    public function next()
    {

    }

    public function valid()
    {
        $this->_needStart = false;
        $commandAndTimestamp = $this->getCommand();
        $this->_needStart = true;

        if ($commandAndTimestamp) {
            list($this->_currentTimestamp, $this->_currentCommand) = $commandAndTimestamp;

            return true;
        } else {
            $this->_currentTimestamp = null;
            $this->_currentCommand = null;

            $this->_timeStart = 0;

            return false;
        }
    }

    public function key()
    {
        return $this->_currentTimestamp;
    }

    public function current()
    {
        return $this->_currentCommand;
    }

    /**
     * Get response from connection
     *
     * @param Rediska_Connection $connection
     * @return array|null
     */
    protected function _getResponseFromConnection(Rediska_Connection $connection)
    {
        $response = Rediska_Connection_Exec::readResponseFromConnection($connection);

        if ($response === null || $response === true) {
            return null;
        }

        $timestampAndCommand = explode(' ', $response, 2);

        $command = $timestampAndCommand[1];

        if ($command == '"MONITOR"') {
            return null;
        }

        return $timestampAndCommand;
    }
}


<?php

/**
 * Return the UNIX time stamp of the last successfully saving of the dataset on disk
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_GetLastSaveTime extends Rediska_Command_Abstract
{
    protected $_connections = array();

    /**
     * Create command
     *
     * @return Rediska_Connection_Exec
     */
    public function create()
    {
        $command = array('LASTSAVE');
        $commands = array();
        foreach($this->_rediska->getConnections() as $connection) {
            $this->_connections[] = $connection->getAlias();
            $commands[] = new Rediska_Connection_Exec($connection, $command);
        }

        return $commands;
    }

    /**
     * Parse response
     *
     * @param array $responses
     * @return integer|array
     */
    public function parseResponses($responses)
    {
        $timestamps = array();
        $count = 0;
        foreach($this->_connections as $connection) {
            $timestamps[$connection] = $responses[$count];
            $count++;
        }

        if (count($timestamps) == 1) {
            $timestamps = array_values($timestamps);
            $timestamps = $timestamps[0];
        }

        return $timestamps;
    }
}


<?php

/**
 * Rediska connection exec
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Connection
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Connection_Exec
{
    const REPLY_STATUS     = '+';
    const REPLY_ERROR      = '-';
    const REPLY_INTEGER    = ':';
    const REPLY_BULK       = '$';
    const REPLY_MULTY_BULK = '*';

    /**
     * Rediska connection
     *
     * @var Rediska_Connection
     */
    protected $_connection;

    /**
     * Cloned connection for iterator
     *
     * @var Rediska_Connection
     */
    protected $_connectionClone;

    /**
     * Command
     *
     * @var array|string
     */
    protected $_command;

    /**
     * Is writed
     *
     * @var $_isWritten boolean
     */
    protected $_isWritten = false;

    /**
     * Response callback
     *
     * @var callback
     */
    protected $_responseCallback;

    /**
     * Retrun iterator as response
     *
     * @var mixin
     */
    protected $_responseIterator;

    /**
     * Constructor
     *
     * @param Rediska_Connection $connection Connection
     * @param array|string       $command    Command
     */
    public function __construct(Rediska_Connection $connection, $command)
    {
        if (is_array($command)) {
            $command = self::transformMultiBulkCommand($command);
        }

        $this->_connection = $connection;
        $this->_command    = $command;
    }

    /**
     * Write command to connection
     *
     * @return boolean
     */
    public function write()
    {
        $result = $this->getConnection()->write($this->getCommand());
        $this->_isWritten = true;

        return $result;
    }

    /**
     * Is writed?
     *
     * @return boolean
     */
    public function isWritten()
    {
        return $this->_isWritten;
    }

    /**
     * Read response from connection
     *
     * @return array|string
     */
    public function read()
    {
        if (!$this->isWritten()) {
            throw new Rediska_Connection_Exec_Exception('You must write command before read');
        }

        $this->_isWritten = false;

        if ($this->getResponseIterator() !== null) {
            if ($this->getResponseIterator() === true) {
                $className = 'Rediska_Connection_Exec_MultiBulkIterator';
            } else {
                $className = $this->getResponseIterator();
            }

            $response = new $className($this->getConnection(), $this->getResponseCallback());
        } else {
            $response = self::readResponseFromConnection($this->getConnection());

            if ($this->_responseCallback !== null) {
                $response = call_user_func($this->getResponseCallback(), $response);
            }
        }

        return $response;
    }

    /**
     * Execute command
     *
     * @return array|string
     */
    public function execute()
    {
        $this->write();
        return $this->read();
    }

    /**
     * Magic method for execute
     *
     * @return array|string
     */
    public function __invoke()
    {
        return $this->execute();
    }

    /**
     * Get connection
     *
     * @return Rediska_Connection
     */
    public function getConnection()
    {
        if ($this->_responseIterator === null) {
            return $this->_connection;
        } else {
            if ($this->_connectionClone === null) {
                $this->_connectionClone = clone $this->_connection;
            }

            return $this->_connectionClone;
        }

        return $this->_connection;
    }

    /**
     * Get command
     *
     * @return string
     */
    public function getCommand()
    {
        return $this->_command;
    }

    /**
     * Set response callback
     *
     * @param mixin $callback
     * @return Rediska_Connection_Exec
     */
    public function setResponseCallback($callback)
    {
        if ($callback !== null && !is_callable($callback)) {
            throw new Rediska_Connection_Exec_Exception('Bad callback');
        }

        $this->_responseCallback = $callback;

        return $this;
    }

    /**
     * Get response callback
     *
     * @return mixin
     */
    public function getResponseCallback()
    {
        return $this->_responseCallback;
    }

    /**
     * Set response iterator
     *
     * @param boolean $enable
     */
    public function setResponseIterator($responseIterator)
    {
        $this->_responseIterator = $responseIterator;

        return $this;
    }

    /**
     * Is enabled response iterator
     *
     * @return boolean
     */
    public function getResponseIterator()
    {
        return $this->_responseIterator;
    }

    /**
     * Transfrom Multi Bulk command to string
     *
     * @param array $command
     * @return string
     */
    public static function transformMultiBulkCommand(array $command)
    {
        $commandString = self::REPLY_MULTY_BULK . count($command) . Rediska::EOL;
        foreach($command as $argument) {
            $commandString .= self::REPLY_BULK . strlen($argument) . Rediska::EOL . $argument . Rediska::EOL;
        }
        return $commandString;
    }

    /**
     * Read response from connection
     *
     * @param Rediska_Connection $connection
     * @return mixed
     */
    public static function readResponseFromConnection(Rediska_Connection $connection)
    {
        $reply = $connection->readLine();

        if ($reply === null) {
            return $reply;
        }

        $type = substr($reply, 0, 1);
        $data = substr($reply, 1);

        switch ($type) {
            case self::REPLY_STATUS:
                if ($data == 'OK') {
                    return true;
                } else {
                    return $data;
                }
            case self::REPLY_ERROR:
                throw new Rediska_Connection_Exec_Exception($data);
            case self::REPLY_INTEGER:
                if (strpos($data, '.') !== false) {
                    $number = (integer)$data;
                } else {
                    $number = (float)$data;
                }

                return $number;
            case self::REPLY_BULK:
                if ($data == '-1') {
                    return null;
                } else {
                    $length = (integer)$data;

                    return $connection->read($length);
                }
            case self::REPLY_MULTY_BULK:
                $count = (integer)$data;

                $replies = array();
                for ($i = 0; $i < $count; $i++) {
                    $replies[] = self::readResponseFromConnection($connection);
                }

                return $replies;
            default:
                throw new Rediska_Connection_Exec_Exception("Invalid reply type: '$type'");
        }
    }
}


<?php

class Rediska_Zend_Profiler_Firebug extends Rediska_Profiler
{
    protected $_message;

    protected $_label = "Rediska '%rediskaName%' instance commands: %count% in %totalElapsedTime% sec";

    /**
     * Constructor
     *
     * @param array $options
     */
    public function __construct($options = array())
    {
        if (isset($options['label'])) {
            $this->setLabel($options['label']);
        }
    }

    public function setLabel($label)
    {
        $this->_label = $label;

        return $this;
    }

    public function getLabel()
    {
        return $this->_label;
    }

    public function getMessage()
    {
        if (!$this->_message) {
             $message = new Zend_Wildfire_Plugin_FirePhp_TableMessage('');
             $message->setBuffered(true);
             $message->setHeader(array('Time', 'Command', 'Arguments'));
             $message->setDestroy(false);
             $message->setOption('includeLineNumbers', false);

             Zend_Wildfire_Plugin_FirePhp::getInstance()->send($message);

             $this->_message = $message;
        }

        return $this->_message;
    }

    /**
     * Stop callback. Called from profile
     *
     * @param Rediska_Profiler_Profile $profile
     */
    public function stopCallback(Rediska_Profiler_Profile $profile)
    {
        $commandString = $profile->getContext()->__toString();

        $matches = array();
        preg_match('/^(.+)\((.*)\)$/s', $commandString, $matches);

        $row = array((double)$profile->getElapsedTime(4));

        if (isset($matches[1])) {
            $row[] = $matches[1];
            $row[] = $matches[2];
        } else {
            $row[] = $commandString;
        }

        $this->getMessage()->addRow($row);

        $placeHolders = array(
            '%rediskaName%'      => $profile->getContext()->getRediska()->getName(),
            '%count%'            => $this->count(),
            '%totalElapsedTime%' => $this->getTotalElapsedTime(4),
        );

        $label = str_replace(
            array_keys($placeHolders),
            array_values($placeHolders),
            $this->getLabel()
        );

        $this->getMessage()->setLabel($label);
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../../Rediska.php';

/**
 * Rediska List key
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Key objects
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Key_List extends Rediska_Key_Abstract implements IteratorAggregate, ArrayAccess, Countable
{
    /**
     * Append value to the end of List
     *
     * @param mixed             $value              Value
     * @param boolean[optional] $createIfNotExists  Create list if not exists
     * @return boolean
     */
    public function append($value, $createIfNotExists = true)
    {
        $result = $this->_getRediskaOn()->appendToList($this->getName(), $value, $createIfNotExists);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Append value to the head of List
     *
     * @param mixed             $value              Value
     * @param boolean[optional] $createIfNotExists  Create list if not exists
     * @return boolean
     */
    public function prepend($value, $createIfNotExists = true)
    {
        $result = $this->_getRediskaOn()->prependToList($this->getName(), $value, $createIfNotExists);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Get List length
     *
     * @return integer
     */
    public function getLength()
    {
        return $this->_getRediskaOn()->getListLength($this->getName());
    }

    /**
     * Trim the list at key to the specified range of elements
     *
     * @param integer $start Start index
     * @param integer $end End index
     * @return boolean
     */
    public function truncate($start, $end)
    {
        $result = $this->_getRediskaOn()->truncateList($this->getName(), $start, $end);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Return element of List by index
     *
     * @param integer $index Index
     * @return mixed
     */
    public function get($index)
    {
        return $this->_getRediskaOn()->getFromList($this->getName(), $index);
    }

    /**
     * Set a new value as the element at index position of the List
     *
     * @param integer $index Index
     * @param mixed $value Value
     * @return boolean
     */
    public function set($index, $value)
    {
        $result = $this->_getRediskaOn()->setToList($this->getName(), $index, $value);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Insert a new value as the element after the reference value
     *
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return integer|boolean
     */
    public function insertAfter($referenceValue, $value)
    {
        return $this->_getRediskaOn()->insertToListAfter($this->getName(), $referenceValue, $value);
    }

    /**
     * Insert a new value as the element before the reference value
     *
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return integer|boolean
     */
    public function insertBefore($referenceValue, $value)
    {
        return $this->_getRediskaOn()->insertToListBefore($this->getName(), $referenceValue, $value);
    }

    /**
     * Insert a new value as the element before or after the reference value
     *
     * @param string  $position       BEFORE or AFTER
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return integer|boolean
     */
    public function insert($position, $referenceValue, $value)
    {
        return $this->_getRediskaOn()->insertToList($this->getName(), $position, $referenceValue, $value);
    }

    /**
     * Delete element from list by value
     *
     * @throws Rediska_Exception
     * @param $value Element value
     * @param $count Limit of deleted items
     * @return integer
     */
    public function remove($value, $count = 0)
    {
        $result = $this->_getRediskaOn()->deleteFromList($this->getName(), $value, $count);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Return and remove the first element of the List
     *
     * @return mixed
     */
    public function shift()
    {
        $result = $this->_getRediskaOn()->shiftFromList($this->getName());

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Return and remove the first element of the List and block if list empty or not exists
     *
     * @param $timeout Blocking timeout
     * @return mixed
     */
    public function shiftBlocking($timeout = 0)
    {
        $result = $this->_getRediskaOn()->shiftFromListBlocking($this->getName(), $timeout);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Return and remove the last element of the List
     *
     * @param string|Rediska_Key_List $pushTo After pop, push value to another list
     * @return mixed
     */
    public function pop($pushTo = null)
    {
        if ($pushTo instanceof Rediska_Key_List) {
            $pushTo = $pushTo->getName();
        }

        $result = $this->_getRediskaOn()->popFromList($this->getName(), $pushTo);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Return and remove the last element of the List and block if list empty or not exists.
     *
     * @param integer $timeout[optional]   Blocking timeout. 0 for default - timeout is disabled.
     * @param string  $pushToKey[optional] If not null - push value to another list.
     * @return mixed
     */
    public function popBlocking($timeout = 0, $pushToKey = null)
    {
        $result = $this->_getRediskaOn()->popFromListBlocking($this->getName(), $timeout, $pushToKey);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Get List values
     *
     * @param integer $start                       Start index
     * @param integer $end                         End index
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return array
     */
    public function getValues($start = 0, $end = -1, $responseIterator = false)
    {
        return $this->_getRediskaOn()->getList($this->getName(), $start, $end, $responseIterator);
    }

    /**
     * Get sorted the elements
     *
     * @param string|array  $value Options or SORT query string (http://code.google.com/p/redis/wiki/SortCommand).
     *                             Important notes for SORT query string:
     *                                 1. If you set Rediska namespace option don't forget add it to key names.
     *                                 2. If you use more then one connection to Redis servers, it will choose by key name,
     *                                    and key by you pattern's may not present on it.
     * @return array
     */
    public function sort($options = array())
    {
        return $this->_getRediskaOn()->sort($this->getName(), $options);
    }

    /**
     * Get List values
     *
     * @param integer $start                       Start index
     * @param integer $end                         End index
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return array
     */
    public function toArray($start = 0, $end = -1, $responseIterator = false)
    {
        return $this->getValues($start, $end, $responseIterator);
    }

    /**
     * Add array to List
     *
     * @param array $array
     */
    public function fromArray(array $array)
    {
        $pipeline = $this->_getRediskaOn()->pipeline();
        foreach($array as $item) {
            $pipeline->appendToList($this->getName(), $item);
        }

        if (!is_null($this->getExpire())) {
            $pipeline->expire($this->getName(), $this->getExpire(), $this->isExpireTimestamp());
        }

        $pipeline->execute();

        return true;
    }

    /**
     * Implement intrefaces
     */

    public function count()
    {
        return $this->getLength();
    }

    public function getIterator()
    {
        return new ArrayObject($this->toArray());
    }

    public function offsetSet($offset, $value)
    {
        if (is_null($offset)) {
            $this->append($value);
        } else {
            $this->set($offset, $value);
        }

        return $value;
    }

    public function offsetExists($offset)
    {
        return (boolean)$this->get($offset);
    }

    public function offsetUnset($offset)
    {
        throw new Rediska_Key_Exception("Redis not support delete by index. User 'remove' method for delete by value");
    }

    public function offsetGet($offset)
    {
        return $this->get($offset);
    }
}


<?php

/**
 * Get all the members of the Sorted Set value at key
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_GetSortedSet extends Rediska_Command_Abstract
{
    /**
     * Supported version
     *
     * @var string
     */
    protected $_version = '1.1';

    /**
     * Create command
     *
     * @param string  $key                         Key name
     * @param integer $withScores[optional]        Return values with scores. For default is false.
     * @param integer $start[optional]             Start index. For default is begin of set.
     * @param integer $end[optional]               End index. For default is end of set.
     * @param boolean $revert[optional]            Revert elements (not used in sorting). For default is false
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return Rediska_Connection_Exec
     */
    public function create($key, $withScores = false, $start = 0, $end = -1, $revert = false, $responseIterator = false)
    {
        $connection = $this->_rediska->getConnectionByKeyName($key);

        $command = array($revert ? 'ZREVRANGE' : 'ZRANGE',
                         $this->_rediska->getOption('namespace') . $key,
                         $start,
                         $end);

        if ($withScores) {
            $command[] = 'WITHSCORES';
        }

        $exec = new Rediska_Connection_Exec($connection, $command);

        if ($responseIterator) {
            if ($withScores) {
                $responseIterator = 'Rediska_Command_GetSortedSet_WithScoresIterator';
            }
            $exec->setResponseIterator($responseIterator);
            $exec->setResponseCallback(array($this, 'parseIteratorResponse'));
        }

        return $exec;
    }

    /**
     * Parse response
     *
     * @param array $response
     * @return array
     */
    public function parseResponse($response)
    {
        if ($this->responseIterator) {
            return $response;
        } else {
            $values = $response;

            if ($this->withScores) {
                $values = Rediska_Command_Response_ValueAndScore::combine($this->_rediska, $values);
            } else {
                $values = array_map(array($this->_rediska->getSerializer(), 'unserialize'), $values);
            }

            return $values;
        }
    }

    public function parseIteratorResponse($response)
    {
        if ($this->withScores) {
            list($value, $score) = $response;

            $value = $this->getRediska()->getSerializer()->unserialize($value);

            return new Rediska_Command_Response_ValueAndScore(array('value' => $value, 'score' => $score));
        } else {
            return $this->getRediska()->getSerializer()->unserialize($response);
        }
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../../../../Rediska.php';

/**
 * @see Zend_Cache_Backend
 */
require_once 'Zend/Cache/Backend.php';

/**
 * @see Zend_Cache_Backend_ExtendedInterface
 */
require_once 'Zend/Cache/Backend/ExtendedInterface.php';

/**
 * Redis adapter for Zend_Cache
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage ZendFrameworkIntegration
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Zend_Cache_Backend_Redis extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
{
    /**
     * Defines the hash field name for the cached data.
     * @var string
     */
    const FIELD_DATA      = 'd';
    /**
     * Defines the hash field name for the cached data mtime.
     * @var string
     */
    const FIELD_MTIME     = 'm';
    /**
     * Defines the hash field name for the cached item tags.
     * @var string
     */
    const FIELD_TAGS      = 't';
    /**
     * Defines the hash field name for the infinite item.
     * @var string
     */
    const FIELD_INF       = 'i';

    /**
     * Redis backend limit
     * @var integer
     */
    const MAX_LIFETIME    = 2592000;
    /**
     * The options storage for this Backend
     * @var array
     */
    protected $_options = array(
        'storage' => array(
            'set_ids'         => 'zc:ids',
            'set_tags'        => 'zc:tags',
            'prefix_key'      => 'zc:k:',
            'prefix_tag_ids'  => 'zc:ti:',
        )
    );
    /**
     * Rediska instance
     *
     * @var Rediska
     */
    protected $_rediska = Rediska::DEFAULT_NAME;
    /**
     *
     * @var Rediska_Transaction
     */
    protected $_transaction;

    /**
     * Contruct Zend_Cache Redis backend
     *
     * Available options are :
     * 'storage`
     *     - set_ids        : the set name that all ids are storage
     *     - set_tags       : the set name that stores the tags
     *     - prefix_key     : the prefix value for all item keys
     *     - prefix_tag_ids : the key prefix value for all tag -to- id items
     *
     * @param mixed $rediska Rediska instance name, Rediska object or array of options
     */
    public function __construct($options = array())
    {
        if ($options instanceof Zend_Config) {
            $options = $options->toArray();
        }
        if (isset($options['rediska'])) {
            $this->setRediska($options['rediska']);
        }
        if(isset($options['storage'])){
            $this->setStorage($options['storage']);
        }
    }
    /**
     *
     * @param  array $options
     * @return Rediska_Zend_Cache_Backend_Redis
     * @throws Zend_Cache_Exception
     */
    public function setStorage($options)
    {
        foreach ($options as $name => $value) {
            if (!is_string($value)) {
                Zend_Cache::throwException(
                    sprintf(
                        'Incorrect value for option %s : `string` expected `%s` provided',
                        $name, gettype($value)
                    )
                );
            }
            $name = strtolower($name);
            if(isset($this->_options['storage'][$name]) ){
                $this->_options['storage'][$name] = $value;
            }
        }
        return $this;
    }
    /**
     *
     * @param  Rediska $rediska
     * @return Rediska_Zend_Cache_Backend_Redis
     */
    public function setRediska($rediska)
    {
        $this->_rediska = $rediska;
        return $this;
    }
    /**
     *
     * @return Rediska
     */
    public function getRediska()
    {
        if (!is_object($this->_rediska)) {
            $this->_rediska = Rediska_Options_RediskaInstance::getRediskaInstance(
                $this->_rediska, 'Zend_Cache_Exception', 'backend'
            );
        }

        return $this->_rediska;
    }
    /**
     *
     * @param  string $name
     * @return multitype:|boolean
     */
    public function getOption($name)
    {
        if (!is_string($name)) {
            Zend_Cache::throwException(
                sprintf(
                    'Incorrect option name %s provided, `string` expected `%s` provided',
                    $name, gettype($name)
                )
            );
        }
        $name = strtolower($name);
        if(isset($this->_options[$name])){
            return $this->_options[$name];
        }
        return false;
    }
    /**
     * Load value with given id from cache
     *
     * @param  string  $id                     Cache id
     * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
     * @return string|false cached datas
     */
    public function load($id, $doNotTestCacheValidity = false)
    {
        $id = (array) $id;
        foreach ($id as $key){
            $key = $this->_options['storage']['prefix_key'] . $key;
            $this->_getTransactionByKey($key)
                ->getFromHash(
                    $key,
                    self::FIELD_DATA
                );
        }
        $oldSerializaerAdapter = $this->getRediska()->getSerializer()->getAdapter();
        $this->getRediska()->setSerializerAdapter('toString');
        $result = $this->_execTransactions();
        $this->getRediska()->getSerializer()->setAdapter($oldSerializaerAdapter);
        if(count($result) == 1){
            if(null === ($result = array_shift($result))){
                return false;
            } else {
                return $result;
            }
        } else {
            return $result;
        }
    }

    /**
     * Test if a cache is available or not (for the given id)
     *
     * @param  string $id Cache id
     * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
     */
    public function test($id)
    {
        $mtime = $this->getRediska()->getFromHash(
            $this->_options['storage']['prefix_key'].$id, self::FIELD_MTIME
        );
        return ($mtime ? $mtime : false);
    }

    /**
     * Save some string datas into a cache record
     *
     * Note : $data is always "string" (serialization is done by the
     * core not by the backend)
     *
     * @param  string  $data             Datas to cache
     * @param  string  $id               Cache id
     * @param  array   $tags             Array of strings, the cache record will be tagged by each string entry
     * @param  int     $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
     * @return boolean True if no problem
     */
    public function save($data, $id, $tags = array(), $specificLifetime = false)
    {
        if(!is_array($tags)) $tags = array($tags);

        $lifetime = $this->getLifetime($specificLifetime);

        $oldTags = explode(
            ',', $this->getRediska()->getFromHash(
                $this->_options['storage']['prefix_key'].$id, self::FIELD_TAGS
            )
        );

        $this->_getTransactionByKey($this->_options['storage']['prefix_key'].$id)
            ->setToHash(
                $this->_options['storage']['prefix_key'].$id,  array(
                self::FIELD_DATA => $data,
                self::FIELD_TAGS => implode(',',$tags),
                self::FIELD_MTIME => time(),
                self::FIELD_INF => $lifetime ? 0 : 1)
            );
        $this->_getTransactionByKey($this->_options['storage']['prefix_key'].$id)
            ->expire($this->_options['storage']['prefix_key'].$id, $lifetime ? $lifetime : self::MAX_LIFETIME);
        if ($addTags = ($oldTags ? array_diff($tags, $oldTags) : $tags)) {
            foreach ($addTags as $add) {
                $this->_getTransactionByKey($this->_options['storage']['set_tags'])
                    ->addToSet($this->_options['storage']['set_tags'], $add);
            }
            foreach($addTags as $tag){
                $this->_getTransactionByKey($this->_options['storage']['prefix_tag_ids'] . $tag)
                    ->addToSet($this->_options['storage']['prefix_tag_ids'] . $tag, $id);
            }
        }
        if ($remTags = ($oldTags ? array_diff($oldTags, $tags) : false)){
            foreach($remTags as $tag){
                $this->_getTransactionByKey($this->_options['storage']['prefix_tag_ids'] . $tag)
                    ->deleteFromSet($this->_options['storage']['prefix_tag_ids'] . $tag, $id);
            }
        }
        $this->_getTransactionByKey($this->_options['storage']['set_ids'])
            ->addToSet($this->_options['storage']['set_ids'], $id);
        try {
            $this->_execTransactions();
            return true;
        } catch(Rediska_Transaction_Exception $e){
            $this->_log($e->getMessage(), Zend_Log::ERR);
            return false;
        }

    }

    /**
     * Remove a cache record
     *
     * @param  string $id Cache id
     * @return boolean True if no problem
     */
    public function remove($id)
    {
        $tags = explode(
            ',', $this->getRediska()->getFromHash(
                $this->_options['storage']['prefix_key'].$id, self::FIELD_TAGS
            )
        );

        $this->_getTransactionByKey($this->_options['storage']['prefix_key'].$id)
            ->delete($this->_options['storage']['prefix_key'].$id);
        $this->_getTransactionByKey($this->_options['storage']['set_ids'], $id)
            ->deleteFromSet( $this->_options['storage']['set_ids'], $id );
        foreach($tags as $tag) {
            $this->_getTransactionByKey($this->_options['storage']['prefix_tag_ids'] . $tag)
                ->deleteFromSet($this->_options['storage']['prefix_tag_ids'] . $tag, $id);
        }
        $result = $this->_execTransactions();
        if(count($result)){
            return array_shift($result);
        } else {
            return false;
        }
    }

    /**
     * Clean some cache records
     *
     * Available modes are :
     * 'all' (default)  => remove all cache entries ($tags is not used)
     * 'old'            => supported
     * 'matchingTag'    => supported
     * 'notMatchingTag' => supported
     * 'matchingAnyTag' => supported
     *
     * @param  string $mode Clean mode
     * @param  array  $tags Array of tags
     * @throws Zend_Cache_Exception
     * @return boolean True if no problem
     */
    public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
    {
        if( $tags && ! is_array($tags)) {
            $tags = array($tags);
        }
        $result = true;
        switch ($mode) {
            case Zend_Cache::CLEANING_MODE_ALL:
                $this->_removeIds($this->getIds());
                break;

            case Zend_Cache::CLEANING_MODE_OLD:
                break;

            case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
                $this->_removeIdsByMatchingTags($tags);
                break;

            case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
                $this->_removeIdsByNotMatchingTags($tags);
                break;

            case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
                $this->_removeIdsByMatchingAnyTags($tags);
                break;

            default:
                Zend_Cache::throwException('Invalid mode for clean() method: '.$mode);
        }
        return $this->_collectGarbage();
    }
    /**
     *
     * @param  array $ids
     * @return boolean
     */
    protected function _removeIds($ids = array())
    {
        foreach($ids as $id){
            $this->_getTransactionByKey($this->_options['storage']['prefix_key']. $id)
                ->delete($this->_options['storage']['prefix_key']. $id);
            $this->_getTransactionByKey($this->_options['storage']['set_ids'])
                ->deleteFromSet( $this->_options['storage']['set_ids'], $id);
        }
        return (bool) $this->_execTransactions();
    }

    /**
     * @param array $tags
     */
    protected function _removeIdsByNotMatchingTags($tags)
    {
        $ids = $this->getIdsNotMatchingTags($tags);
        $this->_removeIds($ids);
    }
    /**
     * @param array $tags
     */
    protected function _removeIdsByMatchingTags($tags)
    {
        $ids = $this->getIdsMatchingTags($tags);
        $this->_removeIds($ids);
    }

    /**
     * @param array $tags
     */
    protected function _removeIdsByMatchingAnyTags($tags)
    {
        $ids = $this->getIdsMatchingAnyTags($tags);
        $this->_removeIds($ids);

        foreach ($this->_preprocessTagIds($tags) as $tag) {
            $this->_getTransactionByKey($tag)->delete($tag);
        }
        $this->_getTransactionByKey($this->_options['storage']['set_tags'])
            ->deleteFromSet( $this->_options['storage']['set_tags'], $tags);

        return (bool) $this->_execTransactions();
    }
    /**
     * Return true if the automatic cleaning is available for the backend
     *
     * @return boolean
     */
    public function isAutomaticCleaningAvailable()
    {
        return true;
    }

    /**
     * Set the frontend directives
     *
     * @param  array $directives Assoc of directives
     * @throws Zend_Cache_Exception
     * @return void
     */
    public function setDirectives($directives)
    {
        parent::setDirectives($directives);
        $lifetime = $this->getLifetime(false);
        if ($lifetime > self::MAX_LIFETIME) {
            $this->_log(
                'redis backend has a limit of 30 days (' . self::MAX_LIFETIME
                . ' seconds) for the lifetime'
            );
        }
    }

    /**
     * Return an array of stored cache ids
     *
     * @return array array of stored cache ids (string)
     */
    public function getIds()
    {
        return (array) $this->getRediska()->getSet($this->_options['storage']['set_ids']);
    }

    /**
     * Return an array of stored tags
     *
     * @return array array of stored tags (string)
     */
    public function getTags()
    {
        return $this->getRediska()->getSet($this->_options['storage']['set_tags']);
    }

    /**
     * Return an array of stored cache ids which match given tags
     *
     * In case of multiple tags, a logical AND is made between tags
     *
     * @param array $tags array of tags
     * @return array array of matching cache ids (string)
     */
    public function getIdsMatchingTags($tags = array())
    {
        return (array) $this->getRediska()->intersectSets(
            $this->_preprocessTagIds($tags)
        );
    }

    /**
     * Return an array of stored cache ids which don't match given tags
     *
     * In case of multiple tags, a logical OR is made between tags
     *
     * @param array $tags array of tags
     * @return array array of not matching cache ids (string)
     */
    public function getIdsNotMatchingTags($tags = array())
    {
            $sets = $this->_preprocessTagIds($tags);
            array_unshift($sets, $this->_options['storage']['set_ids']);
            $data = $this->getRediska()->diffSets($sets);
            return $data;
    }

    /**
     * Return an array of stored cache ids which match any given tags
     *
     * In case of multiple tags, a logical AND is made between tags
     *
     * @param array $tags array of tags
     * @return array array of any matching cache ids (string)
     */
    public function getIdsMatchingAnyTags($tags = array())
    {
        return (array) $this->getRediska()->unionSets($this->_preprocessTagIds($tags));
    }

    /**
     * Return the filling percentage of the backend storage
     *
     * @throws Zend_Cache_Exception
     * @return int integer between 0 and 100
     */
    public function getFillingPercentage()
    {
        $this->_log("Filling percentage not supported by the Redis backend");
        return 0;
    }

    /**
     * Return an array of metadatas for the given cache id
     *
     * The array must include these keys :
     * - expire : the expire timestamp
     * - tags : a string array of tags
     * - mtime : timestamp of last modification time
     *
     * @param string $id cache id
     * @return array array of metadatas (false if the cache id is not found)
     */
    public function getMetadatas($id)
    {
        $metaData = $this->getRediska()->getFromHash(
            $this->_options['storage']['prefix_key'].$id,
            array(self::FIELD_DATA, self::FIELD_TAGS, self::FIELD_MTIME, self::FIELD_INF)
        );
        if(!$metaData[self::FIELD_MTIME]) {
          return false;
        }
        $lifetime = $this->getRediska()
            ->getLifetime($this->_options['storage']['prefix_key'] . $id);
        $tags = explode(',', $metaData[self::FIELD_TAGS]);
        $expire = $metaData[self::FIELD_INF] === '1' ? false : time() + $lifetime;

        return array(
            'expire' => $expire,
            'tags'   => $tags,
            'mtime'  => $metaData[self::FIELD_MTIME],
        );
    }

    /**
     * Give (if possible) an extra lifetime to the given cache id
     *
     * @param string $id cache id
     * @param int $extraLifetime
     * @return boolean true if ok
     */
    public function touch($id, $extraLifetime)
    {
        $data = $this->getRediska()->getFromHash(
            $this->_options['storage']['prefix_key'].$id, array(self::FIELD_INF)
        );
        $lifetime = $this->getRediska()
            ->getLifetime($this->_options['storage']['prefix_key'] . $id);
        if ($data[self::FIELD_INF] === 0) {
            $expireAt = time() + $lifetime + $extraLifetime;
            return (bool) $this->getRediska()->expire(
                $this->_options['storage']['prefix_key'].$id, $expireAt, true
            );
        }
        return false;
    }

    /**
     * Return an associative array of capabilities (booleans) of the backend
     *
     * The array must include these keys :
     * - automatic_cleaning (is automating cleaning necessary)
     * - tags (are tags supported)
     * - expired_read (is it possible to read expired cache records
     *                 (for doNotTestCacheValidity option for example))
     * - priority does the backend deal with priority when saving
     * - infinite_lifetime (is infinite lifetime can work with this backend)
     * - get_list (is it possible to get the list of cache ids and the complete list of tags)
     *
     * @return array associative of with capabilities
     */
    public function getCapabilities()
    {
        return array(
            'automatic_cleaning' => true,
            'tags'               => true,
            'expired_read'       => true,
            'priority'           => false,
            'infinite_lifetime'  => true,
            'get_list'           => true
        );
    }
    /**
     * Cleans up expired keys and list members
     * @return boolean
     */
    protected function _collectGarbage()
    {
        $exists = array();
        $tags = $this->getTags();
        foreach($tags as $tag){
            $prefix_tag_ids = $this->_options['storage']['prefix_tag_ids'] . $tag;
            $tagMembers = $this->getRediska()->getSet($prefix_tag_ids);
            $this->_getTransactionByKey($prefix_tag_ids)->watch($prefix_tag_ids);
            $expired = array();
            if(count($tagMembers)) {
                foreach($tagMembers as $id) {
                    if( ! isset($exists[$id])) {
                        $exists[$id] = $this->getRediska()
                            ->exists($this->_options['storage']['prefix_key'].$id);
                    }
                    if(!$exists[$id]) {
                        $expired[] = $id;
                    }
                }
                if(!count($expired)) continue;
            }
            if(!count($tagMembers) || count($expired) == count($tagMembers)) {
                $this->_getTransactionByKey($this->_options['storage']['set_tags'])
                    ->deleteFromSet($this->_options['storage']['set_tags'], $tag);
                $this->_getTransactionByKey($this->_options['storage']['prefix_tag_ids'] . $tag)
                    ->delete($this->_options['storage']['prefix_tag_ids'] . $tag);
            } else {
                $this->_getTransactionByKey($this->_options['storage']['prefix_tag_ids'] . $tag)
                    ->deleteFromSet( $this->_options['storage']['prefix_tag_ids'] . $tag, $expired);
            }
            $this->_getTransactionByKey($this->_options['storage']['set_ids'])
                ->deleteFromSet( $this->_options['storage']['set_ids'], $expired);
        }
        try{
            return (bool) $this->_execTransactions();
        } catch (Rediska_Transaction_AbortedException $e ){
            $this->_log($e->getMessage(), Zend_Log::ERR);
            return false;
        }
    }
    /**
     * @param $item
     * @param $index
     * @param $prefix
     */
    protected function _preprocess(&$item, $index, $prefix)
    {
        $item = $prefix . $item;
    }

    /**
     * @param $ids
     * @return array
     */
    protected function _preprocessIds($ids)
    {
        array_walk($ids, array($this, '_preprocess'), $this->_options['storage']['prefix_key']);
        return $ids;
    }
    /**
     * @param $tags
     * @return array
     */
    protected function _preprocessTagIds($tags)
    {
        if($tags){
            array_walk($tags, array($this, '_preprocess'), $this->_options['storage']['prefix_tag_ids']);
        }
        return $tags;
    }

    /**
     * @param string $key
     *
     * @return Rediska_Transaction
     */
    protected function _getTransactionByKey($key)
    {
        $connection = $this->getRediska()->getConnectionByKeyName($key);
        if(!$this->_transaction[$connection->getAlias()]){
            $this->_transaction[$connection->getAlias()] = $this->getRediska()
                ->transaction($connection);
        }
        return $this->_transaction[$connection->getAlias()];
    }

    /**
     * @return array
     */
    protected function _execTransactions()
    {
        $result = array();
        /* @var Rediska_Transaction $transaction */
        if ($this->_transaction) {
            foreach ($this->_transaction as $transaction) {
                /*
                 * Do not execute empty transaction avoiding a false
                 * `Rediska_Transaction_AbortedException`
                 */
                $preamble = substr(
                    (string)$transaction, 0,
                    strlen(Rediska_Transaction::TRANSACTION_PREAMBLE)
                );
                if (Rediska_Transaction::TRANSACTION_PREAMBLE == $preamble){
                    $result = array_merge($result, $transaction->execute());
                } else {
                    $transaction->discard();
                }
            }
        }
        return $result;
    }
}


<?php

/**
 * Set value to a key or muliple values to multiple keys
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_Set extends Rediska_Command_Abstract
{
    protected $_multiple = false;

    /**
     * Create command
     *
     * @param string|array $keyOrData                  Key name or array with key => value.
     * @param mixed        $valueOrOverwrite[optional] Value or overwrite property for array of values. For default true.
     * @param boolean      $overwrite[optional]        Overwrite for single value (if false don't set and return false if key already exist). For default true.
     * @return Rediska_Connection_Exec
     */
    public function create($keyOrData, $valueOrOverwrite = null, $overwrite = true)
    {
        if (is_array($keyOrData)) {
            $this->_throwExceptionIfNotSupported('1.1');

            $this->_multiple = true;
            $data = $keyOrData;
            $overwrite = ($valueOrOverwrite === null || $valueOrOverwrite);

            if (empty($data)) {
                throw new Rediska_Command_Exception('Not present keys and values for set');
            }

            $connections = array();
            $keysByConnections = array();
            foreach ($data as $key => $value) {
                $connection = $this->_rediska->getConnectionByKeyName($key);
                $connectionAlias = $connection->getAlias();
                if (!array_key_exists($connectionAlias, $connections)) {
                    $connections[$connectionAlias] = $connection;
                    $keysByConnections[$connectionAlias] = array();
                }
                $keysByConnections[$connectionAlias][$key] = $value;
            }

            $commands = array();
            $namespace = $this->_rediska->getOption('namespace');
            foreach($keysByConnections as $connectionAlias => $data) {
                $command = array($overwrite ? 'MSET' : 'MSETNX');
                foreach($data as $key => $value) {
                    $command[] = $namespace . $key;
                    $command[] = $this->_rediska->getSerializer()->serialize($value);
                }
                $commands[] = new Rediska_Connection_Exec($connections[$connectionAlias], $command);
            }

            return $commands;
        } else {
            $key   = $keyOrData;
            $value = $valueOrOverwrite;

            $connection = $this->_rediska->getConnectionByKeyName($key);

            $value = $this->_rediska->getSerializer()->serialize($value);

            $command = array($overwrite ? 'SET' : 'SETNX',
                             $this->_rediska->getOption('namespace') . $key,
                             $value);

            return new Rediska_Connection_Exec($connection, $command);
        }
    }

    /**
     * Parse responses
     *
     * @param array $responses
     * @return boolean
     */
    public function parseResponses($responses)
    {
        if ($this->_multiple) {
            if (!empty($responses)) {
                foreach($responses as $response) {
                    if (!$response) {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
        } else {
            return (boolean)$responses[0];
        }
    }
}


<?php

/**
 * Return and remove the first element of the List at key and block if list is empty or not exists
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_ShiftFromListBlocking extends Rediska_Command_Abstract
{
    /**
     * Supported version
     *
     * @var string
     */
    protected $_version = '1.3.1';

    /**
     * Create command
     *
     * @param string $keyOrKeys   Key name or array of names
     * @param string $timeout     Blocking timeout in seconds. Timeout disabled for default.
     * @return Rediska_Connection_Exec
     */
    public function create($keyOrKeys, $timeout = 0)
    {
        if (!is_array($keyOrKeys)) {
            $keys = array($keyOrKeys);
        } elseif (!empty($keyOrKeys)) {
            $keys = $keyOrKeys;
        } else {
            throw new Rediska_Command_Exception('Not present keys for shift');
        }

        $connections = array();
        $keysByConnections = array();
        foreach ($keys as $key) {
            $connection = $this->_rediska->getConnectionByKeyName($key);
            $connectionAlias = $connection->getAlias();
            if (!array_key_exists($connectionAlias, $connections)) {
                $connections[$connectionAlias] = $connection;
                $keysByConnections[$connectionAlias] = array();
            }
            $keysByConnections[$connectionAlias][] = $key;
        }

        // TODO: Implement for many connections
        if (count($keysByConnections) > 1) {
            throw new Rediska_Command_Exception("Blocking shift until worked only with one connection. Try to use Rediska#on() method for specify it.");
        }

        $execs = array();
        foreach($keysByConnections as $connectionAlias => $keys) {
            $command = array('BLPOP');
            foreach($keys as $key) {
                $command[] = $this->_rediska->getOption('namespace') . $key;
            }
            $command[] = $timeout;

            $execs[] = new Rediska_Connection_Exec($connections[$connectionAlias], $command);
        }

        return $execs;
    }

    /**
     * Parse response
     *
     * @param array|string $response
     * @return mixed
     */
    public function parseResponse($response)
    {
        if (!is_array($this->keyOrKeys) && !empty($response)) {
            $result = $this->_rediska->getSerializer()->unserialize($response[1]);
        } else {
            $result = Rediska_Command_Response_ListNameAndValue::factory($this->_rediska, $response);
        }

        return $result;
    }
}


<?php

/**
 * Remove all the keys of the currently selected DB
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_FlushDb extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param boolean[optional] $all Remove from all Db. For default is false.
     * @return Rediska_Connection_Exec
     */
    public function create($all = false)
    {
        if ($all) {
            $command = array('FLUSHALL');
        } else {
            $command = array('FLUSHDB');
        }

        $commands = array();
        foreach($this->_rediska->getConnections() as $connection) {
            $commands[] = new Rediska_Connection_Exec($connection, $command);
        }

        return $commands;
    }

    /**
     * Parse response
     *
     * @param string $response
     * @return boolean
     */
    public function parseResponse($response)
    {
        return true;
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../../Rediska.php';

/**
 * Rediska Set key
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Key objects
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Key_Set extends Rediska_Key_Abstract implements IteratorAggregate, ArrayAccess, Countable
{
    /**
     * Add the specified member to the Set
     *
     * @param mixed $value Value
     * @return boolean
     */
    public function add($value)
    {
        $result = $this->_getRediskaOn()->addToSet($this->getName(), $value);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Remove the specified member from the Set
     *
     * @param mixed $value Value
     * @return boolean
     */
    public function remove($value)
    {
        $result = $this->_getRediskaOn()->deleteFromSet($this->getName(), $value);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Move the specified member from one Set to another atomically
     *
     * @param string|Rediska_Key_Set $set   Set key name or object
     * @param mixed                  $value Value
     * @return boolean
     */
    public function move($set, $value)
    {
        if ($set instanceof Rediska_Key_Set) {
            $set = $set->getName();
        }

        return $this->_getRediskaOn()->moveToSet($this->getName(), $set, $value);
    }

    /**
     * Get Set length
     *
     * @return integer
     */
    public function getLength()
    {
        return $this->_getRediskaOn()->getSetLength($this->getName());
    }

    /**
     * Test if the specified value is a member of the Set
     *
     * @param mixed  $value Value
     * @return boolean
     */
    public function exists($value)
    {
        return $this->_getRediskaOn()->existsInSet($this->getName(), $value);
    }

    /**
     * Return the intersection between the Sets
     *
     * @param string|Rediska_Key_Set|array $setOrSets    Set key name or object, or array of its
     * @param string|null                  $storeKeyName Store intersection to set with key name
     * @return array|boolean
     */
    public function intersect($setOrSets, $storeKeyName = null)
    {
        $sets = $this->_prepareSetsForCompare($setOrSets);

        return $this->_getRediskaOn()->intersectSets($sets, $storeKeyName);
    }

    /**
     * Return the union between the Sets
     *
     * @param string|Rediska_Key_Set|array $setOrSets    Set key name or object, or array of its
     * @param string|null                  $storeKeyName Store union to set with key name
     * @return array|boolean
     */
    public function union($setOrSets, $storeKeyName = null)
    {
        $sets = $this->_prepareSetsForCompare($setOrSets);

        return $this->_getRediskaOn()->unionSets($sets, $storeKeyName);
    }

    /**
     * Return the difference between the Sets
     *
     * @param string|array $setOrSets    Set key name or object, or array of its
     * @param string|null  $storeKeyName Store union to set with key name
     * @return array|boolean
     */
    public function diff($setOrSets, $storeKeyName = null)
    {
        $sets = $this->_prepareSetsForCompare($setOrSets);

        return $this->_getRediskaOn()->diffSets($sets, $storeKeyName);
    }

    /**
     * Get Set values
     *
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return array
     */
    public function getValues($responseIterator = false)
    {
        return $this->_getRediskaOn()->getSet($this->getName(), $responseIterator);
    }

    /**
     * Get sorted the elements
     *
     * @param string|array  $value Options or SORT query string (http://code.google.com/p/redis/wiki/SortCommand).
     *                             Important notes for SORT query string:
     *                                 1. If you set Rediska namespace option don't forget add it to key names.
     *                                 2. If you use more then one connection to Redis servers, it will choose by key name,
     *                                    and key by you pattern's may not present on it.
     * @return array
     */
    public function sort($options = array())
    {
        return $this->_getRediskaOn()->sort($this->getName(), $options);
    }

    /**
     * Get Set values
     *
     * @param boolean $responseIterator[optional]  If true - command return iterator which read from socket buffer.
     *                                             Important: new connection will be created
     * @return array
     */
    public function toArray($responseIterator = false)
    {
        return $this->getValues($responseIterator);
    }

    /**
     * Add array to Set
     *
     * @param array $array
     */
    public function fromArray(array $array)
    {
        // TODO: Use pipelines
        $pipeline = $this->_getRediskaOn()->pipeline();
        foreach($array as $item) {
            $pipeline->addToSet($this->getName(), $item);
        }

        if (!is_null($this->getExpire())) {
            $pipeline->expire($this->getName(), $this->getExpire(), $this->isExpireTimestamp());
        }

        $pipeline->execute();

        return true;
    }

    /**
     * Implement intrefaces
     */

    public function count()
    {
        return $this->getLength();
    }

    public function getIterator()
    {
        return new ArrayObject($this->toArray());
    }

    public function offsetSet($offset, $value)
    {
        if (!is_null($offset)) {
            throw new Rediska_Key_Exception('Offset is not allowed in sets');
        }

        $this->add($value);

        return $value;
    }

    public function offsetExists($value)
    {
        throw new Rediska_Key_Exception('Offset is not allowed in sets');
    }

    public function offsetUnset($value)
    {
        throw new Rediska_Key_Exception('Offset is not allowed in sets');
    }

    public function offsetGet($value)
    {
        throw new Rediska_Key_Exception('Offset is not allowed in sets');
    }

    protected function _prepareSetsForCompare($setOrSets)
    {
        if (!is_array($setOrSets)) {
            $sets = array($setOrSets);
        } else {
            $sets = $setOrSets;
        }

        foreach($sets as &$set) {
            if ($set instanceof Rediska_Key_Set) {
                $set = $set->getName();
            }
        }

        if (!in_array($this->getName(), $sets)) {
            array_unshift($sets, $this->getName());
        }

        return $sets;
    }
}


<?php

/**
 * Abstract class for provide options and Rediska instanse setter and getter to Rediska components
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Options_RediskaInstance extends Rediska_Options
{
    /**
     * Rediska instance
     *
     * @var string|array|Rediska
     */
    protected $_rediska = Rediska::DEFAULT_NAME;

    /**
     * Rediska instance option name
     *
     * @var string
     */
    protected $_rediskaOptionName = 'rediska';

    /**
     * Set Rediska instance
     *
     * @param array|string|Rediska $rediska Rediska instance name, Rediska object or Rediska options for new instance
     * @return $this
     */
    public function setRediska($rediska)
    {
        $this->_rediska = $rediska;

        return $this;
    }

    /**
     * Get Rediska instance
     *
     * @return Rediska
     */
    public function getRediska()
    {
        if (!is_object($this->_rediska)) {
            $this->_rediska = self::getRediskaInstance($this->_rediska, $this->_optionsException, $this->_rediskaOptionName);
        }

        return $this->_rediska;
    }

    public static function getRediskaInstance($rediska, $exceptionClassName = 'Rediska_Exception', $optionName = 'rediska')
    {
        if (is_string($rediska)) { // Rediska instance name
            if ($rediska == Rediska::DEFAULT_NAME) { // Default name
                if (Rediska_Manager::has($rediska)) {
                    $rediska = Rediska_Manager::get($rediska);
                } else {
                    throw new $exceptionClassName("You must instance '" . Rediska::DEFAULT_NAME . "' Rediska before or use '$optionName' option for specify another");
                }
            } else { // Another name
                $rediska = Rediska_Manager::get($rediska);
            }
        } else if (is_array($rediska)) { // Rediska options
            $options = $rediska;
            $options['addToManager'] = false;
            $rediska = new Rediska($options);
        } else if (!$rediska instanceof Rediska) {
            throw new $exceptionClassName("'$optionName' option must be instance name, Rediska object or rediska options for new instance");
        }

        return $rediska;
    }
}


<?php

// Require Rediska
require_once dirname(__FILE__) . '/../Rediska.php';

/**
 * Rediska basic key
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Key objects
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Key extends Rediska_Key_Abstract implements Countable, ArrayAccess
{
    /**
     * Set key value
     *
     * @param $value
     * @return boolean
     */
    public function setValue($value)
    {
        if ($this->getExpire() !== null && !$this->isExpireTimestamp()) {
            $reply = $this->setAndExpire($value, $this->getExpire());
        } else {
            $reply = $this->_getRediskaOn()->set($this->getName(), $value);

            if ($reply && !is_null($this->getExpire())) {
                $this->expire($this->getExpire(), $this->isExpireTimestamp());
            }
        }

        return $reply;
    }

    /**
     * Get key value
     *
     * @return mixed
     */
    public function getValue()
    {
        return $this->_getRediskaOn()->get($this->getName());
    }

    /**
     * Increment integer value
     *
     * @param integer $amount
     * @return integer
     */
    public function increment($amount = 1)
    {
        return $this->_getRediskaOn()->increment($this->getName(), $amount);
    }

    /**
     * Decrement integer value
     *
     * @param integer $amount
     * @return integer
     */
    public function decrement($amount = 1)
    {
        return $this->_getRediskaOn()->decrement($this->getName(), $amount);
    }

    /**
     * Set and expire
     *
     * @param mixed   $value
     * @param integer $seconds
     * @return boolean
     */
    public function setAndExpire($value, $seconds)
    {
        return $this->_getRediskaOn()->setAndExpire($this->getName(), $value, $seconds);
    }

    /**
     * Append value
     *
     * @param mixed $value Value
     * @return integer
     */
    public function append($value)
    {
        return $this->_getRediskaOn()->append($this->getName(), $value);
    }

    /**
     * Returns the bit value at offset in the string value stored at key
     *
     * @param integer $offset Offset
     * @param integer $bit    Bit (0 or 1)
     * @return integer
     */
    public function setBit($offset, $bit)
    {
        return $this->_getRediskaOn()->setBit($this->getName(), $offset, $bit);
    }

    /**
     * Returns the bit value at offset in the string value stored at key
     *
     * @param integer $offset Offset
     * @return integer
     */
    public function getBit($offset)
    {
        return $this->_getRediskaOn()->getBit($this->getName(), $offset);
    }

    /**
     * Set range
     *
     * @param string  $key    Key name
     * @param integer $offset Offset
     * @param integer $value  Value
     * @return string
     */
    public function setRange($offset, $value)
    {
        return $this->_getRediskaOn()->setRange($this->getName(), $offset, $value);
    }

    /**
     * Get range
     *
     * @param integer           $start Start
     * @param integer[optional] $end   End. If end is omitted, the substring starting from $start until the end of the string will be returned. For default end of string
     * @return mixin
     */
    public function getRange($start, $end = -1)
    {
        return $this->_getRediskaOn()->getRange($this->getName(), $start, $end);
    }

    /**
     * Get range
     *
     * @deprecated
     * @param integer           $start Start
     * @param integer[optional] $end   End. If end is omitted, the substring starting from $start until the end of the string will be returned. For default end of string
     * @return mixin
     */
    public function substring($start, $end = -1)
    {
        return $this->_getRediskaOn()->substring($this->getName(), $start, $end);
    }

    /**
     * Returns the length of the string
     *
     * @return integer
     */
    public function getLength()
    {
        return $this->_getRediskaOn()->getLength($this->getName());
    }

    /**
     * Get value, if value not present set it from chain method
     *
     * @param mixin[optional]   $object            Object of chain method
     * @param integer[optional] $expire            Expire
     * @param boolean[optional] $expireIsTimestamp If true $expire argument in seconds, or $expire is timestamp
     * @return Rediska_Key_GetOrSetValue
     */
    public function getOrSetValue($object = null, $expire = null, $expireIsTimestamp = false)
    {
        return new Rediska_Key_GetOrSetValue($this, $object, $expire, $expireIsTimestamp);
    }

    /**
     * Magic for get value
     *
     * @return string
     */
    public function __toString()
    {
        return (string)$this->getValue();
    }

    /**
     * Implements Countable
     */

    public function count()
    {
        return $this->getLength();
    }

    /**
     * Implements ArrayAccess
     */

    public function offsetExists($offset)
    {
        return (boolean)$this->getBit($offset);
    }

    public function offsetGet($offset)
    {
        return $this->getBit($offset);
    }

    public function offsetSet($offset, $value)
    {
        if ($offset === null) {
            throw new Rediska_Key_Exception("You must specify offset");
        }

        return $this->setBit($offset, $value);
    }

    public function offsetUnset($offset)
    {
        return $this->setBit($offset, 0);
    }
}

/**
 * GetOrSetValue helper class
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Key_GetOrSetValue
{
    /**
     * Key object
     *
     * @var Rediska_Key
     */
    protected $_key;

    /**
     * Object provider
     *
     * @var object
     */
    protected $_object;

    /**
     * Expire
     *
     * @var integer
     */
    protected $_expire;

    /**
     * Is expire in seconds
     *
     * @var bool
     */
    protected $_expireIsTimestamp = false;

    /**
     * Construct GetOrSetValue provider
     *
     * @param Rediska_Key $key
     * @param object      $object Provider object
     * @param integer[optional] $expire            Expire
     * @param boolean[optional] $expireIsTimestamp If true $expire argument in seconds, or $expire is timestamp
     */
    public function __construct(Rediska_Key $key, $object = null, $expire = null, $expireIsTimestamp = false)
    {
        $this->_key               = $key;
        $this->_object            = $object;
        $this->_expire            = $expire;
        $this->_expireIsTimestamp = $expireIsTimestamp;
    }

    public function __call($method, $args)
    {
        $value = $this->_key->getValue();

        if (is_null($value)) {
            if (is_null($this->_object)) {
                $callback = $method;
            } else {
                $callback = array($this->_object, $method);
            }
            $value = call_user_func_array($callback, $args);
            $this->_key->setValue($value);
            if ($this->_expire !== null) {
                $this->_key->expire($this->_expire, $this->_expireIsTimestamp);
            }
        }

        return $value;
    }

    public function __get($attribute)
    {
        $value = $this->_key->getValue();

        if (is_null($value)) {
            $value = $this->_object->{$attribute};
            $this->_key->setValue($value);
        }

        return $value;
    }

    public function __toString()
    {
        return (string)$this->_object;
    }
}


<?php

class UserController extends Zend_Controller_Action
{
    /**
     * Signup
     */
    public function signupAction()
    {
        $form = new Form_User;

        if ($this->getRequest()->isPost() && $form->isValid($this->getRequest()->getPost())) {

            $userData = $form->getValues();
            $userData['id'] = User::fetchNextId();

            // save user
            $user = new User($userData['id']);
            $user->setValue($userData);

            $users = new Users();
            $users->add($userData['id']);

            // save login to id link
            User::setLoginToIdLink($userData['login'], $userData['id']);

            $this->_redirect('/user/login');
        }

        $this->view->form = $form;
    }

    /**
     * Login
     */
    public function loginAction()
    {
        $form = new Form_UserLogin;

        if ($this->getRequest()->isPost() && $form->isValid($this->getRequest()->getPost())) {
            $auth = Zend_Auth::getInstance();

            $options = array(
                'userIdKey'   => 'userIdKey:*',
                'userDataKey' => 'users:*',
                'userDataIsArray' => true
            );
            $adapter = new Rediska_Zend_Auth_Adapter_Redis($options);

            // Set login and password
            $adapter->setIdentity($form->getElement('login')->getValue())
                    ->setCredential($form->getElement('password')->getValue());

            // Authorization
            $result = $auth->authenticate($adapter);
            if ($result->isValid()) {
                $userData = $adapter->getResultUserData();

                $session = new Zend_Session_Namespace('Zend_Auth');

                $storage = $auth->getStorage();
                $storage->write($userData);

                $this->_redirect('/post/index/');
            } else {
                $form->getElement('login')->addError('Wrong login/password combination');
            }
        }

        $this->view->form = $form;
    }

    /**
     * Users who follow given userId
     */
    public function followersAction()
    {
        $userId = $this->_getParam('userId');

        $user = new User($userId);
        $this->view->user = $user->getValue();

        $followers = new Followers($userId);

        $this->view->users = User::getMultiple($followers->toArray());
        $this->_setUsersIFollow();
    }

    /**
     * Users who are followed by given userId
     */
    public function followingAction()
    {
        $userId = $this->_getParam('userId');

        $following = new Following($userId);

        $this->view->users = User::getMultiple($following->toArray());
        $this->_setUsersIFollow();
    }

    /**
     * Start following given userId
     */
    public function followAction()
    {
        $auth = Zend_Auth::getInstance();
        if (!$auth->hasIdentity()) {
            throw new Zend_Auth_Exception("You're not authorized to see this page");
        }

        $userId = $this->_getParam('userId');
        $follower = $auth->getStorage()->read();

        if ($userId != $follower['id']) {
            $followers = new Followers($userId);
            $followers[] = $follower['id'];

            $following = new Following($follower['id']);
            $following[] = $userId;
        }
        $this->_redirect('/user/followers/userId/' . $userId);
    }

    /**
     * Stop following given userId
     */
    public function unfollowAction()
    {
        $auth = Zend_Auth::getInstance();
        if (!$auth->hasIdentity()) {
            throw new Zend_Auth_Exception("You're not authorized to see this page");
        }

        $userId = $this->_getParam('userId');
        $follower = $auth->getStorage()->read();

        if ($userId != $follower['id']) {
            $followers = new Followers($userId);
            $followers->remove($follower['id']);

            $following = new Following($follower['id']);
            $following->remove($userId);
        }
        $this->_redirect('/user/followers/userId/' . $userId);
    }

    public function logoutAction()
    {
        Zend_Session::destroy();
        $this->_redirect('/user/login');
    }

    public function indexAction()
    {
        $this->view->users = array();

        $users = new Users();

        // just for example, better use multiget
        foreach ($users as $userId) {
            $user = new User($userId);
            $this->view->users[] = $user->getValue();
        }
        $this->_setUsersIFollow();
    }

    protected function _setUsersIFollow()
    {
        $auth = Zend_Auth::getInstance();
        if (!$auth->hasIdentity()) {
            throw new Zend_Auth_Exception("You're not authorized to see this page");
        }

        $currentUser = $auth->getStorage()->read();
        $following = new Following($currentUser['id']);

        $this->view->userIdsIFollow = $following->toArray();
    }
}


<?php

/**
 * Set a time to live in seconds or timestamp on a key
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Commands
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Command_Expire extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param string  $key                   Key name
     * @param integer $secondsOrTimestamp    Time in seconds or timestamp
     * @param boolean $isTimestamp[optional] Time is timestamp. For default is false.
     * @return Rediska_Connection_Exec
     */
    public function create($key, $secondsOrTimestamp, $isTimestamp = false)
    {
        $connection = $this->_rediska->getConnectionByKeyName($key);

        if ($isTimestamp) {
            $this->_throwExceptionIfNotSupported('1.1');
            $command = 'EXPIREAT';
        } else {
            $command = 'EXPIRE';
        }

        $command = array($command,
                         $this->_rediska->getOption('namespace') . $key,
                         $secondsOrTimestamp);

        return new Rediska_Connection_Exec($connection, $command);
    }

    /**
     * Parse response
     *
     * @param integer $response
     * @return boolean
     */
    public function parseResponse($response)
    {
        return (boolean)$response;
    }
}


<?php

/**
 * Rediska key abstract class
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Key objects
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
abstract class Rediska_Key_Abstract extends Rediska_Options_RediskaInstance
{
    /**
     * Key name
     *
     * @var string
     */
    protected $_name;

    /**
     * Exception class name for options
     *
     * @var string
     */
    protected $_optionsException = 'Rediska_Key_Exception';

    /**
     * Options:
     *
     * expire            - Expire time
     * expireIsTimestamp - Expire time is timestamp. For default false (in seconds)
     * serverAlias       - Server alias or connection object
     * rediska           - Rediska instance name, Rediska object or Rediska options for new instance
     *
     * @var array
     */
    protected $_options = array(
        'serverAlias'       => null,
        'expire'            => null,
        'expireIsTimestamp' => false,
    );

    /**
     * Construct key
     *
     * @param string                    $name        Key name
     * @param integer                   $options     Options:
     *                                                  expire            - Expire time
     *                                                  expireIsTimestamp - Expire time is timestamp. For default false (in seconds)
     *                                                  serverAlias       - Server alias or connection object
     *                                                  rediska           - Rediska instance name, Rediska object or Rediska options for new instance
     * @param string|Rediska_Connection $serverAlias Server alias or Rediska_Connection object where key is placed. Deprecated!
     */
    public function __construct($name, $options = array(), $serverAlias = null)
    {
        if (!is_array($options)) {
            throw new Rediska_Key_Exception("\$expire argument is deprectated. Use 'expire' option");
        }
        if (!is_null($serverAlias)) {
            throw new Rediska_Key_Exception("\$serverAlias argument is deprectated. Use 'serverAlias' option");
        }

        $this->setName($name);

        parent::__construct($options);
    }

    /**
     * Delete key
     *
     * @return boolean
     */
    public function delete()
    {
        return $this->_getRediskaOn()->delete($this->getName());
    }

    /**
     * Exists in db
     *
     * @return boolean
     */
    public function isExists()
    {
        return $this->_getRediskaOn()->exists($this->getName());
    }

    /**
     * Get key type
     *
     * @see Rediska#getType
     * @return string
     */
    public function getType()
    {
        return $this->_getRediskaOn()->getType($this->getName());
    }

    /**
     * Rename key
     *
     * @param string  $newName
     * @param boolean $overwrite
     * @return boolean
     */
    public function rename($newName, $overwrite = true)
    {
        try {
            $this->_getRediskaOn()->rename($this->getName(), $newName, $overwrite);
        } catch (Rediska_Exception $e) {
            return false;
        }

        $this->setName($newName);

        if (!is_null($this->getExpire())) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return true;
    }

    /**
     * Expire key
     *
     * @param integer $secondsOrTimestamp Time in seconds or timestamp
     * @param boolean $isTimestamp        Time is timestamp? Default is false.
     * @return boolean
     */
    public function expire($secondsOrTimestamp, $isTimestamp = false)
    {
        return $this->_getRediskaOn()->expire($this->getName(), $secondsOrTimestamp, $isTimestamp);
    }

    /**
     * Get key lifetime
     *
     * @return integer
     */
    public function getLifetime()
    {
        return $this->_getRediskaOn()->getLifetime($this->getName());
    }

    /**
     * Move key to other Db
     *
     * @see Rediska#moveToDb
     * @param integer $dbIndex
     * @return boolean
     */
    public function moveToDb($dbIndex)
    {
        $result = $this->_getRediskaOn()->moveToDb($this->getName(), $dbIndex);

        if (!is_null($this->getExpire()) && $result) {
            $this->expire($this->getExpire(), $this->isExpireTimestamp());
        }

        return $result;
    }

    /**
     * Get key name
     *
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Set key name
     *
     * @param string $name
     * @return Rediska_Key_Abstract
     */
    public function setName($name)
    {
        $this->_name = $name;

        return $this;
    }

    /**
     * Set expire time
     *
     * @param $secondsOrTimestamp Time in seconds or timestamp
     * @param $isTimestamp        Time is timestamp? Default is false.
     * @return Rediska_Key_Abstract
     */
    public function setExpire($secondsOrTimestamp, $isTimestamp = false)
    {
        if ($secondsOrTimestamp !== null) {
            trigger_error('Expire option is deprecated, because expire behaviour was changed in Redis 2.2. Use expire method instead.', E_USER_WARNING);
        }

        $this->_options['expire'] = $secondsOrTimestamp;
        $this->_options['expireIsTimestamp'] = $isTimestamp;

        return $this;
    }

    /**
     * Get expire seconds or timestamp
     *
     * @return integer
     */
    public function getExpire()
    {
        return $this->_options['expire'];
    }

    /**
     * Is expire is timestamp
     *
     * @return boolean
     */
    public function isExpireTimestamp()
    {
        return $this->_options['expireIsTimestamp'];
    }

    /**
     * Set server alias
     *
     * @param $serverAlias
     * @return Rediska_Key_Abstract
     */
    public function setServerAlias($serverAlias)
    {
        $this->_options['serverAlias'] = $serverAlias;

        return $this;
    }

    /**
     * Get server alias
     *
     * @return null|string
     */
    public function getServerAlias()
    {
        return $this->_options['serverAlias'];
    }

    /**
     *  Get rediska and set specified connection
     *
     *  @return Rediska
     */
    protected function _getRediskaOn()
    {
        $rediska = $this->getRediska();

        if (!is_null($this->getServerAlias())) {
            $rediska = $rediska->on($this->getServerAlias());
        }

        return $rediska;
    }
}


<?php

/**
 * Rediska Serializer
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Serializer
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Serializer
{
    /**
     * Serializer
     *
     * @var Rediska_Serializer_Adapter_Interface
     */
    protected $_adapter;

    /**
     * Constuctor
     *
     * @param mixed $adapter Adapter
     */
    public function __construct($adapter)
    {
        $this->setAdapter($adapter);
    }

    /**
     * Set adapter
     *
     * @param $adapter
     * @return Rediska_Serializer
     */
    public function setAdapter($adapter)
    {
        if (is_object($adapter)) {
            $this->_adapter = $adapter;
        } else if (in_array($adapter, array('phpSerialize', 'json', 'toString'))) {
            $adapter = ucfirst($adapter);
            $className = "Rediska_Serializer_Adapter_$adapter";
            $this->_adapter = new $className;
        } else {
            if (!@class_exists($adapter)) {
                throw new Rediska_Serializer_Exception("Serializer adapter '$adapter' not found. You need include it before or setup autoload.");
            }
            $this->_adapter = new $adapter;
        }

        if (!$this->_adapter instanceof Rediska_Serializer_Adapter_Interface) {
            throw new Rediska_Serializer_Exception("'$adapter' must implement Rediska_Serializer_Adapter_Interface");
        }

        return $this;
    }

    /**
     * Get adapter
     *
     * @return Rediska_Serializer_Adapter_Interface
     */
    public function getAdapter()
    {
        return $this->_adapter;
    }

    /**
     * Serialize value
     *
     * @param mixed $value Value for serialize
     * @return string
     */
    public function serialize($value)
    {
        if (is_numeric($value) || is_string($value)) {
            return (string)$value;
        } else {
            return $this->_adapter->serialize($value);
        }
    }

    /**
     * Unserailize value
     *
     * @param string $value Serialized value
     * @return mixed
     */
    public function unserialize($value)
    {
        if (is_null($value)) {
            return null;
        } else if (is_numeric($value)) {
            if (strpos($value, '.') === false) {
                $unserializedValue = (integer)$value;
            } else {
                $unserializedValue = (float)$value;
            }

            if ((string)$unserializedValue !== $value) {
                $unserializedValue = $value;
            }
        } else {
            try {
                $unserializedValue = $this->_adapter->unserialize($value);
            } catch (Rediska_Serializer_Adapter_Exception $e) {
                $unserializedValue = $value;
            }
        }

        return $unserializedValue;
    }
}


<?php

/**
 * Rediska stream profiler
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Profiler
 * @version @package_version@
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Profiler_Stream extends Rediska_Profiler
{
    /**
     * PHP stream
     *
     * @var stream|null
     */
    protected $_stream;

    /**
     * Access mode to stream
     *
     * @var stream
     */
    protected $_mode = 'a';

    /**
     * Output format
     *
     * @var string
     */
    protected $_format = '[%timestamp%] %profile% => %elapsedTime%';

    /**
     * Constructor
     *
     * @param array $options
     */
    public function __construct($options = array())
    {
        if (isset($options['mode'])) {
            $this->setMode($options['mode']);
        }

        if (isset($options['stream'])) {
            $this->setStream($options['stream']);
        } else {
            throw new Rediska_Profiler_Exception("You must specify option 'stream'");
        }

        if (isset($options['format'])) {
            $this->setFormat($options['format']);
        }
    }

    /**
     * Set stream
     *
     * @param stream|string $stream Stream or Url
     * @return Rediska_Profiler_Stream
     */
    public function setStream($stream)
    {
        $this->_stream = $stream;

        return $this;
    }

    /**
     * Get stream
     *
     * @return stream
     */
    public function getStream()
    {
        if (!is_resource($this->_stream)) {
            $stream = $this->_stream;
            if (is_array($stream) && isset($stream['stream'])) {
                $stream = $stream['stream'];
            }

            if (! $this->_stream = @fopen($stream, $this->getMode(), false)) {
                throw new Rediska_Profiler_Exception("'$stream' cannot be opened with mode '{$this->getMode()}'");
            }
        }

        return $this->_stream;
    }

    /**
     * Set access mode
     *
     * @param string $mode
     * @return Rediska_Profiler_Stream
     */
    public function setMode($mode)
    {
        if (is_resource($this->_stream) && $this->_mode != $mode) {
            $meta = stream_get_meta_data($this->_stream);
            $this->setStream($meta['uri']);
        }

        $this->_mode = $mode;

        return $this;
    }

    /**
     * Get access mode
     *
     * @return string
     */
    public function getMode()
    {
        return $this->_mode;
    }

    /**
     * Set output format
     *
     * @param string $format
     * @return Rediska_Profiler_Stream
     */
    public function setFormat($format)
    {
        $this->_format = $format;

        return $this;
    }

    /**
     * Get output format
     *
     * @return string
     */
    public function getFormat()
    {
        return $this->_format;
    }

    /**
     * Stop callback. Called from profile
     *
     * @param Rediska_Profiler_Profile $profile
     */
    public function stopCallback(Rediska_Profiler_Profile $profile)
    {
        $placeHolders = array(
            '%timestamp%'   => date('Y-m-d H:i:s'),
            '%profile%'     => $profile->getContext(),
            '%elapsedTime%' => $profile->getElapsedTime(4)
        );

        $data = str_replace(
            array_keys($placeHolders),
            array_values($placeHolders),
            $this->_format . Rediska::EOL
        );

        $this->_write($data);
    }

    /**
     * Reset profiler
     *
     * @return Rediska_Profiler_Stream
     */
    public function reset()
    {
        parent::reset();

        $prevMode = $this->getMode();

        $this->setMode('w');

        $this->_write('');

        $this->setMode($prevMode);

        return $this;
    }

    /**
     * Write profile to string
     *
     * @param string $data
     */
    protected function _write($data)
    {
        if (false === @fwrite($this->getStream(), $data)) {
            throw new Rediska_Profiler_Exception("Unable to write to stream");
        }
    }

    /**
     * Close stream on destruct
     */
    public function __destruct()
    {
        if (is_resource($this->_stream)) {
            fclose($this->_stream);
        }
    }
}