diff options
Diffstat (limited to 'vendor/clue/redis-protocol')
36 files changed, 2013 insertions, 0 deletions
diff --git a/vendor/clue/redis-protocol/.travis.yml b/vendor/clue/redis-protocol/.travis.yml new file mode 100644 index 0000000..2af5cf5 --- /dev/null +++ b/vendor/clue/redis-protocol/.travis.yml @@ -0,0 +1,8 @@ +language: php +php: + - 5.4 + - 5.3 +before_script: + - composer install --dev --prefer-source --no-interaction +script: + - phpunit --coverage-text diff --git a/vendor/clue/redis-protocol/CHANGELOG.md b/vendor/clue/redis-protocol/CHANGELOG.md new file mode 100644 index 0000000..d037017 --- /dev/null +++ b/vendor/clue/redis-protocol/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog + +## 0.3.1 (2017-06-06) + +* Fix: Fix server-side parsing of legacy inline protocol when multiple requests are processed at once + (#12 by @kelunik and #13 by @clue) + +## 0.3.0 (2014-01-27) + +* Feature: Add dedicated and faster `RequestParser` that also support the old + inline request protocol. +* Feature: Message serialization can now be handled directly by the Serializer + again without having to construct the appropriate model first. +* BC break: The `Factory` now has two distinct methods to create parsers: + * `createResponseParser()` for a client-side library + * `createRequestParser()` for a server-side library / testing framework +* BC break: Simplified parser API, now `pushIncoming()` returns an array of all + parsed message models. +* BC break: The signature for getting a serialized message from a model was + changed and now requires a Serializer passed: + ```php +ModelInterface::getMessageSerialized($serializer) +``` +* Many, many performance improvements + +## 0.2.0 (2014-01-21) + +* Re-organize the whole API into dedicated + * `Parser` (protocol reader) and + * `Serializer` (protocol writer) sub-namespaces. (#4) + +* Use of the factory has now been unified: + + ```php + $factory = new Clue\Redis\Protocol\Factory(); + $parser = $factory->createParser(); + $serializer = $factory->createSerializer(); + ``` + +* Add a dedicated `Model` for each type of reply. Among others, this now allows + you to distinguish a single line `StatusReply` from a binary-safe `BulkReply`. (#2) + +* Fix parsing binary values and do not trip over trailing/leading whitespace. (#4) + +* Improve parser and serializer performance by up to 20%. (#4) + +## 0.1.0 (2013-09-10) + +* First tagged release + diff --git a/vendor/clue/redis-protocol/README.md b/vendor/clue/redis-protocol/README.md new file mode 100644 index 0000000..ea36a67 --- /dev/null +++ b/vendor/clue/redis-protocol/README.md @@ -0,0 +1,139 @@ +# clue/redis-protocol [](https://travis-ci.org/clue/php-redis-protocol) + +A streaming redis protocol parser and serializer written in PHP + +This parser and serializer implementation allows you to parse redis protocol +messages into native PHP values and vice-versa. This is usually needed by a +redis client implementation which also handles the connection socket. + +To re-iterate: This is *not* a redis client implementation. This is a protocol +implementation that is usually used by a redis client implementation. If you're +looking for an easy way to build your own client implementation, then this is +for you. If you merely want to connect to a redis server and issue some +commands, you're probably better off using one of the existing client +implementations. + +**Table of contents** + +* [Quickstart example](#quickstart-example) +* [Usage](#usage) + * [Factory](#factory) + * [Parser](#parser) + * [Model](#model) + * [Serializer](#serializer) +* [Install](#install) +* [License](#license) + +## Quickstart example + +```php +use Clue\Redis\Protocol; + +$factory = new Protocol\Factory(); +$parser = $factory->createResponseParser(); +$serializer = $factory->createSerializer(); + +$fp = fsockopen('tcp://localhost', 6379); +fwrite($fp, $serializer->getRequestMessage('SET', array('name', 'value'))); +fwrite($fp, $serializer->getRequestMessage('GET', array('name'))); + +// the commands are pipelined, so this may parse multiple responses +$models = $parser->pushIncoming(fread($fp, 4096)); + +$reply1 = array_shift($models); +$reply2 = array_shift($models); + +var_dump($reply1->getValueNative()); // string(2) "OK" +var_dump($reply2->getValueNative()); // string(5) "value" +``` + +## Usage + +### Factory + +The factory helps with instantiating the *right* parser and serializer. +Eventually the *best* available implementation will be chosen depending on your +installed extensions. You're also free to instantiate them directly, but this +will lock you down on a given implementation (which could be okay depending on +your use-case). + +### Parser + +The library includes a streaming redis protocol parser. As such, it can safely +parse redis protocol messages and work with an incomplete data stream. For this, +each included parser implements a single method +`ParserInterface::pushIncoming($chunk)`. + +* The `ResponseParser` is what most redis client implementation would want to + use in order to parse incoming response messages from a redis server instance. +* The `RequestParser` can be used to test messages coming from a redis client or + even to implement a redis server. +* The `MessageBuffer` decorates either of the available parsers and merely + offers some helper methods in order to work with single messages: + * `hasIncomingModel()` to check if there's a complete message in the pipeline + * `popIncomingModel()` to extract a complete message from the incoming queue. + +### Model + +Each message (response as well as request) is represented by a model +implementing the `ModelInterface` that has two methods: + +* `getValueNative()` returns the wrapped value. +* `getMessageSerialized($serializer)` returns the serialized protocol messages + that will be sent over the wire. + +These models are very lightweight and add little overhead. They help keeping the +code organized and also provide a means to distinguish a single line +`StatusReply` from a binary-safe `BulkReply`. + +The parser always returns models. Models can also be instantiated directly: + +```php +$model = new Model\IntegerReply(123); +var_dump($model->getValueNative()); // int(123) +var_dump($model->getMessageSerialized($serializer)); // string(6) ":123\r\n" +``` + +### Serializer + +The serializer is responsible for creating serialized messages and the +corresponing message models to be sent across the wire. + +```php +$message = $serializer->getRequestMessage('ping'); +var_dump($message); // string(14) "$1\r\n*4\r\nping\r\n" + +$message = $serializer->getRequestMessage('set', array('key', 'value')); +var_dump($message); // string(33) "$3\r\n*3\r\nset\r\n*3\r\nkey\r\n*5\r\nvalue\r\n" + +$model = $serializer->createRequestModel('get', array('key')); +var_dump($model->getCommand()); // string(3) "get" +var_dump($model->getArgs()); // array(1) { string(3) "key" } +var_dump($model->getValueNative()); // array(2) { string(3) "GET", string(3) "key" } + +$model = $serializer->createReplyModel(array('mixed', 12, array('value'))); +assert($model implement Model\MultiBulkReply); +``` + +## Install + +It's very unlikely you'll want to use this protocol parser standalone. +It should be added as a dependency to your redis client implementation instead. +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require clue/redis-protocol:^0.3.1 +``` + +More details and upgrade guides can be found in the [CHANGELOG](CHANGELOG.md). + +## License + +Its parser and serializer originally used to be based on +[jpd/redisent](https://github.com/jdp/redisent), which is released under the ISC +license, copyright (c) 2009-2012 Justin Poliey <justin@getglue.com>. + +Other than that, this library is MIT licensed. diff --git a/vendor/clue/redis-protocol/composer.json b/vendor/clue/redis-protocol/composer.json new file mode 100644 index 0000000..d99e2ee --- /dev/null +++ b/vendor/clue/redis-protocol/composer.json @@ -0,0 +1,19 @@ +{ + "name": "clue/redis-protocol", + "description": "A streaming redis wire protocol parser and serializer implementation in PHP", + "keywords": ["streaming", "redis", "protocol", "parser", "serializer"], + "homepage": "https://github.com/clue/php-redis-protocol", + "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "require": { + "php": ">=5.3" + }, + "autoload": { + "psr-0": { "Clue\\Redis\\Protocol": "src" } + } +} diff --git a/vendor/clue/redis-protocol/example/client.php b/vendor/clue/redis-protocol/example/client.php new file mode 100644 index 0000000..d1b4428 --- /dev/null +++ b/vendor/clue/redis-protocol/example/client.php @@ -0,0 +1,22 @@ +<?php + +require __DIR__ . '/../vendor/autoload.php'; + +use Clue\Redis\Protocol; + +$factory = new Protocol\Factory(); +$parser = $factory->createResponseParser(); +$serializer = $factory->createSerializer(); + +$fp = fsockopen('tcp://localhost', 6379); +fwrite($fp, $serializer->getRequestMessage('SET', array('name', 'value'))); +fwrite($fp, $serializer->getRequestMessage('GET', array('name'))); + +// the commands are pipelined, so this may parse multiple responses +$models = $parser->pushIncoming(fread($fp, 4096)); + +$reply1 = array_shift($models); +$reply2 = array_shift($models); + +var_dump($reply1->getValueNative()); // string(2) "OK" +var_dump($reply2->getValueNative()); // string(5) "value" diff --git a/vendor/clue/redis-protocol/example/perf.php b/vendor/clue/redis-protocol/example/perf.php new file mode 100644 index 0000000..c5ce6a7 --- /dev/null +++ b/vendor/clue/redis-protocol/example/perf.php @@ -0,0 +1,31 @@ +<?php + +use Clue\Redis\Protocol\ProtocolBuffer; +use Clue\Redis\Protocol\Factory; + +require __DIR__ . '/../vendor/autoload.php'; + +$factory = new Factory(); +$parser = $factory->createResponseParser(); +$serializer = $factory->createSerializer(); + +$n = isset($argv[1]) ? (int)$argv[1] : 10000; // number of dummy messages to parse +$cs = 4096; // pretend we can only read 7 bytes at once. more like 4096/8192 usually + +echo 'benchmarking ' . $n . ' messages (chunksize of ' . $cs .' bytes)' . PHP_EOL; + +$time = microtime(true); + +$stream = ''; +for ($i = 0; $i < $n; ++$i) { + $stream .= $serializer->getRequestMessage('set', array('var' . $i, 'value' . $i)); +} + +echo round(microtime(true) - $time, 3) . 's for serialization' . PHP_EOL; +$time = microtime(true); + +for ($i = 0, $l = strlen($stream); $i < $l; $i += $cs) { + $parser->pushIncoming(substr($stream, $i, $cs)); +} + +echo round(microtime(true) - $time, 3) . 's for parsing' . PHP_EOL; diff --git a/vendor/clue/redis-protocol/phpunit.xml.dist b/vendor/clue/redis-protocol/phpunit.xml.dist new file mode 100644 index 0000000..4f3de2a --- /dev/null +++ b/vendor/clue/redis-protocol/phpunit.xml.dist @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit bootstrap="tests/bootstrap.php" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" +> + <testsuites> + <testsuite name="Redis Protocol Test Suite"> + <directory>./tests/</directory> + </testsuite> + </testsuites> + <filter> + <whitelist> + <directory>./src/</directory> + </whitelist> + </filter> +</phpunit>
\ No newline at end of file diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php new file mode 100644 index 0000000..3997f04 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php @@ -0,0 +1,51 @@ +<?php + +namespace Clue\Redis\Protocol; + +use Clue\Redis\Protocol\Parser\ParserInterface; +use Clue\Redis\Protocol\Parser\ResponseParser; +use Clue\Redis\Protocol\Serializer\SerializerInterface; +use Clue\Redis\Protocol\Serializer\RecursiveSerializer; +use Clue\Redis\Protocol\Parser\RequestParser; + +/** + * Provides factory methods used to instantiate the best available protocol implementation + */ +class Factory +{ + /** + * instantiate the best available protocol response parser implementation + * + * This is the parser every redis client implementation should use in order + * to parse incoming response messages from a redis server. + * + * @return ParserInterface + */ + public function createResponseParser() + { + return new ResponseParser(); + } + + /** + * instantiate the best available protocol request parser implementation + * + * This is most useful for a redis server implementation which needs to + * process client requests. + * + * @return ParserInterface + */ + public function createRequestParser() + { + return new RequestParser(); + } + + /** + * instantiate the best available protocol serializer implementation + * + * @return SerializerInterface + */ + public function createSerializer() + { + return new RecursiveSerializer(); + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php new file mode 100644 index 0000000..e069fda --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php @@ -0,0 +1,34 @@ +<?php + +namespace Clue\Redis\Protocol\Model; + +use Clue\Redis\Protocol\Model\ModelInterface; +use Clue\Redis\Protocol\Serializer\SerializerInterface; + +class BulkReply implements ModelInterface +{ + private $value; + + /** + * create bulk reply (string reply) + * + * @param string|null $data + */ + public function __construct($value) + { + if ($value !== null) { + $value = (string)$value; + } + $this->value = $value; + } + + public function getValueNative() + { + return $this->value; + } + + public function getMessageSerialized(SerializerInterface $serializer) + { + return $serializer->getBulkMessage($this->value); + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php new file mode 100644 index 0000000..556e93b --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php @@ -0,0 +1,34 @@ +<?php + +namespace Clue\Redis\Protocol\Model; + +use Exception; +use Clue\Redis\Protocol\Serializer\SerializerInterface; + +/** + * + * @link http://redis.io/topics/protocol#status-reply + */ +class ErrorReply extends Exception implements ModelInterface +{ + /** + * create error status reply (single line error message) + * + * @param string|ErrorReplyException $message + * @return string + */ + public function __construct($message, $code = 0, $previous = null) + { + parent::__construct($message, $code, $previous); + } + + public function getValueNative() + { + return $this->getMessage(); + } + + public function getMessageSerialized(SerializerInterface $serializer) + { + return $serializer->getErrorMessage($this->getMessage()); + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php new file mode 100644 index 0000000..ba1ff05 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php @@ -0,0 +1,31 @@ +<?php + +namespace Clue\Redis\Protocol\Model; + +use Clue\Redis\Protocol\Model\ModelInterface; +use Clue\Redis\Protocol\Serializer\SerializerInterface; + +class IntegerReply implements ModelInterface +{ + private $value; + + /** + * create integer reply + * + * @param int $data + */ + public function __construct($value) + { + $this->value = (int)$value; + } + + public function getValueNative() + { + return $this->value; + } + + public function getMessageSerialized(SerializerInterface $serializer) + { + return $serializer->getIntegerMessage($this->value); + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php new file mode 100644 index 0000000..b97939e --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php @@ -0,0 +1,23 @@ +<?php + +namespace Clue\Redis\Protocol\Model; + +use Clue\Redis\Protocol\Serializer\SerializerInterface; + +interface ModelInterface +{ + /** + * Returns value of this model as a native representation for PHP + * + * @return mixed + */ + public function getValueNative(); + + /** + * Returns the serialized representation of this protocol message + * + * @param SerializerInterface $serializer; + * @return string + */ + public function getMessageSerialized(SerializerInterface $serializer); +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php new file mode 100644 index 0000000..7198dc6 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php @@ -0,0 +1,100 @@ +<?php + +namespace Clue\Redis\Protocol\Model; + +use InvalidArgumentException; +use UnexpectedValueException; +use Clue\Redis\Protocol\Serializer\SerializerInterface; + +class MultiBulkReply implements ModelInterface +{ + /** + * @var array|null + */ + private $data; + + /** + * create multi bulk reply (an array of other replies, usually bulk replies) + * + * @param array|null $data + * @throws InvalidArgumentException + */ + public function __construct(array $data = null) + { + $this->data = $data; + } + + public function getValueNative() + { + if ($this->data === null) { + return null; + } + + $ret = array(); + foreach ($this->data as $one) { + if ($one instanceof ModelInterface) { + $ret []= $one->getValueNative(); + } else { + $ret []= $one; + } + } + return $ret; + } + + public function getMessageSerialized(SerializerInterface $serializer) + { + return $serializer->getMultiBulkMessage($this->data); + } + + /** + * Checks whether this model represents a valid unified request protocol message + * + * The new unified protocol was introduced in Redis 1.2, but it became the + * standard way for talking with the Redis server in Redis 2.0. The unified + * request protocol is what Redis already uses in replies in order to send + * list of items to clients, and is called a Multi Bulk Reply. + * + * @return boolean + * @link http://redis.io/topics/protocol + */ + public function isRequest() + { + if (!$this->data) { + return false; + } + + foreach ($this->data as $one) { + if (!($one instanceof BulkReply) && !is_string($one)) { + return false; + } + } + + return true; + } + + public function getRequestModel() + { + if (!$this->data) { + throw new UnexpectedValueException('Null-multi-bulk message can not be represented as a request, must contain string/bulk values'); + } + + $command = null; + $args = array(); + + foreach ($this->data as $one) { + if ($one instanceof BulkReply) { + $one = $one->getValueNative(); + } elseif (!is_string($one)) { + throw new UnexpectedValueException('Message can not be represented as a request, must only contain string/bulk values'); + } + + if ($command === null) { + $command = $one; + } else { + $args []= $one; + } + } + + return new Request($command, $args); + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php new file mode 100644 index 0000000..f5881e9 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php @@ -0,0 +1,53 @@ +<?php + +namespace Clue\Redis\Protocol\Model; + +use Clue\Redis\Protocol\Model\ModelInterface; +use Clue\Redis\Protocol\Model\BulkReply; +use Clue\Redis\Protocol\Model\MultiBulkReply; +use Clue\Redis\Protocol\Serializer\SerializerInterface; + +class Request implements ModelInterface +{ + private $command; + private $args; + + public function __construct($command, array $args = array()) + { + $this->command = $command; + $this->args = $args; + } + + public function getCommand() + { + return $this->command; + } + + public function getArgs() + { + return $this->args; + } + + public function getReplyModel() + { + $models = array(new BulkReply($this->command)); + foreach ($this->args as $arg) { + $models []= new BulkReply($arg); + } + + return new MultiBulkReply($models); + } + + public function getValueNative() + { + $ret = $this->args; + array_unshift($ret, $this->command); + + return $ret; + } + + public function getMessageSerialized(SerializerInterface $serializer) + { + return $serializer->getRequestMessage($this->command, $this->args); + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php new file mode 100644 index 0000000..4ea2fcd --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php @@ -0,0 +1,34 @@ +<?php + +namespace Clue\Redis\Protocol\Model; + +use Clue\Redis\Protocol\Serializer\SerializerInterface; +/** + * + * @link http://redis.io/topics/protocol#status-reply + */ +class StatusReply implements ModelInterface +{ + private $message; + + /** + * create status reply (single line message) + * + * @param string|Status $message + * @return string + */ + public function __construct($message) + { + $this->message = $message; + } + + public function getValueNative() + { + return $this->message; + } + + public function getMessageSerialized(SerializerInterface $serializer) + { + return $serializer->getStatusMessage($this->message); + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php new file mode 100644 index 0000000..c1e3001 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php @@ -0,0 +1,40 @@ +<?php + +namespace Clue\Redis\Protocol\Parser; + +use UnderflowException; + +class MessageBuffer implements ParserInterface +{ + private $parser; + private $incomingQueue = array(); + + public function __construct(ParserInterface $parser) + { + $this->parser = $parser; + } + + public function popIncomingModel() + { + if (!$this->incomingQueue) { + throw new UnderflowException('Incoming message queue is empty'); + } + return array_shift($this->incomingQueue); + } + + public function hasIncomingModel() + { + return ($this->incomingQueue) ? true : false; + } + + public function pushIncoming($data) + { + $ret = $this->parser->pushIncoming($data); + + foreach ($ret as $one) { + $this->incomingQueue []= $one; + } + + return $ret; + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php new file mode 100644 index 0000000..e57c5bc --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php @@ -0,0 +1,10 @@ +<?php + +namespace Clue\Redis\Protocol\Parser; + +use UnexpectedValueException; + +class ParserException extends UnexpectedValueException +{ + +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php new file mode 100644 index 0000000..a322719 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php @@ -0,0 +1,28 @@ +<?php + +namespace Clue\Redis\Protocol\Parser; + +use Clue\Redis\Protocol\Model\ModelInterface; +use Clue\Redis\Protocol\Parser\ParserException; + +interface ParserInterface +{ + /** + * push a chunk of the redis protocol message into the buffer and parse + * + * You can push any number of bytes of a redis protocol message into the + * parser and it will try to parse messages from its data stream. So you can + * pass data directly from your socket stream and the parser will return the + * right amount of message model objects for you. + * + * If you pass an incomplete message, expect it to return an empty array. If + * your incomplete message is split to across multiple chunks, the parsed + * message model will be returned once the parser has sufficient data. + * + * @param string $dataChunk + * @return ModelInterface[] 0+ message models + * @throws ParserException if the message can not be parsed + * @see self::popIncomingModel() + */ + public function pushIncoming($dataChunk); +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php new file mode 100644 index 0000000..a47d137 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php @@ -0,0 +1,125 @@ +<?php + +namespace Clue\Redis\Protocol\Parser; + +use Clue\Redis\Protocol\Parser\ParserException; +use Clue\Redis\Protocol\Model\Request; + +class RequestParser implements ParserInterface +{ + const CRLF = "\r\n"; + + private $incomingBuffer = ''; + private $incomingOffset = 0; + + public function pushIncoming($dataChunk) + { + $this->incomingBuffer .= $dataChunk; + + $parsed = array(); + + do { + $saved = $this->incomingOffset; + $message = $this->readRequest(); + if ($message === null) { + // restore previous position for next parsing attempt + $this->incomingOffset = $saved; + break; + } + + if ($message !== false) { + $parsed []= $message; + } + } while($this->incomingBuffer !== ''); + + if ($this->incomingOffset !== 0) { + $this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset); + $this->incomingOffset = 0; + } + + return $parsed; + } + + /** + * try to parse request from incoming buffer + * + * @throws ParserException if the incoming buffer is invalid + * @return Request|null + */ + private function readRequest() + { + $crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset); + if ($crlf === false) { + return null; + } + + // line starts with a multi-bulk header "*" + if (isset($this->incomingBuffer[$this->incomingOffset]) && $this->incomingBuffer[$this->incomingOffset] === '*') { + $line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1); + $this->incomingOffset = $crlf + 2; + $count = (int)$line; + + if ($count <= 0) { + return false; + } + $command = null; + $args = array(); + for ($i = 0; $i < $count; ++$i) { + $sub = $this->readBulk(); + if ($sub === null) { + return null; + } + if ($command === null) { + $command = $sub; + } else { + $args []= $sub; + } + } + return new Request($command, $args); + } + + // parse an old inline request instead + $line = substr($this->incomingBuffer, $this->incomingOffset, $crlf - $this->incomingOffset); + $this->incomingOffset = $crlf + 2; + + $args = preg_split('/ +/', trim($line, ' ')); + $command = array_shift($args); + + if ($command === '') { + return false; + } + + return new Request($command, $args); + } + + private function readBulk() + { + $crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset); + if ($crlf === false) { + return null; + } + + // line has to start with a bulk header "$" + if (!isset($this->incomingBuffer[$this->incomingOffset]) || $this->incomingBuffer[$this->incomingOffset] !== '$') { + throw new ParserException('ERR Protocol error: expected \'$\', got \'' . substr($this->incomingBuffer, $this->incomingOffset, 1) . '\''); + } + + $line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1); + $this->incomingOffset = $crlf + 2; + $size = (int)$line; + + if ($size < 0) { + throw new ParserException('ERR Protocol error: invalid bulk length'); + } + + if (!isset($this->incomingBuffer[$this->incomingOffset + $size + 1])) { + // check enough bytes + crlf are buffered + return null; + } + + $ret = substr($this->incomingBuffer, $this->incomingOffset, $size); + $this->incomingOffset += $size + 2; + + return $ret; + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php new file mode 100644 index 0000000..19ac90c --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php @@ -0,0 +1,151 @@ +<?php + +namespace Clue\Redis\Protocol\Parser; + +use Clue\Redis\Protocol\Parser\ParserInterface; +use Clue\Redis\Protocol\Model\ModelInterface; +use Clue\Redis\Protocol\Model\BulkReply; +use Clue\Redis\Protocol\Model\ErrorReply; +use Clue\Redis\Protocol\Model\IntegerReply; +use Clue\Redis\Protocol\Model\MultiBulkReply; +use Clue\Redis\Protocol\Model\StatusReply; +use Clue\Redis\Protocol\Parser\ParserException; + +/** + * Simple recursive redis wire protocol parser + * + * Heavily influenced by blocking parser implementation from jpd/redisent. + * + * @link https://github.com/jdp/redisent + * @link http://redis.io/topics/protocol + */ +class ResponseParser implements ParserInterface +{ + const CRLF = "\r\n"; + + private $incomingBuffer = ''; + private $incomingOffset = 0; + + public function pushIncoming($dataChunk) + { + $this->incomingBuffer .= $dataChunk; + + return $this->tryParsingIncomingMessages(); + } + + private function tryParsingIncomingMessages() + { + $messages = array(); + + do { + $message = $this->readResponse(); + if ($message === null) { + // restore previous position for next parsing attempt + $this->incomingOffset = 0; + break; + } + + $messages []= $message; + + $this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset); + $this->incomingOffset = 0; + } while($this->incomingBuffer !== ''); + + return $messages; + } + + private function readLine() + { + $pos = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset); + + if ($pos === false) { + return null; + } + + $ret = (string)substr($this->incomingBuffer, $this->incomingOffset, $pos - $this->incomingOffset); + $this->incomingOffset = $pos + 2; + + return $ret; + } + + private function readLength($len) + { + $ret = substr($this->incomingBuffer, $this->incomingOffset, $len); + if (strlen($ret) !== $len) { + return null; + } + + $this->incomingOffset += $len; + + return $ret; + } + + /** + * try to parse response from incoming buffer + * + * ripped from jdp/redisent, with some minor modifications to read from + * the incoming buffer instead of issuing a blocking fread on a stream + * + * @throws ParserException if the incoming buffer is invalid + * @return ModelInterface|null + * @link https://github.com/jdp/redisent + */ + private function readResponse() + { + /* Parse the response based on the reply identifier */ + $reply = $this->readLine(); + if ($reply === null) { + return null; + } + switch (substr($reply, 0, 1)) { + /* Error reply */ + case '-': + $response = new ErrorReply(substr($reply, 1)); + break; + /* Inline reply */ + case '+': + $response = new StatusReply(substr($reply, 1)); + break; + /* Bulk reply */ + case '$': + $size = (int)substr($reply, 1); + if ($size === -1) { + return new BulkReply(null); + } + $data = $this->readLength($size); + if ($data === null) { + return null; + } + if ($this->readLength(2) === null) { /* discard crlf */ + return null; + } + $response = new BulkReply($data); + break; + /* Multi-bulk reply */ + case '*': + $count = (int)substr($reply, 1); + if ($count === -1) { + return new MultiBulkReply(null); + } + $response = array(); + for ($i = 0; $i < $count; $i++) { + $sub = $this->readResponse(); + if ($sub === null) { + return null; + } + $response []= $sub; + } + $response = new MultiBulkReply($response); + break; + /* Integer reply */ + case ':': + $response = new IntegerReply(substr($reply, 1)); + break; + default: + throw new ParserException('Invalid message can not be parsed: "' . $reply . '"'); + break; + } + /* Party on */ + return $response; + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php new file mode 100644 index 0000000..6e25125 --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php @@ -0,0 +1,111 @@ +<?php + +namespace Clue\Redis\Protocol\Serializer; + +use Clue\Redis\Protocol\Model\StatusReply; +use InvalidArgumentException; +use Exception; +use Clue\Redis\Protocol\Model\BulkReply; +use Clue\Redis\Protocol\Model\IntegerReply; +use Clue\Redis\Protocol\Model\ErrorReply; +use Clue\Redis\Protocol\Model\MultiBulkReply; +use Clue\Redis\Protocol\Model\ModelInterface; +use Clue\Redis\Protocol\Model\Request; + +class RecursiveSerializer implements SerializerInterface +{ + const CRLF = "\r\n"; + + public function getRequestMessage($command, array $args = array()) + { + $data = '*' . (count($args) + 1) . "\r\n$" . strlen($command) . "\r\n" . $command . "\r\n"; + foreach ($args as $arg) { + $data .= '$' . strlen($arg) . "\r\n" . $arg . "\r\n"; + } + return $data; + } + + public function createRequestModel($command, array $args = array()) + { + return new Request($command, $args); + } + + public function getReplyMessage($data) + { + if (is_string($data) || $data === null) { + return $this->getBulkMessage($data); + } else if (is_int($data) || is_float($data) || is_bool($data)) { + return $this->getIntegerMessage($data); + } else if ($data instanceof Exception) { + return $this->getErrorMessage($data->getMessage()); + } else if (is_array($data)) { + return $this->getMultiBulkMessage($data); + } else { + throw new InvalidArgumentException('Invalid data type passed for serialization'); + } + } + + public function createReplyModel($data) + { + if (is_string($data) || $data === null) { + return new BulkReply($data); + } else if (is_int($data) || is_float($data) || is_bool($data)) { + return new IntegerReply($data); + } else if ($data instanceof Exception) { + return new ErrorReply($data->getMessage()); + } else if (is_array($data)) { + $models = array(); + foreach ($data as $one) { + $models []= $this->createReplyModel($one); + } + return new MultiBulkReply($models); + } else { + throw new InvalidArgumentException('Invalid data type passed for serialization'); + } + } + + public function getBulkMessage($data) + { + if ($data === null) { + /* null bulk reply */ + return '$-1' . self::CRLF; + } + /* bulk reply */ + return '$' . strlen($data) . self::CRLF . $data . self::CRLF; + } + + public function getErrorMessage($data) + { + /* error status reply */ + return '-' . $data . self::CRLF; + } + + public function getIntegerMessage($data) + { + return ':' . (int)$data . self::CRLF; + } + + public function getMultiBulkMessage($data) + { + if ($data === null) { + /* null multi bulk reply */ + return '*-1' . self::CRLF; + } + /* multi bulk reply */ + $ret = '*' . count($data) . self::CRLF; + foreach ($data as $one) { + if ($one instanceof ModelInterface) { + $ret .= $one->getMessageSerialized($this); + } else { + $ret .= $this->getReplyMessage($one); + } + } + return $ret; + } + + public function getStatusMessage($data) + { + /* status reply */ + return '+' . $data . self::CRLF; + } +} diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php new file mode 100644 index 0000000..bb7cb3e --- /dev/null +++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php @@ -0,0 +1,83 @@ +<?php + +namespace Clue\Redis\Protocol\Serializer; + +use Clue\Redis\Protocol\Model\ErrorReplyException; +use Clue\Redis\Protocol\Model\ModelInterface; +use Clue\Redis\Protocol\Model\MultiBulkReply; + +interface SerializerInterface +{ + /** + * create a serialized unified request protocol message + * + * This is the *one* method most redis client libraries will likely want to + * use in order to send a serialized message (a request) over the* wire to + * your redis server instance. + * + * This method should be used in favor of constructing a request model and + * then serializing it. While its effect might be equivalent, this method + * is likely to (i.e. it /could/) provide a faster implementation. + * + * @param string $command + * @param array $args + * @return string + * @see self::createRequestMessage() + */ + public function getRequestMessage($command, array $args = array()); + + /** + * create a unified request protocol message model + * + * @param string $command + * @param array $args + * @return MultiBulkReply + */ + public function createRequestModel($command, array $args = array()); + + /** + * create a serialized unified protocol reply message + * + * This is most useful for a redis server implementation which needs to + * process client requests and send resulting reply messages. + * + * This method does its best to guess to right reply type and then returns + * a serialized version of the message. It follows the "redis to lua + * conversion table" (see link) which means most basic types can be mapped + * as is. + * + * This method should be used in favor of constructing a reply model and + * then serializing it. While its effect might be equivalent, this method + * is likely to (i.e. it /could/) provide a faster implementation. + * + * Note however, you may still want to explicitly create a nested reply + * model hierarchy if you need more control over the serialized message. For + * instance, a null value will always be returned as a Null-Bulk-Reply, so + * there's no way to express a Null-Multi-Bulk-Reply, unless you construct + * it explicitly. + * + * @param mixed $data + * @return string + * @see self::createReplyModel() + * @link http://redis.io/commands/eval + */ + public function getReplyMessage($data); + + /** + * create response message by determining datatype from given argument + * + * @param mixed $data + * @return ModelInterface + */ + public function createReplyModel($data); + + public function getBulkMessage($data); + + public function getErrorMessage($data); + + public function getIntegerMessage($data); + + public function getMultiBulkMessage($data); + + public function getStatusMessage($data); +} diff --git a/vendor/clue/redis-protocol/tests/FactoryTest.php b/vendor/clue/redis-protocol/tests/FactoryTest.php new file mode 100644 index 0000000..b669223 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/FactoryTest.php @@ -0,0 +1,34 @@ +<?php + +use Clue\Redis\Protocol\Factory; + +class FactoryTest extends TestCase +{ + private $factory; + + public function setUp() + { + $this->factory = new Factory(); + } + + public function testCreateResponseParser() + { + $parser = $this->factory->createResponseParser(); + + $this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $parser); + } + + public function testCreateRequestParser() + { + $parser = $this->factory->createRequestParser(); + + $this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $parser); + } + + public function testCreateSerializer() + { + $serializer = $this->factory->createSerializer(); + + $this->assertInstanceOf('Clue\Redis\Protocol\Serializer\SerializerInterface', $serializer); + } +} diff --git a/vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php b/vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php new file mode 100644 index 0000000..1358533 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php @@ -0,0 +1,22 @@ +<?php + +use Clue\Redis\Protocol\Serializer\RecursiveSerializer; + +abstract class AbstractModelTest extends TestCase +{ + protected $serializer; + + abstract protected function createModel($value); + + public function setUp() + { + $this->serializer = new RecursiveSerializer(); + } + + public function testConstructor() + { + $model = $this->createModel(null); + + $this->assertInstanceOf('Clue\Redis\Protocol\Model\ModelInterface', $model); + } +} diff --git a/vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php b/vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php new file mode 100644 index 0000000..78ed04c --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php @@ -0,0 +1,43 @@ +<?php + +use Clue\Redis\Protocol\Model\BulkReply; + +class BulkReplyTest extends AbstractModelTest +{ + protected function createModel($value) + { + return new BulkReply($value); + } + + public function testStringReply() + { + $model = $this->createModel('test'); + + $this->assertEquals('test', $model->getValueNative()); + $this->assertEquals("$4\r\ntest\r\n", $model->getMessageSerialized($this->serializer)); + } + + public function testEmptyStringReply() + { + $model = $this->createModel(''); + + $this->assertEquals('', $model->getValueNative()); + $this->assertEquals("$0\r\n\r\n", $model->getMessageSerialized($this->serializer)); + } + + public function testIntegerCast() + { + $model = $this->createModel(123); + + $this->assertEquals('123', $model->getValueNative()); + $this->assertEquals("$3\r\n123\r\n", $model->getMessageSerialized($this->serializer)); + } + + public function testNullBulkReply() + { + $model = $this->createModel(null); + + $this->assertEquals(null, $model->getValueNative()); + $this->assertEquals("$-1\r\n", $model->getMessageSerialized($this->serializer)); + } +} diff --git a/vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php b/vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php new file mode 100644 index 0000000..2585b08 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php @@ -0,0 +1,19 @@ +<?php + +use Clue\Redis\Protocol\Model\ErrorReply; + +class ErrorReplyTest extends AbstractModelTest +{ + protected function createModel($value) + { + return new ErrorReply($value); + } + + public function testError() + { + $model = $this->createModel('ERR error'); + + $this->assertEquals('ERR error', $model->getValueNative()); + $this->assertEquals("-ERR error\r\n", $model->getMessageSerialized($this->serializer)); + } +} diff --git a/vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php b/vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php new file mode 100644 index 0000000..f6a2e10 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php @@ -0,0 +1,40 @@ +<?php + +use Clue\Redis\Protocol\Model\IntegerReply; + +class IntegerReplyTest extends AbstractModelTest +{ + protected function createModel($value) + { + return new IntegerReply($value); + } + + public function testIntegerReply() + { + $model = $this->createModel(0); + $this->assertEquals(0, $model->getValueNative()); + $this->assertEquals(":0\r\n", $model->getMessageSerialized($this->serializer)); + } + + public function testFloatCasted() + { + $model = $this->createModel(-12.99); + $this->assertEquals(-12, $model->getValueNative()); + $this->assertEquals(":-12\r\n", $model->getMessageSerialized($this->serializer)); + + $model = $this->createModel(14.99); + $this->assertEquals(14, $model->getValueNative()); + $this->assertEquals(":14\r\n", $model->getMessageSerialized($this->serializer)); + } + + public function testBooleanCasted() + { + $model = $this->createModel(true); + $this->assertEquals(1, $model->getValueNative()); + $this->assertEquals(":1\r\n", $model->getMessageSerialized($this->serializer)); + + $model = $this->createModel(false); + $this->assertEquals(0, $model->getValueNative()); + $this->assertEquals(":0\r\n", $model->getMessageSerialized($this->serializer)); + } +} diff --git a/vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php b/vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php new file mode 100644 index 0000000..04dd389 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php @@ -0,0 +1,115 @@ +<?php + +use Clue\Redis\Protocol\Model\MultiBulkReply; +use Clue\Redis\Protocol\Model\BulkReply; +use Clue\Redis\Protocol\Model\IntegerReply; + +class MultiBulkReplyTest extends AbstractModelTest +{ + protected function createModel($value) + { + return new MultiBulkReply($value); + } + + public function testEmptyArray() + { + $model = $this->createModel(array()); + + $this->assertEquals(array(), $model->getValueNative()); + $this->assertEquals("*0\r\n", $model->getMessageSerialized($this->serializer)); + + $this->assertFalse($model->isRequest()); + } + + public function testNullMultiBulkReply() + { + $model = $this->createModel(null); + + $this->assertEquals(null, $model->getValueNative()); + $this->assertEquals("*-1\r\n", $model->getMessageSerialized($this->serializer)); + + $this->assertFalse($model->isRequest()); + + return $model; + } + + /** + * @param MultiBulkReply $model + * @depends testNullMultiBulkReply + * @expectedException UnexpectedValueException + */ + public function testNullMultiBulkReplyIsNotARequest(MultiBulkReply $model) + { + $model->getRequestModel(); + } + + public function testSingleBulkEnclosed() + { + $model = $this->createModel(array(new BulkReply('test'))); + + $this->assertEquals(array('test'), $model->getValueNative()); + $this->assertEquals("*1\r\n$4\r\ntest\r\n", $model->getMessageSerialized($this->serializer)); + + $this->assertTrue($model->isRequest()); + + // this can be represented by a request + $request = $model->getRequestModel(); + $this->assertEquals($model->getValueNative(), $request->getValueNative()); + + // representing the request as a reply should return our original instance + $reply = $request->getReplyModel(); + $this->assertEquals($model, $reply); + + return $model; + } + + /** + * @depends testSingleBulkEnclosed + */ + public function testStringEnclosedEqualsSingleBulk(MultiBulkReply $expected) + { + $model = $this->createModel(array('test')); + + $this->assertEquals($expected->getValueNative(), $model->getValueNative()); + $this->assertEquals($expected->getMessageSerialized($this->serializer), $model->getMessageSerialized($this->serializer)); + + $this->assertTrue($model->isRequest()); + } + + public function testMixedReply() + { + $model = $this->createModel(array(new BulkReply('test'), new IntegerReply(123))); + + $this->assertEquals(array('test', 123), $model->getValueNative()); + $this->assertEquals("*2\r\n$4\r\ntest\r\n:123\r\n", $model->getMessageSerialized($this->serializer)); + + $this->assertFalse($model->isRequest()); + + return $model; + } + + /** + * @param MultiBulkReply $model + * @depends testMixedReply + * @expectedException UnexpectedValueException + */ + public function testMixedReplyIsNotARequest(MultiBulkReply $model) + { + $model->getRequestModel(); + } + + public function testMultiStrings() + { + $model = $this->createModel(array('SET', 'a', 'b')); + + $this->assertEquals(array('SET', 'a', 'b'), $model->getValueNative()); + + $this->assertTrue($model->isRequest()); + + $request = $model->getRequestModel(); + + // this can be represented by a request + $request = $model->getRequestModel(); + $this->assertEquals($model->getValueNative(), $request->getValueNative()); + } +} diff --git a/vendor/clue/redis-protocol/tests/Model/RequestTest.php b/vendor/clue/redis-protocol/tests/Model/RequestTest.php new file mode 100644 index 0000000..6719481 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Model/RequestTest.php @@ -0,0 +1,37 @@ +<?php + +use Clue\Redis\Protocol\Model\Request; + +class RequestTest extends AbstractModelTest +{ + protected function createModel($value) + { + return new Request('QUIT'); + } + + public function testPing() + { + $model = new Request('PING'); + + $this->assertEquals('PING', $model->getCommand()); + $this->assertEquals(array(), $model->getArgs()); + $this->assertEquals(array('PING'), $model->getValueNative()); + $this->assertEquals("*1\r\n$4\r\nPING\r\n", $model->getMessageSerialized($this->serializer)); + + $reply = $model->getReplyModel(); + $this->assertEquals($model->getValueNative(), $reply->getValueNative()); + } + + public function testGet() + { + $model = new Request('GET', array('a')); + + $this->assertEquals('GET', $model->getCommand()); + $this->assertEquals(array('a'), $model->getArgs()); + $this->assertEquals(array('GET', 'a'), $model->getValueNative()); + $this->assertEquals("*2\r\n$3\r\nGET\r\n$1\r\na\r\n", $model->getMessageSerialized($this->serializer)); + + $reply = $model->getReplyModel(); + $this->assertEquals($model->getValueNative(), $reply->getValueNative()); + } +} diff --git a/vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php b/vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php new file mode 100644 index 0000000..8debd85 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php @@ -0,0 +1,19 @@ +<?php + +use Clue\Redis\Protocol\Model\StatusReply; + +class StatusReplyTest extends AbstractModelTest +{ + protected function createModel($value) + { + return new StatusReply($value); + } + + public function testStatusOk() + { + $model = $this->createModel('OK'); + + $this->assertEquals('OK', $model->getValueNative()); + $this->assertEquals("+OK\r\n", $model->getMessageSerialized($this->serializer)); + } +} diff --git a/vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php b/vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php new file mode 100644 index 0000000..3d45c20 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php @@ -0,0 +1,67 @@ +<?php + +use Clue\Redis\Protocol\Parser\ParserInterface; +use Clue\Redis\Protocol\Parser\MessageBuffer; + +abstract class AbstractParserTest extends TestCase +{ + /** + * + * @var ParserInterface + */ + protected $parser; + + abstract protected function createParser(); + + public function setUp() + { + $this->parser = $this->createParser(); + $this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $this->parser); + } + + public function testParsingMessageOne() + { + // getRequestMessage('test') + $message = $expected = "*1\r\n$4\r\ntest\r\n"; + + $models = $this->parser->pushIncoming($message); + $this->assertCount(1, $models); + + $model = reset($models); + $this->assertEquals(array('test'), $model->getValueNative()); + } + + public function testParsingMessageTwoPartial() + { + // getRequestMessage('test', array('second')) + $message = "*2\r\n$4\r\ntest\r\n$6\r\nsecond\r\n"; + + $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 0, 1))); + $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 1, 1))); + $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 2, 1))); + $this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 3, 10))); + $this->assertCount(1, $models = $this->parser->pushIncoming(substr($message, 13))); + + $model = reset($models); + + $this->assertEquals(array('test', 'second'), $model->getValueNative()); + } + + public function testMessageBuffer() + { + $buffer = new MessageBuffer($this->parser); + + $this->assertFalse($buffer->hasIncomingModel()); + + $data = "*1\r\n$4\r\ntest\r\n"; + $this->assertCount(1, $models = $buffer->pushIncoming($data)); + $this->assertTrue($buffer->hasIncomingModel()); + + $expected = reset($models); + $this->assertSame($expected, $buffer->popIncomingModel()); + $this->assertFalse($buffer->hasIncomingModel()); + + $this->setExpectedException('UnderflowException'); + $buffer->popIncomingModel(); + } +} diff --git a/vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php b/vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php new file mode 100644 index 0000000..645b673 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php @@ -0,0 +1,132 @@ +<?php + +use Clue\Redis\Protocol\Parser\RequestParser; + +class RequestParserTest extends AbstractParserTest +{ + protected function createParser() + { + return new RequestParser(); + } + + public function testSimplePingRequest() + { + $message = "*1\r\n$4\r\nping\r\n"; + + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $request = reset($models); + + $this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request); + $this->assertEquals('ping', $request->getCommand()); + $this->assertEquals(array(), $request->getArgs()); + + return $request; + } + + /** + * + * @param Request $expected + * @depends testSimplePingRequest + */ + public function testInlinePingRequest($expected) + { + $message = "ping\r\n"; + + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $request = reset($models); + + $this->assertEquals($expected, $request); + } + + public function testInlineWhitespaceIsIgnored() + { + $message = " set name value \r\n"; + + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $request = reset($models); + + $this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request); + $this->assertEquals('set', $request->getCommand()); + $this->assertEquals(array('name', 'value'), $request->getArgs()); + } + + public function testIncompleteSuccessive() + { + $this->assertEquals(array(), $this->parser->pushIncoming("*1\r\n")); + $this->assertEquals(array(), $this->parser->pushIncoming("$4\r\n")); + $this->assertEquals(array(), $this->parser->pushIncoming("test")); + $this->assertCount(1, $models = $this->parser->pushIncoming("\r\n")); + } + + public function testNullMultiBulkRequestIsIgnored() + { + $message = "*-1\r\n"; + + $this->assertEquals(array(), $this->parser->pushIncoming($message)); + } + + public function testEmptyMultiBulkRequestIsIgnored() + { + $message = "*0\r\n"; + + $this->assertEquals(array(), $this->parser->pushIncoming($message)); + } + + public function testEmptyInlineIsIgnored() + { + $message = "\r\n"; + + $this->assertEquals(array(), $this->parser->pushIncoming($message)); + } + + public function testInlineParsesMultipleRequestsAtOnce() + { + $message = "hello\r\n\world\r\ntest\r\n"; + + $this->assertCount(3, $models = $this->parser->pushIncoming($message)); + } + + + public function testEmptyInlineAroundInlineIsIgnored() + { + $message = "\r\n\r\n" . "ping\r\n\r\n"; + + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $request = reset($models); + + $this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request); + $this->assertEquals('ping', $request->getCommand()); + $this->assertEquals(array(), $request->getArgs()); + } + + public function testWhitespaceInlineIsIgnored() + { + $message = " \r\n"; + + $this->assertEquals(array(), $this->parser->pushIncoming($message)); + } + + /** + * @expectedException Clue\Redis\Protocol\Parser\ParserException + */ + public function testInvalidMultiBulkMustContainBulk() + { + $message = "*1\r\n:123\r\n"; + + $this->parser->pushIncoming($message); + } + + /** + * @expectedException Clue\Redis\Protocol\Parser\ParserException + */ + public function testInvalidBulkLength() + { + $message = "*1\r\n$-1\r\n"; + + $this->parser->pushIncoming($message); + } +} diff --git a/vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php b/vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php new file mode 100644 index 0000000..762c8bd --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php @@ -0,0 +1,130 @@ +<?php + +use Clue\Redis\Protocol\Parser\ResponseParser; + +class RecursiveParserTest extends AbstractParserTest +{ + protected function createParser() + { + return new ResponseParser(); + } + + public function testPartialIncompleteBulkReply() + { + $this->assertEquals(array(), $this->parser->pushIncoming("$20\r\nincompl")); + } + + public function testParsingStatusReplies() + { + // C: PING + $message = "+PONG\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals('PONG', $data); + + // C: SET key value + $message = "+OK\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals('OK', $data); + } + + public function testParsingErrorReply() + { + $message = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"; + + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + $exception = reset($models); + + $this->assertInstanceOf('Exception', $exception); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\ErrorReply', $exception); + $this->assertEquals('WRONGTYPE Operation against a key holding the wrong kind of value', $exception->getMessage()); + } + + public function testParsingIntegerReply() + { + // C: INCR mykey + $message = ":1\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals(1, $data); + } + + public function testParsingBulkReply() + { + // C: GET mykey + $message = "$6\r\nfoobar\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals("foobar", $data); + } + + public function testParsingNullBulkReply() + { + // C: GET nonexistingkey + $message = "$-1\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals(null, $data); + } + + public function testParsingEmptyMultiBulkReply() + { + // C: LRANGE nokey 0 1 + $message = "*0\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals(array(), $data); + } + + public function testParsingNullMultiBulkReply() + { + // C: BLPOP key 1 + $message = "*-1\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals(null, $data); + } + + public function testParsingMultiBulkReplyWithMixedElements() + { + $message = "*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$6\r\nfoobar\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals(array(1, 2, 3, 4, 'foobar'), $data); + } + + public function testParsingMultiBulkReplyWithIncompletePush() + { + $this->assertCount(0, $this->parser->pushIncoming("*5\r\n:1\r\n:2\r")); + $this->assertCount(1, $models = $this->parser->pushIncoming("\n:3\r\n:4\r\n$6\r\nfoobar\r\n")); + + $data = reset($models)->getValueNative(); + $this->assertEquals(array(1, 2, 3, 4, 'foobar'), $data); + } + + public function testParsingMultiBulkReplyWithNullElement() + { + $message = "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n"; + $this->assertCount(1, $models = $this->parser->pushIncoming($message)); + + $data = reset($models)->getValueNative(); + $this->assertEquals(array('foo', null, 'bar'), $data); + } + + /** + * @expectedException Clue\Redis\Protocol\Parser\ParserException + */ + public function testParseError() + { + $this->parser->pushIncoming("invalid string\r\n"); + } +} diff --git a/vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php b/vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php new file mode 100644 index 0000000..ba2200a --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php @@ -0,0 +1,141 @@ +<?php + +use Clue\Redis\Protocol\Serializer\SerializerInterface; +use Clue\Redis\Protocol\Model\Status; +use Clue\Redis\Protocol\Model\ErrorReplyException; +//use Exception; + +abstract class AbstractSerializerTest extends TestCase +{ + /** + * @return SerializerInterface + */ + abstract protected function createSerializer(); + + public function setUp() + { + $this->serializer = $this->createSerializer(); + } + + public function testIntegerReply() + { + $model = $this->serializer->createReplyModel(0); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model); + $this->assertEquals(0, $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(0)); + } + + public function testFloatCastIntegerReply() + { + $model = $this->serializer->createReplyModel(-12.99); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model); + $this->assertEquals(-12, $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(-12.99)); + + $model = $this->serializer->createReplyModel(14.99); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model); + $this->assertEquals(14, $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(14.99)); + } + + public function testBooleanCastIntegerReply() + { + $model = $this->serializer->createReplyModel(true); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model); + $this->assertEquals(1, $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(true)); + + $model = $this->serializer->createReplyModel(false); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model); + $this->assertEquals(0, $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(false)); + } + + public function testStringReply() + { + $model = $this->serializer->createReplyModel('test'); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\BulkReply', $model); + $this->assertEquals('test', $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage('test')); + } + + public function testNullCastNullBulkReply() + { + $model = $this->serializer->createReplyModel(null); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\BulkReply', $model); + $this->assertEquals(null, $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(null)); + } + + public function testEmptyArrayMultiBulkReply() + { + $model = $this->serializer->createReplyModel(array()); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\MultiBulkReply', $model); + $this->assertEquals(array(), $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(array())); + } + + public function testArrayMultiBulkReply() + { + $model = $this->serializer->createReplyModel(array('test', 123)); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\MultiBulkReply', $model); + $this->assertEquals(array('test', 123), $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(array('test', 123))); + } + + public function testErrorReply() + { + $model = $this->serializer->createReplyModel(new Exception('ERR failure')); + $this->assertInstanceOf('Clue\Redis\Protocol\Model\ErrorReply', $model); + $this->assertEquals('ERR failure', $model->getValueNative()); + $this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(new Exception('ERR failure'))); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidArgument() + { + $this->serializer->createReplyModel((object)array()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidReplyData() + { + $this->serializer->getReplyMessage((object)array()); + } + + /** + * + * @param array $data + * @dataProvider provideRequestMessage + */ + public function testRequestMessage($command, $args) + { + // the model is already unit-tested, so just compare against its message + $model = $this->serializer->createRequestModel($command, $args); + + $message = $this->serializer->getRequestMessage($command, $args); + + $this->assertEquals($model->getMessageSerialized($this->serializer), $message); + } + + public function provideRequestMessage() + { + return array( + array('PING', array()), + array('GET', array('a')), + array('SET', array('a', 'b')), + array('SET', array('empty', '')) + ); + } + +// public function testBenchCreateRequest() +// { +// for ($i = 0; $i < 100000; ++$i) { +// $this->serializer->createReplyModel(array('a', 'b', 'c')); +// } +// } +} diff --git a/vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php b/vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php new file mode 100644 index 0000000..fe62ac5 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php @@ -0,0 +1,11 @@ +<?php + +use Clue\Redis\Protocol\Serializer\RecursiveSerializer; + +class RecursiveSerializerTest extends AbstractSerializerTest +{ + protected function createSerializer() + { + return new RecursiveSerializer(); + } +} diff --git a/vendor/clue/redis-protocol/tests/bootstrap.php b/vendor/clue/redis-protocol/tests/bootstrap.php new file mode 100644 index 0000000..0b2ea18 --- /dev/null +++ b/vendor/clue/redis-protocol/tests/bootstrap.php @@ -0,0 +1,7 @@ +<?php + +(include_once __DIR__ . '/../vendor/autoload.php') OR die(PHP_EOL . 'ERROR: composer autoloader not found, run "composer install" or see README for instructions' . PHP_EOL); + +class TestCase extends PHPUnit_Framework_TestCase +{ +} |