tipo: modificación del cpanel y gitignore de la carpeta vendor

This commit is contained in:
2026-07-07 11:35:09 -06:00
parent a0c91dc567
commit 676ef0d506
8510 changed files with 1132803 additions and 4 deletions
+2
View File
@@ -0,0 +1,2 @@
github: clue
custom: https://clue.engineering/support
+69
View File
@@ -0,0 +1,69 @@
# Changelog
## 0.3.2 (2024-08-07)
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable types.
(#19 by @clue)
* Update project structure, homepage and examples.
Add `.gitattributes` to exclude dev files from exports.
(#16, #20, #21 and #22 by @clue)
* Update test suite to use GitHub actions for continuous integration (CI),
run tests on all PHP versions up to PHP 8.3 and ensure 100% code coverage.
(#15 by @SimonFrings and #17 and #18 by @clue)
## 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
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013 Christian Lück
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+189
View File
@@ -0,0 +1,189 @@
# clue/redis-protocol
[![CI status](https://github.com/clue/redis-protocol/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/redis-protocol/actions)
[![code coverage](https://img.shields.io/badge/code%20coverage-100%25-success)](#tests)
[![installs on Packagist](https://img.shields.io/packagist/dt/clue/redis-protocol?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/redis-protocol)
A streaming Redis protocol (RESP) parser and serializer written in pure 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**
* [Support us](#support-us)
* [Quickstart example](#quickstart-example)
* [Usage](#usage)
* [Factory](#factory)
* [Parser](#parser)
* [Model](#model)
* [Serializer](#serializer)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## Support us
We invest a lot of time developing, maintaining and updating our awesome
open-source projects. You can help us sustain this high-quality of our work by
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
for details.
Let's take these projects to the next level together! 🚀
## Quickstart example
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$factory = new Clue\Redis\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"
```
See also the [examples](examples/).
## 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.2
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 8+.
It's *highly recommended to use the latest supported PHP version* for this project.
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):
```bash
composer install
```
To run the test suite, go to the project root and run:
```bash
vendor/bin/phpunit
```
The test suite is set up to always ensure 100% code coverage across all
supported environments. If you have the Xdebug extension installed, you can also
generate a code coverage report locally like this:
```bash
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text
```
## 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 project is released under the permissive [MIT license](LICENSE).
> Did you know that I offer custom development services and issuing invoices for
sponsorships of releases and for contributions? Contact me (@clue) for details.
+29
View File
@@ -0,0 +1,29 @@
{
"name": "clue/redis-protocol",
"description": "A streaming Redis protocol (RESP) parser and serializer written in pure PHP.",
"keywords": ["streaming", "redis", "protocol", "resp", "parser", "serializer"],
"homepage": "https://github.com/clue/redis-protocol",
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"email": "christian@lueck.tv"
}
],
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"autoload": {
"psr-4": {
"Clue\\Redis\\Protocol\\": "src/"
}
} ,
"autoload-dev": {
"psr-4": {
"Clue\\Tests\\Redis\\Protocol\\": "tests/"
}
}
}
+51
View File
@@ -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();
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace Clue\Redis\Protocol\Model;
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);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Clue\Redis\Protocol\Model;
use Clue\Redis\Protocol\Serializer\SerializerInterface;
use Exception;
/**
*
* @link http://redis.io/topics/protocol#status-reply
*/
class ErrorReply extends Exception implements ModelInterface
{
/**
* create error status reply (single line error message)
*
* @param string $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());
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Clue\Redis\Protocol\Model;
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);
}
}
+23
View File
@@ -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);
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Clue\Redis\Protocol\Model;
use Clue\Redis\Protocol\Serializer\SerializerInterface;
use InvalidArgumentException;
use UnexpectedValueException;
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($data = null)
{
if ($data !== null && !is_array($data)) { // manual type check to support legacy PHP < 7.1
throw new InvalidArgumentException('Argument #1 ($data) expected array|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);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Clue\Redis\Protocol\Model;
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);
}
}
+34
View File
@@ -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 $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);
}
}
+40
View File
@@ -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;
}
}
@@ -0,0 +1,10 @@
<?php
namespace Clue\Redis\Protocol\Parser;
use UnexpectedValueException;
class ParserException extends UnexpectedValueException
{
}
@@ -0,0 +1,25 @@
<?php
namespace Clue\Redis\Protocol\Parser;
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 \Clue\Redis\Protocol\Model\ModelInterface[] 0+ message models
* @throws \Clue\Redis\Protocol\Parser\ParserException if the message can not be parsed
* @see self::popIncomingModel()
*/
public function pushIncoming($dataChunk);
}
+124
View File
@@ -0,0 +1,124 @@
<?php
namespace Clue\Redis\Protocol\Parser;
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;
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
namespace Clue\Redis\Protocol\Parser;
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;
/**
* 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 . '"');
}
/* Party on */
return $response;
}
}
@@ -0,0 +1,110 @@
<?php
namespace Clue\Redis\Protocol\Serializer;
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\ModelInterface;
use Clue\Redis\Protocol\Model\MultiBulkReply;
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;
}
}
@@ -0,0 +1,82 @@
<?php
namespace Clue\Redis\Protocol\Serializer;
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);
}