<?php

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

/**
 * Rediska instances manager
 *
 * @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_Manager
{
    /**
     * Rediska instances
     *
     * @var array
     */
    protected static $_instances = array();

    /**
     * Add Rediska
     * Return true if new instance and false if instance overwrited
     *
     * @param Rediska $rediska Rediska instance or options
     * @return boolean
     */
    public static function add($rediska)
    {
        if ($rediska instanceof Rediska) {
            if (!self::has($rediska->getName())) {
                foreach(self::$_instances as $name => $instance) {
                    if ($instance === $rediska && $name != $rediska->getName()) {
                        unset(self::$_instances[$name]);
                        break;
                    }
                }
            }
            $name = $rediska->getName();
        } else if (is_array($rediska)) {
            if (isset($rediska['name'])) {
                $name = $rediska['name'];
            } else {
                $name = Rediska::DEFAULT_NAME;
            }
        } else {
            throw new Rediska_Exception('Rediska must be a instance or options');
        }

        if (self::has($name)) {
            $result = false;
        } else {
            $result = true;
        }

        self::$_instances[$name] = $rediska;

        return $result;
    }

    /**
     * Has Rediska instances
     *
     * @param string $name
     */
    public static function has($name)
    {
        return isset(self::$_instances[$name]);
    }

    /**
     * Get Rediska
     *
     * @param string[optional] $name Instance name
     * @return Rediska
     */
    public static function get($name = Rediska::DEFAULT_NAME)
    {
        if (!self::has($name)) {
            throw new Rediska_Exception("Rediska instance '$name' not present");
        }

        self::_instanceFromOptions($name);

        return self::$_instances[$name];
    }

    /**
     * Get all Rediska instances
     *
     * @return array
     */
    public static function getAll()
    {
        foreach(self::$_instances as $name => $instanceOrOptions) {
            self::_instanceFromOptions($name);
        }

        return self::$_instances;
    }

    /**
     * Remove Rediska
     *
     * @param Rediska $rediska Rediska instance or options
     * @return boolean
     */
    public static function remove($rediska)
    {
        if ($rediska instanceof Rediska) {
            $name = $rediska->getName();
        } else if (is_string($rediska)) {
            $name = $rediska;
        } else {
            throw new Rediska_Exception('Rediska must be a instance or name');
        }

        if (!isset(self::$_instances[$name])) {
            throw new Rediska_Exception("Rediska instance '$name' not present");
        }

        unset(self::$_instances[$name]);

        return true;
    }

    /**
     * Remove all rediska
     * Return count of removed instance
     *
     * @return integer
     */
    public static function removeAll()
    {
        $count = count(self::$_instances);

        self::$_instances = array();

        return $count;
    }

    /**
     * Instance from options if not yet
     *
     * @static
     * @param  $name
     * @return void
     */
    protected static function _instanceFromOptions($name)
    {
        if (!is_object(self::$_instances[$name])) {
            $options = self::$_instances[$name];
            self::$_instances[$name] = new Rediska($options);
        }
    }
}


<?php

/**
 * Provide information and statistics about the server
 *
 * @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_Info extends Rediska_Command_Abstract
{
    protected $_connections = array();

    /**
     * Create
     *
     * @return array
     */
    public function create()
    {
        $command = array('INFO');
        $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 array
     */
    public function parseResponses($responses)
    {
        $info = array();
        $count = 0;
        foreach($this->_connections as $connection) {
            $info[$connection] = array();

            foreach (explode(Rediska::EOL, $responses[$count]) as $param) {
                if (!$param || !strpos($param, ':') !== false) {
                    continue;
                }
                list($name, $stringValue) = explode(':', $param, 2);

                if (strpos($stringValue, '.') !== false) {
                    $value = (float)$stringValue;
                } else {
                    $value = (integer)$stringValue;
                }

                if ((string)$value != $stringValue) {
                    $value = $stringValue;
                }

                $info[$connection][$name] = $value;
            }
            $info[$connection] = new Rediska_Info($info[$connection]);
            $count++;
        }
        if (count($info) == 1) {
            $info = array_shift($info);
        }

        return $info;
    }
}


<?php

class Ratelimit {

	/**
	 * Rediska instance
	 * @var Rediska
	 */
	protected $_rediska;

	/**
	 * Action e.g. 'page:id:view', 'hit',  etc ..
	 * @var string
	 */
	protected $_action;

	/**
	 *  is the total size of "hashring" in seconds
	 * @var int
	 */
	protected $_bucketSpan;

	/**
	 * amount of seconds each bucket represents
	 * @var int
	 */
	protected $_bucketInterval;

	/**
	 * (derived) Bucket count = Bucket span / Bucket interval
	 * @var int
	 */
	protected $_bucketCount;

	/**
	 * amount of (inactive) seconds before a subject's time buckets expire
	 * @var int
	 */
	protected $_subjectExpire;

	/**
	 * Constructor
	 *
	 * @param Rediska $client
	 * @param string $action
	 * @param int $bucketSpan
	 * @param int $bucketInterval
	 * @param int $subjectExpire
	 */
	public function __construct(Rediska $rediska, $action, $bucketSpan = 600, $bucketInterval = 5, $subjectExpire = 1200) {
		$this->_rediska = $rediska;
		$this->_action = (string) $action;
		$this->_bucketSpan = (int) $bucketSpan;
		$this->_bucketInterval = (int) $bucketInterval;
		$this->_bucketCount = (int) round($this->_bucketSpan / $this->_bucketInterval);
		$this->_subjectExpire = (int) $subjectExpire;
	}

	/**
	 * Increment the count for the specified subject.
	 *
	 * @param string $subject A unique identifier, for example a session id or an IP
	 * @return void
	 */
	public function increment($subject) {
		$bucket = $this->_getBucketName();
		$transaction = $this->_getTransaction();
		$this->_setMultiIncrementTransactionPart($transaction, $subject, $bucket);
		$transaction->execute();
	}

	/**
	 * Count the number of times the subject has performed an action in the last `$interval` seconds.
	 *
	 * @param string $subject A unique identifier, for example a session id or an IP
	 * @param int $interval Interval in seconds
	 * @return int
	 */
	public function getRateByInterval($subject, $interval) {
		$bucket = $this->_getBucketName();
		$count = (int) floor($interval / $this->bucketInterval);
		$transaction = $this->_getTransaction();
		$this->_setMulitExecGetCountPart($transaction, $subject, $bucket, $count);
		return array_sum($transaction->execute());
	}

	/**
	 * Calls the increment() and count() function using a single MULTI/EXEC block.
	 *
	 * @param string $subject A unique identifier, for example a session id or an IP
	 * @param int $interval Interval in seconds
	 * @return int
	 */
	public function incrementAndGetCountByInterval($subject, $interval) {
		$bucket = $this->_getBucketName();
		$count = (int) floor($interval / $this->_bucketInterval);
		$transaction = $this->_getTransaction();
		$this->_setMultiIncrementTransactionPart($transaction, $subject, $bucket);
		$this->_setMulitExecGetCountPart($transaction, $subject, $bucket, $count);

		return array_sum(array_slice($transaction->execute(), 4));
	}

	/**
	 * Resets the counter for the specified subject.
	 *
	 * @param string $subject A unique identifier, for example a session id or an IP
	 * @return bool
	 */
	public function reset($subject) {
		$keyName = $this->_getKeyName($subject);
		return (bool) $this->_rediska->delete($keyName);
	}

	/**
	 * Get the bucket associated with the current time.
	 *
	 * @param int $time (optional) - default is the current time (seconds since epoch)
	 * @return int bucket
	 */
	protected function _getBucketName($time = null) {
		$time = $time ? : time();
		return (int) floor(($time % $this->_bucketSpan) / $this->_bucketInterval);
	}

	/**
	 * Adds the commands needed for the increment function
	 *
	 * @param Rediska_Transaction $transaction
	 * @param string $subject A unique identifier, for example a session id or an IP
	 * @param int $bucket
	 * @return void
	 */
	protected function _setMultiIncrementTransactionPart(Rediska_Transaction $transaction, $subject, $bucket) {
		$keyName = $this->_getKeyName($subject);

		$transaction->incrementinhash($keyName, $bucket, 1)
				->deletefromhash($keyName, ($bucket + 1) % $this->_bucketCount)
				->deletefromhash($keyName, ($bucket + 2) % $this->_bucketCount)
				->expire($keyName, $this->_subjectExpire);
	}

	/**
	 * return Rediska_Transaction instance
	 * @return Rediska_Transaction
	 */
	protected function _getTransaction() {
		return $this->_rediska->transaction();
	}

	/**
	 * Adds the commands needed for the count function
	 *
	 * @param Rediska_Transaction $transaction
	 * @param string $subject A unique identifier, for example a session id or an IP
	 * @param int $bucket
	 * @param int $count
	 * @return void
	 */
	protected function _setMulitExecGetCountPart(Rediska_Transaction $transaction, $subject, $bucket, $count) {
		$keyName = $this->_getKeyName($subject);
		// Get the counts from the previous `$count` buckets
		$transaction->getfromhash($keyName, $bucket);
		for ($i = $count; $i > 0; $i--) {
			$transaction->getfromhash($keyName, (--$bucket + $this->_bucketCount) % $this->_bucketCount);
		}
		return $transaction;
	}

	/**
	 * get key name
	 * @param string $subject A unique identifier, for example a session id or an IP
	 * @return string
	 */
	protected function _getKeyName($subject) {
		return $this->_action . ':' . $subject;
	}

}


<?php

/**
 * Decrement the number value of key by integer
 *
 * @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_Decrement extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param string            $key    Key name
     * @param integer[optional] $amount Amount to decrement. One for default
     * @return Rediska_Connection_Exec
     */
    public function create($key, $amount = 1)
    {
        $connection = $this->_rediska->getConnectionByKeyName($key);

        if ($amount == 1) {
            $command = array('DECR',
                             $this->_rediska->getOption('namespace') . $key);
        } else {
            $command = array('DECRBY',
                             $this->_rediska->getOption('namespace') . $key,
                             $amount);
        }

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


<?php

/**
 * Insert a new value as the element before the reference value
 *
 * @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_InsertToListBefore extends Rediska_Command_InsertToListAbstract
{
    /**
     * Create command
     *
     * @param string  $key            Key name
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return Rediska_Connection_Exec
     */
    public function create($key, $referenceValue, $value)
    {
        return $this->_create($key, self::BEFORE, $referenceValue, $value);
    }
}


<?php

/**
 * Set value to a hash field or fields
 *
 * @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_SetToHash extends Rediska_Command_Abstract
{
    /**
     * Supported version
     *
     * @var string
     */
    protected $_version = '1.3.10';

    /**
     * Create command
     *
     * @param string        $key          Key name
     * @param array|string  $fieldOrData  Field or array of many fields and values: field => value
     * @param mixed         $value        Value for single field
     * @param boolean       $overwrite    Overwrite for single field (if false don't set and return false if key already exist). For default true.
     * @return Rediska_Connection_Exec
     */
    public function create($key, $fieldOrData, $value = null, $overwrite = true)
    {
        if (is_array($fieldOrData)) {
            $data = $fieldOrData;

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

            $command = array('HMSET',
                             $this->_rediska->getOption('namespace') . $key);
            foreach($data as $field => $value) {
                $command[] = $field;
                $command[] = $this->_rediska->getSerializer()->serialize($value);
            }
        } else {
            $field = $fieldOrData;

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

            $command = array($overwrite ? 'HSET' : 'HSETNX',
                             $this->_rediska->getOption('namespace') . $key,
                             $field,
                             $value);
        }

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

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

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


<?php

/**
 * Get hash fields and values
 *
 * @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_GetHash extends Rediska_Command_Abstract
{
    /**
     * Supported version
     *
     * @var string
     */
    protected $_version = '1.3.10';

    /**
     * Create command
     *
     * @param string $key Key name
     * @return Rediska_Connection_Exec
     */
    public function create($key)
    {
        $connection = $this->_rediska->getConnectionByKeyName($key);

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

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

    /**
     * Parse response
     *
     * @param array $response
     * @return array
     */
    public function parseResponse($response)
    {
        $isField = true;
        $result = array();
        foreach($response as $fieldOrValue) {
            if ($isField) {
                $field = $fieldOrValue;
            } else {
                $result[$field] = $this->_rediska->getSerializer()->unserialize($fieldOrValue);
            }

            $isField = !$isField;
        }

        return $result;
    }
}


<?php

/**
 * Rediska socket connection
 *
 * @author Ivan Shumkov
 * @package Rediska
 * @subpackage Connection
 * @version 0.5.6
 * @link http://rediska.geometria-lab.net
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class Rediska_Connection_Socket extends Rediska_Connection
{
    /**
     * @var Rediska_Connection_Socket_ReadBuffer
     */
    protected $_readBuffer;

    public function __construct(array $options = array())
    {
        if (!function_exists('socket_create')) {
            throw new Rediska_Connection_Exception("Can't use socket connection: socket functions not present.");
        }

        parent::__construct($options);
    }

    /**
     * Connect to redis server
     *
     * @throws Rediska_Connection_Exception
     * @return boolean
     */
    public function connect()
    {
        if ($this->isConnected()) {
            return false;
        }

        $this->_socket = $this->_createSocketConnection();

        // Throw exception if can't connect
        if (!is_resource($this->_socket)) {
            $errorCode = socket_last_error();
            $errorMessage = socket_strerror($errorCode);

            $msg = "Can't connect to Redis server on {$this->getHost()}:{$this->getPort()}";
            if ($errorCode || $errorMessage) {
                $msg .= "," . ($errorCode ? " error $errorCode" : "") . ($errorMessage ? " $errorMessage" : "");
            }

            $this->_socket = null;

            throw new Rediska_Connection_Exception($msg);
        }

        // Set read timeout
        if ($this->_options['readTimeout'] != null) {
            $this->setReadTimeout($this->_options['readTimeout']);
        }

        // Set blocking mode
        //if (1 || $this->_options['blockingMode'] == false) {
        //    $this->setBlockingMode($this->_options['blockingMode']);
        //}

        // Send password
        if ($this->getPassword() != '') {
            $auth = new Rediska_Connection_Exec($this, "AUTH {$this->getPassword()}");
            try {
               $auth->execute();
            } catch (Rediska_Command_Exception $e) {
                throw new Rediska_Connection_Exception("Password error: {$e->getMessage()}");
            }
        }

        // Set db
        if ($this->_options['db'] !== self::DEFAULT_DB) {
            $selectDb = new Rediska_Connection_Exec($this, "SELECT {$this->_options['db']}");
            try {
               $selectDb->execute();
            } catch (Rediska_Command_Exception $e) {
                throw new Rediska_Connection_Exception("Select db error: {$e->getMessage()}");
            }
        }

        return true;
    }

    /**
     * Disconnect
     *
     * @return boolean
     */
    public function disconnect()
    {
        if (!$this->isConnected()) {
            return false;
        }

        @socket_close($this->_socket);
        $this->_socket = null;
        $this->_readBuffer = null;

        return true;
    }

    /**
     * Write to connection stream
     *
     * @param $string
     * @return bool
     * @throws Rediska_Connection_Exception
     */
    public function write($string)
    {
        if ($string == '') {
            return false;
        }

        $this->connect();

        $string = (string)$string . Rediska::EOL;

        $offset = 0;
        $length = strlen($string);

        while (true) {
            $sent = @socket_write($this->getSocket(), substr($string, $offset, $length), $length);

            if ($sent === false) {
                $errorCode = socket_last_error();
                $errorMessage = socket_strerror($errorCode);

                // in nonblock mode  EAGAIN is OK
                if ($errorCode == 35) continue;

                $this->disconnect();

                throw new Rediska_Connection_Exception("Can't write to socket: " . $errorMessage);
            }

            $offset += $sent;
            $length -= $sent;

            if ($length <= 0) {
                return true;
            }
        }

        return true;
    }

    /**
     * Read line from connection stream
     *
     * @throws Rediska_Connection_Exception
     * @throws Rediska_Connection_TimeoutException
     * @return string
     */
    public function readLine()
    {
        if (!$this->isConnected()) {
            throw new Rediska_Connection_Exception("Can't read without connection to Redis server. Do connect or write first.");
        }

        return $this->_getReadBuffer()->readLine();
    }

    /**
     * Set read timeout
     *
     * @param $timeout
     * @return Rediska_Connection
     */
    public function setReadTimeout($timeout)
    {
        $this->_options['readTimeout'] = $timeout;

        return $this;
    }

    /**
     * Set blocking/non-blocking mode for reads
     *
     * @param $flag
     * @return Rediska_Connection
     */
    public function setBlockingMode($flag = true)
    {
        if ($flag !== true) {
            throw new Rediska_Connection_Exception('Rediska_Connection_Socket not supported blocking mode');
        }

        return $this;
    }

    /**
     * Get socket
     *
     * @return resource
     */
    public function getSocket()
    {
        return $this->_socket;
    }

    /**
     * Read and throw exception if somthing wrong
     *
     * @param $length Lenght of bytes to read
     * @return string
     */
    protected function _readAndThrowException($length)
    {
        return $this->_getReadBuffer()->read($length);
    }

    /**
     * Create socket connection
     *
     * @return null|resource
     */
    protected function _createSocketConnection()
    {
        $socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));

        socket_set_option($socket, SOL_SOCKET, TCP_NODELAY, 1);

        @socket_set_nonblock($socket);
        $result = @socket_connect($socket, $this->getHost(), $this->getPort());

        if ($result === false) {
            $errorCode = socket_last_error($socket);
            if ($errorCode !== SOCKET_EINPROGRESS) {
                return null;
            }
        }else{
            //@socket_set_block($socket);

            return $socket;
        }

        /* Do whatever we want while the connect is taking place. */
        $result = @socket_select($r = array($socket), $w = array($socket), $e = array($socket), $this->getTimeout());

        if ($result === 0 || $result === false) {
            return null;
        }

        if (!(($r && count($r)) || ($w && count($w))) || @socket_get_option($socket, SOL_SOCKET, SO_ERROR) != 0) {
            return null;
        }

        //@socket_set_block($socket);

        return $socket;
    }

    /**
     * Get read buffer
     *
     * @return Rediska_Connection_Socket_ReadBuffer
     */
    protected function _getReadBuffer()
    {
        if ($this->_readBuffer === null) {
            $this->_readBuffer = new Rediska_Connection_Socket_ReadBuffer($this);
        }

        return $this->_readBuffer;
    }
}


<?php

/**
 * Change the replication settings of a slave on the fly
 *
 * @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_SlaveOf extends Rediska_Command_Abstract
{
    /**
     * Create command
     *
     * @param string|Rediska_Connection|false $aliasOrConnection Server alias, Rediska_Connection object or false if not slave
     * @return Rediska_Connection_Exec
     */
    public function create($aliasOrConnection)
    {
        if ($aliasOrConnection === false) {
            $host = 'no';
            $port = 'one';
        } else {
            if ($aliasOrConnection instanceof Rediska_Connection) {
                $connection = $aliasOrConnection;
            } else {
                $alias = $aliasOrConnection;
                $connection = $this->_rediska->getConnectionByAlias($alias);
            }

            $host = $connection->getHost();
            $port = $connection->getPort();
        }

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

        return $commands;
    }

    /**
     * Parse responses
     *
     * @param array $responses
     * @return boolean
     */
    public function parseResponses($responses)
    {
        return true;
    }
}


<?php

/**
 * Rediska connection
 *
 * @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 extends Rediska_Options
{
    const DEFAULT_HOST = '127.0.0.1';
    const DEFAULT_PORT = 6379;
    const DEFAULT_WEIGHT = 1;
    const DEFAULT_DB = 0;

    /**
     * Socket
     *
     * @var resource
     */
    protected $_socket;

    /**
     * Options
     *
     * host         - Redis server host. For default 127.0.0.1
     * port         - Redis server port. For default 6379
     * db           - Redis server DB index. For default 0
     * alias        - Redis server alias for operate keys on specified server. For default [host]:[port]
     * weight       - Weight of Redis server for key distribution. For default 1
     * password     - Redis server password. Optional
     * persistent   - Persistent connection to Redis server. For default false
     * timeout      - Connection timeout for Redis server. Optional
     * readTimeout  - Read timeout for Redis server
     * blockingMode - Blocking/non-blocking mode for reads
     *
     * @var array
     */
    protected $_options = array(
        'host' => self::DEFAULT_HOST,
        'port' => self::DEFAULT_PORT,
        'db' => self::DEFAULT_DB,
        'alias' => null,
        'weight' => self::DEFAULT_WEIGHT,
        'password' => null,
        'persistent' => false,
        'timeout' => null,
        'readTimeout' => null,
        'blockingMode' => true,
        'streamContext' => null
    );

    /**
     * Connect to redis server
     *
     * @throws Rediska_Connection_Exception
     * @return boolean
     * @uses   self::getStreamContext()
     */
    public function connect()
    {
        if (!$this->isConnected()) {
            $socketAddress = 'tcp://' . $this->getHost() . ':' . $this->getPort();

            if ($this->_options['persistent']) {
                $flag = STREAM_CLIENT_PERSISTENT | STREAM_CLIENT_CONNECT;
            } else {
                $flag = STREAM_CLIENT_CONNECT;
            }

            $socketParams = array(
                $socketAddress,
                &$errno,
                &$errmsg,
                $this->getTimeout(),
                $flag
            );

            $streamContext = $this->getStreamContext();

            if ($streamContext) {
                $socketParams[] = $streamContext;
            }

            $this->_socket = call_user_func_array('stream_socket_client', $socketParams);

            // Throw exception if can't connect
            if (!is_resource($this->_socket)) {
                $msg = "Can't connect to Redis server on {$this->getHost()}:{$this->getPort()}";
                if ($errno || $errmsg) {
                    $msg .= "," . ($errno ? " error $errno" : "") . ($errmsg ? " $errmsg" : "");
                }

                $this->_socket = null;

                throw new Rediska_Connection_Exception($msg);
            }

            // Set read timeout
            if ($this->_options['readTimeout'] != null) {
                $this->setReadTimeout($this->_options['readTimeout']);
            }

            // Set blocking mode
            if ($this->_options['blockingMode'] == false) {
                $this->setBlockingMode($this->_options['blockingMode']);
            }

            // Send password
            if ($this->getPassword() != '') {
                $auth = new Rediska_Connection_Exec($this, "AUTH {$this->getPassword()}");
                try {
                    $auth->execute();
                } catch (Rediska_Command_Exception $e) {
                    throw new Rediska_Connection_Exception("Password error: {$e->getMessage()}");
                }
            }

            // Set db
            if ($this->_options['db'] !== self::DEFAULT_DB) {
                $selectDb = new Rediska_Connection_Exec($this, "SELECT {$this->_options['db']}");
                try {
                    $selectDb->execute();
                } catch (Rediska_Command_Exception $e) {
                    throw new Rediska_Connection_Exception("Select db error: {$e->getMessage()}");
                }
            }

            return true;
        } else {
            return false;
        }
    }

    /**
     * Disconnect
     *
     * @return boolean
     */
    public function disconnect()
    {
        if ($this->isConnected()) {
            @fclose($this->_socket);

            return true;
        } else {
            return false;
        }
    }

    /**
     * Is connected
     *
     * @return boolean
     */
    public function isConnected()
    {
        return is_resource($this->_socket);
    }

    /**
     * Write to connection stream
     *
     * @param $string
     * @return boolean
     */
    public function write($string)
    {
        if ($string !== '') {
            $needToWrite = (string) $string . Rediska::EOL;

            $this->connect();

            while ($needToWrite) {
                $bytes = @fwrite($this->_socket, $needToWrite);

                if ($bytes === false) {
                    $this->disconnect();
                    throw new Rediska_Connection_Exception("Can't write to socket.");
                }

                if ($bytes == 0) {
                    return true;
                }

                $needToWrite = substr($needToWrite, $bytes);
            }

            return true;
        } else {
            return false;
        }
    }

    /**
     * Read length bytes from connection stram
     *
     * @throws Rediska_Connection_Exception
     * @param integer $length
     * @return boolean
     */
    public function read($length)
    {
        if (!$this->isConnected()) {
            throw new Rediska_Connection_Exception("Can't read without connection to Redis server. Do connect or write first.");
        }

        if ($length > 0) {
            $data = $this->_readAndThrowException($length);
        } else {
            $data = null;
        }

        if ($length !== -1) {
            $this->_readAndThrowException(2);
        }

        return $data;
    }

    /**
     * Read line from connection stream
     *
     * @throws Rediska_Connection_Exception
     * @throws Rediska_Connection_TimeoutException
     * @return string
     */
    public function readLine()
    {
        if (!$this->isConnected()) {
            throw new Rediska_Connection_Exception("Can't read without connection to Redis server. Do connect or write first.");
        }

        $reply = @fgets($this->_socket);

        if ($reply === false || $reply === '') {
            $metaData = stream_get_meta_data($this->_socket);
            if ($metaData['timed_out']) {
                throw new Rediska_Connection_TimeoutException("Connection read timed out.");
            }

            if ($this->_options['blockingMode'] && !$metaData['eof']) {
                $this->disconnect();
                throw new Rediska_Connection_Exception("Can't read from socket.");
            }

            $reply = null;
        } else {
            $reply = trim($reply);
        }

        return $reply;
    }

    /**
     * Set read timeout
     *
     * @param $timeout
     * @return Rediska_Connection
     */
    public function setReadTimeout($timeout)
    {
        $this->_options['readTimeout'] = $timeout;

        if ($this->isConnected()) {
            $seconds = floor($this->_options['readTimeout']);
            $microseconds = ($this->_options['readTimeout'] - $seconds) * 1000000;

            stream_set_timeout($this->_socket, $seconds, $microseconds);
        }

        return $this;
    }

    /**
     * Set blocking/non-blocking mode for reads
     *
     * @param $flag
     * @return Rediska_Connection
     */
    public function setBlockingMode($flag = true)
    {
        $this->_options['blockingMode'] = $flag;

        if ($this->isConnected()) {
            stream_set_blocking($this->_socket, $this->_options['blockingMode']);
        }

        return $this;
    }

    /**
     * Get option host
     *
     * @return string
     */
    public function getHost()
    {
        return $this->_options['host'];
    }

    /**
     * Get option port
     *
     * @return string
     */
    public function getPort()
    {
        return $this->_options['port'];
    }

    /**
     * Get option weight
     *
     * @return string
     */
    public function getWeight()
    {
        return $this->_options['weight'];
    }

    /**
     * Get option password
     *
     * @return string
     */
    public function getPassword()
    {
        return $this->_options['password'];
    }

    /**
     * Get option timout
     *
     * @return string
     */
    public function getTimeout()
    {
        if (null !== $this->_options['timeout']) {
            return $this->_options['timeout'];
        } else {
            return ini_get('default_socket_timeout');
        }
    }

    /**
     * Connection alias
     *
     * @return string
     */
    public function getAlias()
    {
        if ($this->_options['alias'] != '') {
            return $this->_options['alias'];
        } else {
            return $this->_options['host'] . ':' . $this->_options['port'];
        }
    }

    /**
     * If a stream context is provided, use it creating the socket.
     *
     * It's supported to provide either an array with options, or an already created
     * resource.
     *
     * @return mixed null or resource
     * @see    self::connect()
     */
    public function getStreamContext()
    {
        if ($this->_options['streamContext'] !== null) {
            if (is_resource($this->_options['streamContext'])) {
                return $this->_options['streamContext'];
            }
            if (is_array($this->_options['streamContext'])) {
                return stream_context_create($this->_options['streamContext']);
            }
        }
        return null;
    }

    /**
     * Read and throw exception if somthing wrong
     *
     * @param $length Lenght of bytes to read
     * @return string
     */
    protected function _readAndThrowException($length)
    {
        $data = @stream_get_contents($this->_socket, $length);

        $info = stream_get_meta_data($this->_socket);
        if ($info['timed_out']) {
            throw new Rediska_Connection_TimeoutException("Connection read timed out.");
        }

        if ($data === false) {
            $this->disconnect();
            throw new Rediska_Connection_Exception("Can't read from socket.");
        }

        return $data;
    }

    /**
     * Magic method for execute command in connection
     *
     * @return string
     */
    public function __invoke($command)
    {
        $exec = new Rediska_Connection_Exec($this, $command);

        return $exec->execute();
    }

    /**
     * Return alias to strings
     */
    public function __toString()
    {
        return $this->getAlias();
    }

    /**
     * Disconnect on destrcuct connection object
     */
    public function __destruct()
    {
        if (!$this->_options['persistent']) {
            $this->disconnect();
        }
    }

    /**
     * Do not clone socket
     */
    public function __clone()
    {
        $this->_socket = null;
    }
}


<?php

/**
 * Return a subset of the string from offset start to offset end (both offsets are inclusive)
 *
 * @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_GetRange extends Rediska_Command_Abstract
{
    protected $_version = '1.3.4';

    /**
     * Create command
     *
     * @param string            $key   Key name
     * @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 Rediska_Connection_Exec
     */
    public function create($key, $start, $end = -1)
    {
        if ($this->getName() == 'substring') {
            trigger_error('Substring is deprecated. Use getRange command instead', E_USER_WARNING);
        }

        $redisVersion = $this->getRediska()->getOption('redisVersion');
        $isVersionLessThen2 = version_compare('2.0.4', $redisVersion) >= 0;

        $command = array($isVersionLessThen2 ? 'SUBSTR' : 'GETRANGE',
                         $this->_rediska->getOption('namespace') . $key,
                         $start,
                         $end);

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

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

    /**
     * Parse response
     *
     * @param string $response
     * @return mixin
     */
    public function parseResponse($response)
    {
        return $this->getRediska()->getSerializer()->unserialize($response);
    }
}


<?php

/**
 * Ask the server to silently close the connection.
 *
 * @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_Quit extends Rediska_Command_Abstract
{
    protected $_affectedConnections = array();

    /**
     * Create command
     *
     * @return Rediska_Connection_Exec
     */
    public function create()
    {
        $command = array('QUIT');

        $this->_affectedConnections = $this->_rediska->getConnections();

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

        return $commands;
    }

    public function write()
    {
        parent::write();

        foreach($this->_affectedConnections as $connection) {
            $connection->disconnect();
        }

        return true;
    }

    public function read()
    {
        return true;
    }
}


<?php

/**
 * Insert a new value as the element after the reference value
 *
 * @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_InsertToListAfter extends Rediska_Command_InsertToListAbstract
{
    /**
     * Create command
     *
     * @param string  $key            Key name
     * @param mixed   $referenceValue Reference value
     * @param mixed   $value          Value
     * @return Rediska_Connection_Exec
     */
    public function create($key, $referenceValue, $value)
    {
        return $this->_create($key, self::AFTER, $referenceValue, $value);
    }
}


<?php

/**
 * Return and remove the last 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_PopFromListBlocking extends Rediska_Command_Abstract
{
    /**
     * Supported version
     *
     * @var string
     */
    protected $_version = '1.3.1';

    /**
     * Store connection
     *
     * @var Rediska_Connection
     */
    protected $_storeConnection;

    /**
     * Create command
     *
     * @param string|array $keyOrKeys           Key name or array of names
     * @param integer      $timeout[optional]   Timeout. 0 for default - timeout is disabled.
     * @param string       $pushToKey[optional] If not null - push value to another list.
     * @return Rediska_Connection_Exec
     */
    public function create($keyOrKeys, $timeout = 0, $pushToKey = null)
    {
        // TODO: Refactor this shit

        if ($pushToKey !== null) {
            if (is_array($keyOrKeys)) {
                throw new Rediska_Command_Exception('PopFromListBlocking with $pushToKey argument accept only one key');
            }

            $key = $keyOrKeys;

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

            $this->_storeConnection = $this->getRediska()->getConnectionByKeyName($pushToKey);

            if ($connection != $this->_storeConnection) {
                $this->setAtomic(false);

                $command = array('BRPOP',
                                 $this->getRediska()->getOption('namespace') . $key,
                                 $timeout);
            } else {
                $this->_throwExceptionIfNotSupported('2.1.7');

                $command = array('BRPOPLPUSH',
                                 $this->getRediska()->getOption('namespace') . $key,
                                 $this->getRediska()->getOption('namespace') . $pushToKey,
                                 $timeout);
            }

            return new Rediska_Connection_Exec($connection, $command);
        } else {
            $keys = array();
            if (!is_array($keyOrKeys)) {
                $keys = array($keyOrKeys);
            } elseif (!empty($keyOrKeys)) {
                $keys = $keyOrKeys;
            } else {
                throw new Rediska_Command_Exception('Not present keys for pop');
            }

            $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 pop until worked only with one connection. Try to use Rediska#on() method for specify it.");
            }

            $execs = array();
            foreach ($keysByConnections as $connectionAlias => $keys) {
                $command = array('BRPOP');
                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 string|array $response
     * @return mixed
     */
    public function parseResponse($response)
    {
        if ($this->pushToKey !== null) {
            if (empty($response)) {
                return null;
            }

            if (!$this->isAtomic()) {
                $command = array('LPUSH',
                                 $this->_rediska->getOption('namespace') . $this->pushToKey,
                                 $response[1]);

                $exec = new Rediska_Connection_Exec($this->_storeConnection, $command);
                $exec->execute();

                $value = $response[1];
            } else {
                $value = $response;
            }

            return $this->_rediska->getSerializer()->unserialize($value);
        } else {
            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

namespace Onoi\MessageReporter;

/**
 * Message reporter that reports messages by passing them along to all
 * registered handlers.
 *
 * @since 1.0
 *
 * @license GNU GPL v2+
 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
 */
class ObservableMessageReporter implements MessageReporter {

	/**
	 * @since 1.0
	 *
	 * @var MessageReporter[]
	 */
	protected $reporters = [];

	/**
	 * @since 1.0
	 *
	 * @var callable[]
	 */
	protected $callbacks = [];

	/**
	 * @since 1.0
	 *
	 * @param string $message
	 *
	 * @see MessageReporter::report
	 */
	public function reportMessage( $message ) {
		foreach ( $this->reporters as $reporter ) {
			$reporter->reportMessage( $message );
		}

		foreach ( $this->callbacks as $callback ) {
			call_user_func( $callback, $message );
		}
	}

	/**
	 * Register a new message reporter.
	 *
	 * @since 1.0
	 *
	 * @param MessageReporter $reporter
	 *
	 */
	public function registerMessageReporter( MessageReporter $reporter ) {
		$this->reporters[] = $reporter;
	}

	/**
	 * Register a callback as message reporter.
	 *
	 * @since 1.0
	 *
	 * @param callable $handler |null
	 *
	 */
	public function registerReporterCallback( $handler = null ) {
		if ( is_callable( $handler ) ) {
			$this->callbacks[] = $handler;
		}
	}

}


<?php # -*- coding: utf-8 -*-
/*
 * This file is part of the Brain Nonces package.
 *
 * (c) Giuseppe Mazzapica
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Brain\Nonces;

/**
 * WordPress-specific nonce implementation.
 *
 * @author  Giuseppe Mazzapica <giuseppe.mazzapica@gmail.com>
 * @package Brain\Nonces
 * @license http://opensource.org/licenses/MIT MIT
 */
final class WpNonce implements NonceInterface
{

    /**
     * @var string
     */
    private $action;

    /**
     * @var int
     */
    private $life;

    /**
     * Constructor. Save properties as instance variables.
     *
     * We allow to customize nonce Time To Live, defaulting to 30 minutes (1800 seconds) that
     * is much less than the 24 hours of WordPress defaults.
     *
     * @param string $action
     * @param int $life
     */
    public function __construct($action = '', $life = 1800)
    {
        $this->action = is_string($action) ? $action : '';
        $this->life = is_numeric($life) ? (int)$life : 1800;
    }

    /**
     * @inheritdoc
     */
    public function action()
    {
        return $this->action;
    }

    /**
     * Validates the nonce against given context.
     *
     * When not provided, context defaults to `RequestGlobalsContext`, so that value is searched
     * in super globals.
     *
     * We need to filter the nonce life and remove the filter afterwards, because WP does not
     * allow to filter nonce by action (yet? @link https://core.trac.wordpress.org/ticket/35188)
     *
     * @param NonceContextInterface $context
     * @return bool
     */
    public function validate(NonceContextInterface $context = null)
    {
        $context or $context = new RequestGlobalsContext();

        $value = $context->offsetExists($this->action) ? $context[$this->action] : '';
        if (!$value || !is_string($value)) {
            return false;
        }

        $lifeFilter = $this->lifeFilter();

        add_filter('nonce_life', $lifeFilter);
        $valid = wp_verify_nonce($value, $this->hashedAction());
        remove_filter('nonce_life', $lifeFilter);

        return (bool)$valid;
    }

    /**
     * Returns the nonce string built with WordPress core function.
     *
     * We need to filter the nonce life and remove the filter afterwards, because WP does not
     * allow to filter nonce by action (yet? @link https://core.trac.wordpress.org/ticket/35188)
     *
     * @return string Nonce value.
     */
    public function __toString()
    {
        $lifeFilter = $this->lifeFilter();

        add_filter('nonce_life', $lifeFilter);
        $value = wp_create_nonce($this->hashedAction());
        remove_filter('nonce_life', $lifeFilter);

        return $value;
    }

    /**
     * Returns the callback that will be used to filter nonce life.
     *
     * @return \Closure
     */
    private function lifeFilter()
    {
        return function () {
            return $this->life;
        };
    }

    /**
     * Returns an hashed version of the action.
     *
     * Current blog id is appended to nonce action to make nonce blog specific.
     * WordPress will hash the action, so we could avoid do the hashing here.
     * However, unlike WordPress, we don't have a nonce "key" in URL or form fields, we use the
     * action for that, so nonce action publicly clearly accessible.
     * For this reason we do hash to make sure that trace back the nonce value from the action
     * is as much hard as possible.
     * This relies on a strong salt, which is required anyway for good WP security.
     *
     * @return string
     */
    private function hashedAction()
    {
        return wp_hash($this->action . get_current_blog_id(), 'nonce');
    }
}


<?php

namespace Burrow\Publisher;

use Assert\AssertionFailedException;
use Burrow\Driver;
use Burrow\Message;
use Burrow\QueuePublisher;

class AsyncPublisher implements QueuePublisher
{
    /** @var Driver */
    private $driver;

    /** @var string */
    private $exchangeName;

    /**
     * Constructor
     *
     * @param Driver $driver
     * @param string $exchangeName
     */
    public function __construct(Driver $driver, $exchangeName)
    {
        $this->driver = $driver;
        $this->exchangeName = $exchangeName;
    }

    /**
     * Publish a message on the queue
     *
     * @param mixed    $data
     * @param string   $routingKey
     * @param string[] $headers
     *
     * @return mixed
     *
     * @throws AssertionFailedException
     * @throws \InvalidArgumentException
     */
    public function publish($data, $routingKey = '', array $headers = [])
    {
        $this->driver->publish($this->exchangeName, new Message($data, $routingKey, $headers));

        return null;
    }
}


<?php

namespace Burrow\Publisher;

use Assert\AssertionFailedException;
use Burrow\Driver;
use Burrow\Message;
use Burrow\QueueHandler;
use Burrow\QueuePublisher;

class SyncPublisher implements QueuePublisher
{
    /** @var Driver */
    private $driver;

    /** @var string */
    private $exchangeName;

    /** @var int */
    private $timeout;

    /**
     * Constructor
     *
     * @param Driver $driver
     * @param string $exchangeName
     * @param int    $timeout
     */
    public function __construct(Driver $driver, $exchangeName, $timeout = 10)
    {
        $this->driver = $driver;
        $this->exchangeName = $exchangeName;
        $this->timeout = $timeout;
    }

    /**
     * Publish a message on the queue
     *
     * @param mixed    $data
     * @param string   $routingKey
     * @param string[] $headers
     *
     * @return mixed
     *
     * @throws AssertionFailedException
     * @throws \InvalidArgumentException
     */
    public function publish($data, $routingKey = '', array $headers = [])
    {
        $response = null;
        $correlationId = uniqid('', false);
        $replyTo = $this->driver->declareSimpleQueue('', Driver::QUEUE_EXCLUSIVE);

        $this->driver->publish(
            $this->exchangeName,
            new Message($data, $routingKey, $headers, $correlationId, $replyTo)
        );

        $this->driver->consume(
            $replyTo,
            function (Message $message) use ($correlationId, &$response) {
                if ($message->getCorrelationId() == $correlationId) {
                    $response = $message->getBody();
                    return QueueHandler::STOP_CONSUMING;
                }
                return QueueHandler::CONTINUE_CONSUMING;
            },
            $this->timeout
        );

        return $response;
    }
}


<?php

namespace Burrow\CLI;

use Burrow\Driver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @codeCoverageIgnore
 */
class BindCommand extends Command
{
    /** @var Driver */
    private $driver;

    /**
     * DeclareQueueCommand constructor.
     *
     * @param Driver $driver
     *
     * @throws LogicException
     */
    public function __construct(Driver $driver)
    {
        parent::__construct();

        $this->driver = $driver;
    }

    protected function configure()
    {
        $this->setName('admin:bind')
            ->setDescription('Declares an exchange in RabbitMQ.')
            ->addArgument(
                'exchange',
                InputArgument::REQUIRED,
                'The name of the exchange to bind.'
            )
            ->addArgument(
                'queue',
                InputArgument::REQUIRED,
                'The name of the queue to bind.'
            )
            ->addOption(
                'routingKey',
                'k',
                InputOption::VALUE_REQUIRED,
                'The routing key to use.'
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int|null|void
     *
     * @throws InvalidArgumentException
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $exchange = $input->getArgument('exchange');
        $queue = $input->getArgument('queue');
        $routingKey = ($input->getOption('routingKey') !== null) ? $input->getOption('routingKey') : '';

        $this->driver->bindQueue(
            $input->getArgument('exchange'),
            $input->getArgument('queue'),
            $routingKey
        );

        $output->writeln(sprintf(
            '<info>Bind exchange <comment>%s</comment> to queue <comment>%s</comment> [<comment>%s</comment>]</info>',
            $exchange,
            $queue,
            $routingKey
        ));
    }
}


<?php

namespace Burrow\Publisher;

use Burrow\QueuePublisher;
use Burrow\Serializer;

class SerializingPublisher implements QueuePublisher
{
    /** @var QueuePublisher */
    private $publisher;

    /** @var Serializer */
    private $serializer;

    /**
     * SerializingPublisher constructor.
     *
     * @param QueuePublisher $publisher
     * @param Serializer     $serializer
     */
    public function __construct(
        QueuePublisher $publisher,
        Serializer $serializer
    ) {
        $this->publisher = $publisher;
        $this->serializer = $serializer;
    }

    /**
     * Publish a message on the queue
     *
     * @param mixed    $data
     * @param string   $routingKey
     * @param string[] $headers
     *
     * @return mixed
     */
    public function publish($data, $routingKey = '', array $headers = [])
    {
        $returnValue = $this->publisher->publish(
            $this->serializer->serialize($data),
            $routingKey,
            $headers
        );

        return $this->serializer->deserialize($returnValue);
    }
}


<?php

namespace Burrow\Handler;

use Burrow\ConsumeOptions;
use Burrow\Message;
use Burrow\QueueHandler;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

class StopOnExceptionHandler implements QueueHandler, LoggerAwareInterface
{
    use LoggerAwareTrait;

    /** @var QueueHandler */
    private $handler;

    /**
     * AckHandler constructor.
     *
     * @param QueueHandler $handler
     */
    public function __construct(QueueHandler $handler)
    {
        $this->handler = $handler;

        $this->logger = new NullLogger();
    }

    /**
     * Handle a message.
     *
     * @param Message $message
     *
     * @return bool
     */
    public function handle(Message $message)
    {
        try {
            $this->handler->handle($message);
            return self::CONTINUE_CONSUMING;
        } catch (\Exception $e) {
            $this->logger->error($e);
            return self::STOP_CONSUMING;
        }
    }

    /**
     * Modify and return the options for consumption.
     *
     * @param ConsumeOptions $options
     *
     * @return ConsumeOptions
     */
    public function options(ConsumeOptions $options)
    {
        return $this->handler->options($options);
    }
}


<?php

namespace Burrow\Event\Listener\Metric;

use Burrow\Event\DaemonStarted;
use Burrow\Event\Listener\ListenerException;
use Burrow\Metric\MetricService;
use League\Event\EventInterface;
use League\Event\ListenerInterface;

class SendMetricOnDaemonStarted implements ListenerInterface
{
    /** @var MetricService */
    private $metricService;

    /**
     * SendMetricOnDaemonStarted constructor.
     *
     * @param MetricService $metricService
     */
    public function __construct(MetricService $metricService)
    {
        $this->metricService = $metricService;
    }

    /**
     * @param EventInterface $event
     *
     * @throws ListenerException
     */
    public function handle(EventInterface $event)
    {
        if (!($event instanceof DaemonStarted)) {
            throw ListenerException::badEventGiven($event);
        }

        $this->metricService->increment('daemon.started');
    }

    public function isListener($listener)
    {
        return $listener === $this;
    }
}


<?php

namespace Burrow\Driver;

use Assert\AssertionFailedException;
use Burrow\Driver;
use Burrow\Exception\ConsumerException;
use Burrow\Exception\TimeoutException;
use Burrow\Message;

/**
 * @codeCoverageIgnore
 */
class PeclAmqpDriver implements Driver
{
    const DELIVERY_MODE = 'delivery_mode';
    const CONTENT_TYPE = 'content_type';
    const APPLICATION_HEADERS = 'headers';
    const CORRELATION_ID = 'correlation_id';
    const REPLY_TO = 'reply_to';

    /** @var \AMQPConnection */
    private $connection;

    /** @var \AMQPChannel */
    private $channel;

    /**
     * PeclAmqpDriver constructor.
     *
     * @param \AMQPConnection $connection
     */
    public function __construct(\AMQPConnection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * Declare a persistent queue
     *
     * @param string $queueName
     * @param string $type
     *
     * @return string
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPQueueException
     */
    public function declareSimpleQueue($queueName = '', $type = self::QUEUE_DURABLE)
    {
        $flag = AMQP_DURABLE;
        if ($type === self::QUEUE_EXCLUSIVE) {
            $flag = AMQP_EXCLUSIVE;
        }

        $queue = $this->getQueue($queueName);
        $queue->setFlags($flag);
        $queue->declareQueue();

        return $queue->getName();
    }

    /**
     * Declare an exchange
     *
     * @param  string $exchangeName
     * @param  string $type
     *
     * @return string
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPExchangeException
     */
    public function declareExchange($exchangeName = '', $type = self::EXCHANGE_TYPE_FANOUT)
    {
        $exchange = $this->getExchange($exchangeName);
        $exchange->setType($type);
        $exchange->setFlags(AMQP_DURABLE);
        $exchange->declareExchange();

        return $exchange->getName();
    }

    /**
     * Bind an existing queue to an exchange
     *
     * @param  string $exchange
     * @param  string $queueName
     * @param  string $routingKey
     *
     * @return void
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPQueueException
     */
    public function bindQueue($exchange, $queueName, $routingKey = '')
    {
        $queue = $this->getQueue($queueName);
        $queue->bind($exchange, $routingKey);
    }

    /**
     * Create a persisting queue and bind it to an exchange
     *
     * @param  string $exchange
     * @param  string $queueName
     * @param  string $routingKey
     *
     * @return void
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPQueueException
     */
    public function declareAndBindQueue($exchange, $queueName, $routingKey = '')
    {
        $this->declareSimpleQueue($queueName);
        $this->bindQueue($exchange, $queueName, $routingKey);
    }

    /**
     * Delete a queue
     *
     * @param string $queueName
     *
     * @return void
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPQueueException
     */
    public function deleteQueue($queueName)
    {
        $this->getQueue($queueName)->delete();
    }

    /**
     * Delete an exchange
     *
     * @param string $exchangeName
     *
     * @return void
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPExchangeException
     */
    public function deleteExchange($exchangeName)
    {
        $this->getExchange($exchangeName)->delete();
    }

    /**
     * Publish a message in the exchange
     *
     * @param string  $exchangeName
     * @param Message $message
     *
     * @return void
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPExchangeException
     */
    public function publish($exchangeName, Message $message)
    {
        $exchange = $this->getExchange($exchangeName);
        $exchange->publish(
            $message->getBody(),
            $message->getRoutingKey(),
            AMQP_NOPARAM,
            self::getMessageProperties($message)
        );
    }

    /**
     * Consume the queue
     *
     * @param string   $queueName
     * @param callable $callback
     * @param int      $timeout
     * @param bool     $autoAck
     *
     * @return void
     *
     * @throws ConsumerException
     * @throws TimeoutException
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPQueueException
     * @throws \InvalidArgumentException
     * @throws AssertionFailedException
     */
    public function consume($queueName, callable $callback, $timeout = 0, $autoAck = true)
    {
        $this->connection->setReadTimeout($timeout);
        $this->getChannel()->setPrefetchCount(1);
        $queue = $this->getQueue($queueName);
        $flags = $autoAck ? AMQP_AUTOACK : AMQP_NOPARAM;

        try {
            $queue->consume(function (\AMQPEnvelope $message) use ($callback, $queueName) {

                $burrowMessage = new Message(
                    $message->getBody(),
                    $message->getRoutingKey(),
                    $message->getHeaders(),
                    $message->getCorrelationId(),
                    $message->getReplyTo()
                );
                $burrowMessage->setDeliveryTag($message->getDeliveryTag());
                $burrowMessage->setQueue($queueName);

                return $callback($burrowMessage);
            }, $flags);
        } catch (\AMQPQueueException $e) {
            if ($e->getMessage() === 'Consumer timeout exceed') {
                throw TimeoutException::build($e, $timeout);
            }
            throw ConsumerException::build($e);
        }
    }

    /**
     * Acknowledge the reception of the message.
     *
     * @param Message $message
     *
     * @return void
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPQueueException
     */
    public function ack(Message $message)
    {
        $queue = $this->getQueue($message->getQueue());
        $queue->ack($message->getDeliveryTag());
    }

    /**
     * Acknowledge an error during the consumption of the message
     *
     * @param Message $message
     * @param bool    $requeue
     *
     * @return void
     *
     * @throws \AMQPConnectionException
     * @throws \AMQPChannelException
     * @throws \AMQPQueueException
     */
    public function nack(Message $message, $requeue = true)
    {
        $queue = $this->getQueue($message->getQueue());
        $queue->nack($message->getDeliveryTag(), $requeue ? AMQP_REQUEUE : AMQP_NOPARAM);
    }

    /**
     * Close the connection
     *
     * @return void
     */
    public function close()
    {
        $this->connection->disconnect();
    }

    /**
     * @return \AMQPChannel
     *
     * @throws \AMQPConnectionException
     */
    private function getChannel()
    {
        if (null === $this->channel) {
            $this->connection->connect();
            $this->channel = new \AMQPChannel($this->connection);
        }

        return $this->channel;
    }

    /**
     * @param string $queueName
     *
     * @return \AMQPQueue
     *
     * @throws \AMQPQueueException
     * @throws \AMQPConnectionException
     */
    private function getQueue($queueName)
    {
        $queue = new \AMQPQueue($this->getChannel());
        if ($queueName) {
            $queue->setName($queueName);
        }

        return $queue;
    }

    /**
     * @param string $exchangeName
     *
     * @return \AMQPExchange
     *
     * @throws \AMQPExchangeException
     * @throws \AMQPConnectionException
     */
    private function getExchange($exchangeName)
    {
        $exchange = new \AMQPExchange($this->getChannel());
        if ($exchangeName) {
            $exchange->setName($exchangeName);
        }

        return $exchange;
    }

    /**
     * Returns the message parameters
     *
     * @param Message $message
     *
     * @return array
     */
    private static function getMessageProperties(Message $message)
    {
        $properties = [
            self::DELIVERY_MODE       => 2,
            self::CONTENT_TYPE        => 'text/plain',
            self::APPLICATION_HEADERS => $message->getHeaders()
        ];

        if ($message->getCorrelationId() !== null) {
            $properties[self::CORRELATION_ID] = $message->getCorrelationId();
        }

        if ($message->getReplyTo() !== null) {
            $properties[self::REPLY_TO] = $message->getReplyTo();
        }

        return $properties;
    }
}


<?php

namespace Burrow\Daemon;

use Burrow\ConsumeOptions;
use Burrow\Event\DaemonStarted;
use Burrow\Event\DaemonStopped;
use Burrow\Event\MessageConsumed;
use Burrow\Event\MessageReceived;
use Burrow\Event\NullEmitter;
use Evaneos\Daemon\Daemon;
use Evaneos\Daemon\DaemonMonitor;
use Burrow\Driver;
use Burrow\Message;
use Evaneos\Daemon\Monitor\NullMonitor;
use Burrow\QueueHandler;
use League\Event\Emitter;
use League\Event\EmitterInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

class QueueHandlingDaemon implements Daemon, LoggerAwareInterface
{
    use LoggerAwareTrait;

    /** @var Driver */
    private $driver;

    /** @var QueueHandler */
    private $handler;

    /** @var string */
    private $queueName;

    /** @var DaemonMonitor */
    private $monitor;

    /** @var Emitter */
    private $eventEmitter;

    /**
     * Handler constructor.
     *
     * @param Driver                $driver
     * @param QueueHandler          $handler
     * @param string                $queueName
     * @param EmitterInterface|null $eventEmitter
     */
    public function __construct(
        Driver $driver,
        QueueHandler $handler,
        $queueName,
        EmitterInterface $eventEmitter = null
    ) {
        $this->driver = $driver;
        $this->handler = $handler;
        $this->queueName = $queueName;

        $this->monitor = new NullMonitor();
        $this->logger = new NullLogger();
        $this->eventEmitter = $eventEmitter ?: new NullEmitter();
    }

    /**
     * Run the daemon
     *
     * @return void
     */
    public function start()
    {
        $this->eventEmitter->emit(new DaemonStarted());

        $this->logger->info('Starting daemon...');

        $options = $this->handler->options(new ConsumeOptions());

        $this->driver->consume(
            $this->queueName,
            function (Message $message) {
                $this->eventEmitter->emit(new MessageReceived());
                $this->monitor->monitor($this, $message);

                $result = $this->handler->handle($message);

                $this->eventEmitter->emit(new MessageConsumed());
                pcntl_signal_dispatch();

                return $result;
            },
            $options->getTimeout(),
            $options->isAutoAck()
        );

        $this->stop();
    }

    /**
     * Stop the daemon
     *
     * @return void
     */
    public function stop()
    {
        $this->logger->info('Closing daemon...');

        $this->driver->close();

        $this->eventEmitter->emit(new DaemonStopped());
    }

    /**
     * Set a monitor.
     *
     * @param DaemonMonitor $monitor
     */
    public function setMonitor(DaemonMonitor $monitor)
    {
        $this->monitor = $monitor;
    }
}


<?php

namespace Burrow\Event\Listener\Metric;

use Burrow\Event\DaemonStarted;
use Burrow\Event\DaemonStopped;
use Burrow\Event\MessageConsumed;
use Burrow\Event\MessageReceived;
use Burrow\Metric\MetricService;
use League\Event\ListenerAcceptorInterface;
use League\Event\ListenerProviderInterface;

class SendMetricListenerProvider implements ListenerProviderInterface
{
    /** @var MetricService  */
    private $metricService;

    /**
     * SendMetricSubscriber constructor.
     *
     * @param MetricService $metricService
     */
    public function __construct(MetricService $metricService)
    {
        $this->metricService = $metricService;
    }

    /**
     * @param ListenerAcceptorInterface $listenerAcceptor
     *
     * @return ListenerProviderInterface|void
     */
    public function provideListeners(ListenerAcceptorInterface $listenerAcceptor)
    {
        $listenerAcceptor->addListener(DaemonStarted::NAME, new SendMetricOnDaemonStarted($this->metricService));
        $listenerAcceptor->addListener(DaemonStopped::NAME, new SendMetricOnDaemonStopped($this->metricService));
        $listenerAcceptor->addListener(MessageReceived::NAME, new SendMetricOnMessageReceived($this->metricService));

        $sendMetricOnMessageConsumed = new SendMetricOnMessageConsumed($this->metricService);
        $listenerAcceptor->addListener(MessageReceived::NAME, $sendMetricOnMessageConsumed);
        $listenerAcceptor->addListener(MessageConsumed::NAME, $sendMetricOnMessageConsumed);
    }
}


<?php

namespace Burrow\CLI;

use Assert\Assertion;
use Assert\AssertionFailedException;
use Burrow\Driver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @codeCoverageIgnore
 */
class DeclareExchangeCommand extends Command
{
    /** @var Driver */
    private $driver;

    /**
     * DeclareQueueCommand constructor.
     *
     * @param Driver $driver
     *
     * @throws LogicException
     */
    public function __construct(Driver $driver)
    {
        parent::__construct();

        $this->driver = $driver;
    }

    protected function configure()
    {
        $this->setName('admin:declare:exchange')
            ->setDescription('Declares an exchange in RabbitMQ.')
            ->addArgument(
                'name',
                InputArgument::REQUIRED,
                'The name of the exchange to declare.'
            )
            ->addArgument(
                'type',
                InputArgument::OPTIONAL,
                'The type of the exchange. Can be any of ' .
                '"' . Driver::EXCHANGE_TYPE_DIRECT . '", ' .
                '"' . Driver::EXCHANGE_TYPE_TOPIC . '", ' .
                '"' . Driver::EXCHANGE_TYPE_FANOUT . '", ' .
                '"' . Driver::EXCHANGE_TYPE_HEADERS . '".',
                Driver::EXCHANGE_TYPE_FANOUT
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int|null|void
     *
     * @throws InvalidArgumentException
     * @throws AssertionFailedException
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $name = $input->getArgument('name');
        $type = $input->getArgument('type');
        Assertion::choice(
            $type,
            [
                Driver::EXCHANGE_TYPE_DIRECT,
                Driver::EXCHANGE_TYPE_TOPIC,
                Driver::EXCHANGE_TYPE_FANOUT,
                Driver::EXCHANGE_TYPE_HEADERS
            ],
            'The type of the exchange must be one of the four valid values.'
        );

        $this->driver->declareExchange($name, $type);
        $output->writeln(
            sprintf(
                '<info>Declare exchange <comment>%s</comment> [<comment>%s</comment>]</info>',
                $name,
                $type
            )
        );
    }
}


<?php

namespace Burrow\Serializer;

use Assert\Assertion;
use Assert\AssertionFailedException;
use Burrow\Serializer;

class JsonSerializer implements Serializer
{
    /**
     * @param mixed $message
     *
     * @return string
     */
    public function serialize($message)
    {
        return json_encode($message);
    }

    /**
     * @param string $message
     *
     * @return mixed
     *
     * @throws AssertionFailedException
     */
    public function deserialize($message)
    {
        if ($message === null) {
            return null;
        }

        Assertion::string($message, 'The message to deserialize must be a valid string');

        return json_decode($message);
    }
}


<?php

namespace Burrow\Handler;

use Burrow\ConsumeOptions;
use Burrow\Driver;
use Burrow\Message;
use Burrow\QueueHandler;

class AckHandler implements QueueHandler
{
    /** @var QueueHandler */
    private $handler;

    /** @var Driver */
    private $driver;

    /** @var bool */
    private $requeueOnFailure;

    /**
     * AckHandler constructor.
     *
     * @param QueueHandler $handler
     * @param Driver       $driver
     * @param bool         $requeueOnFailure
     */
    public function __construct(QueueHandler $handler, Driver $driver, $requeueOnFailure = true)
    {
        $this->handler = $handler;
        $this->driver = $driver;
        $this->requeueOnFailure = $requeueOnFailure;
    }

    /**
     * Handle a message.
     *
     * @param Message $message
     *
     * @return bool
     *
     * @throws \Exception
     */
    public function handle(Message $message)
    {
        try {
            $result = $this->handler->handle($message);
            $this->driver->ack($message);
            return $result;
        } catch (\Exception $e) {
            $this->driver->nack($message, $this->requeueOnFailure);
            throw $e;
        }
    }

    /**
     * Modify and return the options for consumption.
     *
     * @param ConsumeOptions $options
     *
     * @return ConsumeOptions
     */
    public function options(ConsumeOptions $options)
    {
        $options->disableAutoAck();

        return $this->handler->options($options);
    }
}


<?php

namespace Burrow\Exception;

class TimeoutException extends BurrowException
{
    /**
     * Build the exception from another timeout exception.
     *
     * @param \Exception $e
     * @param int        $timeout
     *
     * @return TimeoutException
     */
    public static function build(\Exception $e, $timeout)
    {
        return new self(
            sprintf('The connection timed out after %d sec while awaiting incoming data', $timeout),
            $e->getCode(),
            $e
        );
    }
}


<?php

namespace Burrow\Metric;

use Beberlei\Metrics\Collector\DogStatsD;
use Beberlei\Metrics\Collector\StatsD;
use Beberlei\Metrics\Factory;
use Beberlei\Metrics\MetricsException;

class MetricServiceFactory
{
    /**
     * @param       $type
     * @param array $options
     * @param array $contextualTags
     *
     * @return MetricService
     * @throws MetricsException
     * @throws \Exception
     */
    public static function create($type, $options = [], $contextualTags = [])
    {
        $collector = Factory::create($type, $options);

        switch ($type) {
            case 'dogstatsd':
                /** @var DogStatsD $collector */
                return new DogStatsDMetricService($collector, $contextualTags);
            case 'statsd':
                /** @var StatsD $collector */
                return new StatsDMetricService($collector);
        }

        throw new \Exception(sprintf('MetricService for %s is not implemented', $type));
    }
}


<?php

namespace Burrow\CLI;

use Assert\Assertion;
use Assert\AssertionFailedException;
use Burrow\Driver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @codeCoverageIgnore
 */
class InitCommand extends Command
{
    /** @var Driver */
    private $driver;

    /**
     * DeclareQueueCommand constructor.
     *
     * @param Driver $driver
     *
     * @throws LogicException
     */
    public function __construct(Driver $driver)
    {
        parent::__construct();

        $this->driver = $driver;
    }

    protected function configure()
    {
        $this->setName('admin:init')
            ->setDescription('Init a RabbitMQ exchange / queue wiring.')
            ->addArgument(
                'file',
                InputArgument::REQUIRED,
                'The path of the file to load wiring configuration from. It must contain a json declaration.'
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int|null|void
     *
     * @throws InvalidArgumentException
     * @throws \InvalidArgumentException
     * @throws AssertionFailedException
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $configuration = $this->getConfiguration($input);
        $this->declareQueues($configuration, $output);
        $this->bind($configuration, $output);
    }

    /**
     * @param InputInterface $input
     *
     * @return array
     *
     * @throws \InvalidArgumentException
     * @throws AssertionFailedException
     * @throws InvalidArgumentException
     */
    protected function getConfiguration(InputInterface $input)
    {
        $file = $input->getArgument('file');
        Assertion::file($file, 'You must provide a valid file name');

        $configurationString = file_get_contents($file);
        Assertion::isJsonString($configurationString, 'The file must be a valid JSON');

        $configuration = @json_decode($configurationString, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new \InvalidArgumentException('Invalid json : ' . json_last_error_msg());
        }

        self::checkConfiguration($configuration);

        return $configuration;
    }

    /**
     * @param array $configuration
     *
     * @throws AssertionFailedException
     */
    private static function checkConfiguration(array $configuration)
    {
        self::checkQueuesConfiguration($configuration);
        self::checkExchangesConfiguration($configuration);
    }

    /**
     * @param array $configuration
     *
     * @throws AssertionFailedException
     */
    private static function checkQueuesConfiguration(array $configuration)
    {
        Assertion::keyIsset($configuration, 'queues', 'You must provide a `queues` configuration');

        $queues = $configuration['queues'];
        Assertion::isArray($queues, 'The `queues` configuration must be an array');

        /** @var string[]|string[][] $queues */
        foreach ($queues as $queueInformation) {
            if (is_string($queueInformation)) {
                continue;
            }
            Assertion::keyIsset($queueInformation, 'name', 'You must provide a name for the queue');
        }
    }

    /**
     * @param array $configuration
     *
     * @throws AssertionFailedException
     */
    private static function checkExchangesConfiguration(array $configuration)
    {
        Assertion::keyIsset($configuration, 'exchanges', 'You must provide an `exchanges` configuration');

        $exchanges = $configuration['exchanges'];
        Assertion::isArray($exchanges, 'The `exchanges` configuration must be an array');

        /** @var string[][] $exchanges */
        foreach ($exchanges as $exchangeInformation) {
            self::checkExchangeConfiguration($exchangeInformation);
        }
    }

    /**
     * @param array $exchangeInformation
     *
     * @throws AssertionFailedException
     */
    private static function checkExchangeConfiguration(array $exchangeInformation)
    {
        Assertion::keyIsset($exchangeInformation, 'name', 'You must provide a name for the exchange');
        Assertion::keyIsset($exchangeInformation, 'type', 'You must provide a type for the exchange');

        self::checkQueuesConfiguration($exchangeInformation);
    }

    /**
     * @param array           $configuration
     * @param OutputInterface $output
     */
    private function declareQueues($configuration, OutputInterface $output)
    {
        $queues = $configuration['queues'];
        /** @var string[] $queues */
        foreach ($queues as $queue) {
            $this->driver->declareSimpleQueue($queue);
            $output->writeln(sprintf('<info>Declare queue <comment>%s</comment></info>', $queue));
        }
    }

    /**
     * @param array           $configuration
     * @param OutputInterface $output
     */
    private function bind(array $configuration, OutputInterface $output)
    {
        $exchanges = $configuration['exchanges'];
        /** @var string[][] $exchanges */
        foreach ($exchanges as $exchangeInformation) {
            $this->bindExchange($exchangeInformation, $output);
        }
    }

    /**
     * @param array           $exchangeInformation
     * @param OutputInterface $output
     */
    private function bindExchange(array $exchangeInformation, OutputInterface $output)
    {
        $exchangeName = $exchangeInformation['name'];
        $exchangeType = $exchangeInformation['type'];

        $this->driver->declareExchange($exchangeName, $exchangeType);

        $output->writeln(
            sprintf(
                '<info>Declare exchange <comment>%s</comment> [<comment>%s</comment>]</info>',
                $exchangeName,
                $exchangeType
            )
        );

        $queues = $exchangeInformation['queues'];
        /** @var string[][] $queues */
        foreach ($queues as $queueInformation) {
            $this->bindQueue($exchangeName, $queueInformation, $output);
        }
    }

    /**
     * @param string          $exchangeName
     * @param array           $queueInformation
     * @param OutputInterface $output
     */
    private function bindQueue(
        $exchangeName,
        array $queueInformation,
        OutputInterface $output
    ) {
        $queueName = $queueInformation['name'];
        $routingKey = isset($queueInformation['routingKey']) ? $queueInformation['routingKey'] : '';

        $this->driver->declareAndBindQueue($exchangeName, $queueName, $routingKey);

        $output->writeln(sprintf(
            '<info>Bind exchange <comment>%s</comment> to queue ' .
            '<comment>%s</comment> [<comment>%s</comment>]</info>',
            $exchangeName,
            $queueName,
            $routingKey
        ));
    }
}


<?php

namespace Burrow\Event\Listener\Metric;

use Burrow\Clock;
use Burrow\Event\Listener\ListenerException;
use Burrow\Event\MessageConsumed;
use Burrow\Event\MessageReceived;
use Burrow\Metric\MetricService;
use Burrow\RealClock;
use League\Event\EventInterface;
use League\Event\ListenerInterface;

class SendMetricOnMessageConsumed implements ListenerInterface
{
    /** @var MetricService */
    private $metricService;

    /** @var Clock */
    private $clock;

    /** @var null|float */
    private $messageReceivedAt;

    /**
     * SendMetricOnMessageConsumed constructor.
     *
     * @param MetricService $metricService
     * @param Clock|null    $clock
     */
    public function __construct(MetricService $metricService, Clock $clock = null)
    {
        $this->metricService = $metricService;
        $this->clock = $clock ?: new RealClock();
    }

    /**
     * @param EventInterface | MessageConsumed $event
     *
     * @throws ListenerException
     */
    public function handle(EventInterface $event)
    {
        if ($event instanceof MessageReceived) {
            $this->messageReceivedAt = $this->clock->timestampInMs();
            return;
        }

        if (!($event instanceof MessageConsumed)) {
            throw ListenerException::badEventGiven($event);
        }

        $this->metricService->increment(
            'daemon.message_consumed'
        );

        $this->metricService->timing(
            'daemon.message_processing_time',
            $this->clock->timestampInMs() - $this->messageReceivedAt
        );
    }

    public function isListener($listener)
    {
        return $listener === $this;
    }
}


<?php

namespace Burrow\Handler;

use Burrow\ConsumeOptions;
use Burrow\Exception\ConsumerException;
use Burrow\Message;
use Burrow\QueueHandler;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

class ContinueOnExceptionHandler implements QueueHandler, LoggerAwareInterface
{
    use LoggerAwareTrait;

    /** @var QueueHandler */
    private $handler;

    /**
     * AckHandler constructor.
     *
     * @param QueueHandler $handler
     */
    public function __construct(QueueHandler $handler)
    {
        $this->handler = $handler;

        $this->logger = new NullLogger();
    }

    /**
     * Handle a message.
     *
     * @param Message $message
     *
     * @return bool
     */
    public function handle(Message $message)
    {
        // Beware of infinite loop!
        try {
            $this->handler->handle($message);
        } catch (\Exception $e) {
            $this->logger->error($e);

            if ($e instanceof ConsumerException) {
                return false;
            }
        }

        return self::CONTINUE_CONSUMING;
    }

    /**
     * Modify and return the options for consumption.
     *
     * @param ConsumeOptions $options
     *
     * @return ConsumeOptions
     */
    public function options(ConsumeOptions $options)
    {
        return $this->handler->options($options);
    }
}


<?php

namespace Burrow\Handler;

use Assert\Assertion;
use Assert\AssertionFailedException;
use Burrow\Driver;
use Burrow\QueueConsumer;
use Burrow\QueueHandler;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class HandlerBuilder
{
    /** @var Driver */
    private $driver;

    /** @var bool */
    private $autoAck;

    /** @var bool */
    private $sync;

    /** @var bool */
    private $requeueOnFailure;

    /** @var bool */
    private $stopOnFailure;

    /** @var LoggerInterface */
    private $logger;

    /**
     * HandlerBuilder constructor.
     *
     * @param Driver $driver
     */
    public function __construct(Driver $driver)
    {
        $this->driver = $driver;

        $this->autoAck = false;

        $this->sync = null;
        $this->requeueOnFailure = true;
        $this->stopOnFailure = true;

        $this->logger = new NullLogger();
    }

    /**
     * Keeps the autoAcking behavior
     */
    public function autoAck()
    {
        $this->autoAck = true;

        return $this->doNotRequeueOnFailure(); // cannot be re-queued if it's auto-acked
    }

    /**
     * Build a sync Handler.
     *
     * @return $this
     */
    public function sync()
    {
        $this->sync = true;

        return $this;
    }

    /**
     * Build an async Handler.
     *
     * @return $this
     */
    public function async()
    {
        $this->sync = false;

        return $this;
    }

    /**
     * Must the failed message be requeued.
     *
     * @return $this
     */
    public function doNotRequeueOnFailure()
    {
        $this->requeueOnFailure = false;

        return $this;
    }

    /**
     * Must the handler continue on failure
     *
     * @return $this
     */
    public function continueOnFailure()
    {
        $this->stopOnFailure = false;

        return $this;
    }

    /**
     * Set a logger.
     *
     * @param LoggerInterface $logger
     *
     * @return $this
     */
    public function log(LoggerInterface $logger)
    {
        $this->logger = $logger;

        return $this;
    }

    /**
     * Build the Handler.
     *
     * @param QueueConsumer $consumer
     *
     * @return QueueHandler
     *
     * @throws AssertionFailedException
     */
    public function build(QueueConsumer $consumer)
    {
        Assertion::notNull($this->sync, 'You must specify if the handler must be sync or async');

        // Sync
        $handler = $this->sync ?
            new SyncConsumerHandler($consumer, $this->driver) :
            new AsyncConsumerHandler($consumer);
        $handler->setLogger($this->logger);

        // Ack
        $handler = $this->autoAck ?
            $handler :
            new AckHandler($handler, $this->driver, $this->requeueOnFailure);

        // Stop / Continue
        $handler = $this->stopOnFailure ?
            new StopOnExceptionHandler($handler) :
            new ContinueOnExceptionHandler($handler);
        $handler->setLogger($this->logger);

        return $handler;
    }
}


<?php

namespace Burrow\Handler;

use Assert\AssertionFailedException;
use Burrow\ConsumeOptions;
use Burrow\Driver;
use Burrow\Exception\ConsumerException;
use Burrow\Message;
use Burrow\QueueConsumer;
use Burrow\QueueHandler;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

class SyncConsumerHandler implements QueueHandler, LoggerAwareInterface
{
    use LoggerAwareTrait;

    /** @var QueueConsumer */
    private $consumer;

    /** @var Driver */
    private $driver;

    /**
     * ConsumerHandler constructor.
     *
     * @param QueueConsumer $consumer
     * @param Driver        $driver
     */
    public function __construct(QueueConsumer $consumer, Driver $driver)
    {
        $this->consumer = $consumer;
        $this->driver = $driver;

        $this->logger = new NullLogger();
    }

    /**
     * Handle a message.
     *
     * @param Message $message
     *
     * @return bool
     *
     * @throws AssertionFailedException
     * @throws ConsumerException
     * @throws \InvalidArgumentException
     */
    public function handle(Message $message)
    {
        $returnValue = $this->consumer->consume($message->getBody(), $message->getHeaders());
        $this->handleSyncMessage($message, $returnValue);

        return self::CONTINUE_CONSUMING;
    }

    /**
     * Modify and return the options for consumption.
     *
     * @param ConsumeOptions $options
     *
     * @return ConsumeOptions
     */
    public function options(ConsumeOptions $options)
    {
        return $options;
    }

    /**
     * Handle the return value.
     *
     * @param Message $message
     * @param string  $returnValue
     *
     * @throws ConsumerException
     * @throws AssertionFailedException
     * @throws \InvalidArgumentException
     */
    private function handleSyncMessage(Message $message, $returnValue)
    {
        self::checkMessageIsSync($message);

        $this->logger->debug(
            'Send return value back!',
            [
                'returnValue' => $returnValue,
                'correlationId' => $message->getCorrelationId(),
                'replyTo' => $message->getReplyTo()
            ]
        );

        $this->driver->publish(
            '',
            new Message(
                $returnValue,
                $message->getReplyTo(),
                $message->getHeaders(),
                $message->getCorrelationId()
            )
        );
    }

    /**
     * @param Message $message
     *
     * @throws ConsumerException
     */
    private static function checkMessageIsSync(Message $message)
    {
        if ($message->getCorrelationId() == '' && $message->getReplyTo() == '') {
            throw ConsumerException::invalidSyncMessage();
        }
    }
}


<?php

namespace Burrow\Serializer;

use Assert\Assertion;
use Assert\AssertionFailedException;
use Burrow\Serializer;

class PhpSerializer implements Serializer
{
    /**
     * @param mixed $message
     *
     * @return string
     */
    public function serialize($message)
    {
        return serialize($message);
    }

    /**
     * @param string $message
     *
     * @return mixed
     *
     * @throws AssertionFailedException
     */
    public function deserialize($message)
    {
        if ($message === null) {
            return null;
        }

        Assertion::string($message, 'The message to deserialize must be a valid string');

        return unserialize($message);
    }
}


<?php

namespace Burrow\CLI;

use Burrow\Driver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @codeCoverageIgnore
 */
class DeclareQueueCommand extends Command
{
    /** @var Driver */
    private $driver;

    /**
     * DeclareQueueCommand constructor.
     *
     * @param Driver $driver
     *
     * @throws LogicException
     */
    public function __construct(Driver $driver)
    {
        parent::__construct();

        $this->driver = $driver;
    }

    protected function configure()
    {
        $this->setName('admin:declare:queue')
            ->setDescription('Declares a durable queue in RabbitMQ.')
            ->addArgument(
                'name',
                InputArgument::REQUIRED,
                'The name of the queue to declare.'
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int|null|void
     *
     * @throws InvalidArgumentException
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $queue = $input->getArgument('name');
        $this->driver->declareSimpleQueue($queue);
        $output->writeln(sprintf('<info>Declare queue <comment>%s</comment></info>', $queue));
    }
}


<?php

namespace Burrow\Driver;

use Burrow\Driver;
use PhpAmqpLib\Connection\AbstractConnection;
use PhpAmqpLib\Connection\AMQPLazyConnection;

class DriverFactory
{
    /**
     * Return the driver for your system
     *
     * It accepts a PECL connection, a PhpAmqpLib connection or an array following
     * the following schema :
     * [
     *     'host' => '<hostValue>',
     *     'port' => '<portValue>',
     *     'user' => '<userValue>',
     *     'pwd'  => '<pwdValue>'
     * ]
     *
     * If you provide an array, the PECL extension has precedence over the PhpAmqpLib.
     *
     * @param $connection
     *
     * @return Driver
     *
     * @throws \AMQPConnectionException
     * @throws \InvalidArgumentException
     */
    public static function getDriver($connection)
    {
        if (is_array($connection) &&
            isset($connection['host'], $connection['port'], $connection['user'], $connection['pwd'])
        ) {
            $connection = self::getConnectionFromArray($connection);
        }

        if ($connection instanceof AbstractConnection) {
            return new PhpAmqpLibDriver($connection);
        }

        if ($connection instanceof \AMQPConnection) {
            return new PeclAmqpDriver($connection);
        }

        throw new \InvalidArgumentException('You provided an unsupported connection');
    }

    /**
     * @param $connection
     *
     * @return mixed|\AMQPConnection|AMQPLazyConnection
     *
     * @throws \AMQPConnectionException
     */
    private static function getConnectionFromArray($connection)
    {
        if (static::peclExtensionIsAvailable()) {
            return static::getPeclConnection($connection);
        }

        return static::getPhpAmqpLibConnection($connection);
    }

    /**
     * @param string[] $connection
     *
     * @return \AMQPConnection
     *
     * @throws \AMQPConnectionException
     *
     * @codeCoverageIgnore
     */
    protected static function getPeclConnection(array $connection)
    {
        $amqpConnection = new \AMQPConnection();
        $amqpConnection->setHost($connection['host']);
        $amqpConnection->setPort($connection['port']);
        $amqpConnection->setLogin($connection['user']);
        $amqpConnection->setPassword($connection['pwd']);

        return $amqpConnection;
    }

    /**
     * @param string[] $connection
     *
     * @return AMQPLazyConnection
     *
     * @codeCoverageIgnore
     */
    protected static function getPhpAmqpLibConnection(array $connection)
    {
        return new AMQPLazyConnection(
            $connection['host'],
            $connection['port'],
            $connection['user'],
            $connection['pwd']
        );
    }

    /**
     * @return bool
     *
     * @codeCoverageIgnore
     */
    protected static function peclExtensionIsAvailable()
    {
        return class_exists(\AMQPConnection::class);
    }
}