From 7daa2f681efb9ec8226c431b4508b0269bcefdbf Mon Sep 17 00:00:00 2001 From: MacFJA Date: Wed, 19 Oct 2022 22:32:10 +0200 Subject: [PATCH] Add client for ReactPHP Redis --- CHANGELOG.md | 1 + README.md | 4 + composer.json | 2 + src/Redis/Client/AbstractClient.php | 12 +++ src/Redis/Client/AmpRedisClient.php | 12 +-- src/Redis/Client/CheprasovRedisClient.php | 7 -- src/Redis/Client/ClientFacade.php | 5 +- src/Redis/Client/CredisClient.php | 7 -- src/Redis/Client/PhpiredisClient.php | 7 -- src/Redis/Client/PredisClient.php | 7 -- src/Redis/Client/ReactRedisClient.php | 102 ++++++++++++++++++++++ src/Redis/Client/RedisentClient.php | 7 -- src/Redis/Client/TinyRedisClient.php | 7 -- tests/Redis/Client/ClientFacadeTest.php | 1 + tests/integration/DockerTest.php | 31 ++++--- 15 files changed, 146 insertions(+), 66 deletions(-) create mode 100644 src/Redis/Client/ReactRedisClient.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf22d6..bbdd66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add client for ReactPHP Redis - Path validation for JSON fields - Helper function get current RedisJSON version + (abstraction) - (dev) More unit tests diff --git a/README.md b/README.md index 38d8ac0..39d8597 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ This lib can use several connector for Redis: - [Rediska](https://github.com/Shumkov/Rediska) - Pure PHP implementation - [Redisent](https://github.com/jdp/redisent) - Pure PHP implementation - [TinyRedis](https://github.com/ptrofimov/tinyredisclient) - Pure PHP implementation + - [RecatPHP Redis](https://github.com/clue/reactphp-redis) - Pure PHP Async implementation You can pick the connector depending on your need. @@ -56,6 +57,9 @@ $client = $clientFacade->getClient(new \TinyRedisClient(/* ... */)); // With Credis $client = $clientFacade->getClient(new \Credis_Client(/* ... */)); + +// With ReactPHP Redis +$client = $clientFacade->getClient(new (new \Clue\React\Redis\Factory())->createClient(/* ... */)); ``` You can add your own implementation, all you need is to implement the interface `\MacFJA\RediSearch\Redis\Client` and add it to the client facace with: diff --git a/composer.json b/composer.json index 03926c5..a11f43a 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "ext-mbstring": "*", "amphp/redis": "^1.0", "cheprasov/php-redis-client": "^1.10", + "clue/redis-react": "^2.6", "colinmollenhour/credis": "^1.12", "enlightn/security-checker": "^1.9", "ergebnis/composer-normalize": "^2.13", @@ -42,6 +43,7 @@ "phpunit/phpunit": "^8.5 || ^9.3", "predis/predis": "^1.1 || ^2.0", "ptrofimov/tinyredisclient": "^1.1", + "react/async": "^3.0", "redisent/redisent": "dev-master", "roave/security-advisories": "dev-latest", "rskuipers/php-assumptions": "^0.8.0", diff --git a/src/Redis/Client/AbstractClient.php b/src/Redis/Client/AbstractClient.php index 3f12b39..5934df1 100644 --- a/src/Redis/Client/AbstractClient.php +++ b/src/Redis/Client/AbstractClient.php @@ -41,6 +41,18 @@ public function pipeline(Command ...$commands): array }, $results, array_keys($results)); } + public function execute(Command $command) + { + $args = [$command->getId()]; + $commandArgs = $command->getArguments(); + if (count($commandArgs) > 0) { + array_push($args, ...$commandArgs); + } + $result = $this->executeRaw(...$args); + + return $command->parseResponse($result); + } + /** * @return array */ diff --git a/src/Redis/Client/AmpRedisClient.php b/src/Redis/Client/AmpRedisClient.php index 540641c..3532b49 100644 --- a/src/Redis/Client/AmpRedisClient.php +++ b/src/Redis/Client/AmpRedisClient.php @@ -53,15 +53,6 @@ public static function make($redis): Client return new self($redis); } - public function execute(Command $command) - { - /** @var Promise $query */ - $query = $this->redis->query($command->getId(), ...array_map('strval', $command->getArguments())); - $result = wait($query); - - return $command->parseResponse($result); - } - public function executeRaw(...$args) { /** @var Promise $query */ @@ -77,6 +68,9 @@ public static function supports($redis): bool && function_exists('\\Amp\\Promise\\wait'); } + /** + * @codeCoverageIgnore + */ public function pipeline(Command ...$commands): array { false === static::$disableNotice diff --git a/src/Redis/Client/CheprasovRedisClient.php b/src/Redis/Client/CheprasovRedisClient.php index ca71cde..dfc2fef 100644 --- a/src/Redis/Client/CheprasovRedisClient.php +++ b/src/Redis/Client/CheprasovRedisClient.php @@ -52,13 +52,6 @@ public static function make($redis): Client return new self($redis); } - public function execute(Command $command) - { - $response = $this->redis->executeRaw(array_merge([$command->getId()], $command->getArguments())); - - return $command->parseResponse($response); - } - public function executeRaw(...$args) { return $this->redis->executeRaw(array_map('strval', $args)); diff --git a/src/Redis/Client/ClientFacade.php b/src/Redis/Client/ClientFacade.php index d16a269..af21954 100644 --- a/src/Redis/Client/ClientFacade.php +++ b/src/Redis/Client/ClientFacade.php @@ -22,9 +22,11 @@ namespace MacFJA\RediSearch\Redis\Client; use Amp\Redis\Redis as AmpRedis; +use Clue\React\Redis\Client as ReactClient; use Credis_Client as CredisRedis; use MacFJA\RediSearch\Redis\Client; use Predis\ClientInterface as PredisRedis; +use React\Promise\Promise as ReactPromise; use Redis as PhpredisRedis; use RedisClient\Client\AbstractRedisClient as CheprasovRedis; use redisent\Redis as RedisentRedis; @@ -44,10 +46,11 @@ class ClientFacade RediskaClient::class, AmpRedisClient::class, TinyRedisClient::class, + ReactRedisClient::class, ]; /** - * @param AmpRedis|CheprasovRedis|CredisRedis|mixed|PhpredisRedis|PredisRedis|RedisentRedis|RediskaRedis|resource $redis + * @param AmpRedis|CheprasovRedis|CredisRedis|mixed|PhpredisRedis|PredisRedis|ReactClient|ReactPromise|RedisentRedis|RediskaRedis|resource $redis */ public function getClient($redis): Client { diff --git a/src/Redis/Client/CredisClient.php b/src/Redis/Client/CredisClient.php index 2569e09..2334e3a 100644 --- a/src/Redis/Client/CredisClient.php +++ b/src/Redis/Client/CredisClient.php @@ -51,13 +51,6 @@ public static function make($redis): Client return new self($redis); } - public function execute(Command $command) - { - $result = $this->redis->__call($command->getId(), $command->getArguments()); - - return $command->parseResponse($this->fixFalseToNull($result)); - } - public function executeRaw(...$args) { $command = array_shift($args); diff --git a/src/Redis/Client/PhpiredisClient.php b/src/Redis/Client/PhpiredisClient.php index 0c30a6b..bbe08bb 100644 --- a/src/Redis/Client/PhpiredisClient.php +++ b/src/Redis/Client/PhpiredisClient.php @@ -51,13 +51,6 @@ private function __construct($redis) $this->redis = $redis; } - public function execute(Command $command) - { - $rawResponse = phpiredis_command_bs($this->redis, array_merge([$command->getId()], $command->getArguments())); - - return $command->parseResponse($rawResponse); - } - public static function supports($redis): bool { if (!is_resource($redis) diff --git a/src/Redis/Client/PredisClient.php b/src/Redis/Client/PredisClient.php index 89cf769..db148cc 100644 --- a/src/Redis/Client/PredisClient.php +++ b/src/Redis/Client/PredisClient.php @@ -55,13 +55,6 @@ private function __construct(ClientInterface $redis) $this->redis = $redis; } - public function execute(Command $command) - { - $rawResponse = $this->redis->executeCommand(self::createRawCommand(array_merge([$command->getId()], $command->getArguments()))); - - return $command->parseResponse($rawResponse); - } - public static function supports($redis): bool { if ( diff --git a/src/Redis/Client/ReactRedisClient.php b/src/Redis/Client/ReactRedisClient.php new file mode 100644 index 0000000..8bc8260 --- /dev/null +++ b/src/Redis/Client/ReactRedisClient.php @@ -0,0 +1,102 @@ +getMissingMessage('Clue Redis React and/or React\\Async', false, [ + Client::class => [], + PromiseInterface::class => [], + ], ['\React\Async\await'])); + } + if ($redis instanceof Promise) { + $realRedis = await($redis); + if (!$realRedis instanceof Client) { + throw new RuntimeException('The provided $redis parameter is not a valid Redis client'); + } + $this->redis = $realRedis; + + return; + } + $this->redis = $redis; + } + + /** + * @codeCoverageIgnore + */ + public function pipeline(Command ...$commands): array + { + false === static::$disableNotice + && trigger_error('Warning, Clue\\React\\Redis\\Client don\'t use a real Redis Pipeline', E_USER_NOTICE); + + return array_map(function (Command $command) { + return $this->execute($command); + }, $commands); + } + + public static function make($redis): \MacFJA\RediSearch\Redis\Client + { + return new self($redis); + } + + public function executeRaw(...$args) + { + $command = array_shift($args); + + return await($this->redis->__call((string) $command, array_map('strval', $args))); + } + + public static function supports($redis): bool + { + return ($redis instanceof Client || $redis instanceof PromiseInterface) && function_exists('\React\Async\await'); + } + + /** + * @codeCoverageIgnore + */ + protected function doPipeline(Command ...$commands): array + { + return []; + } +} diff --git a/src/Redis/Client/RedisentClient.php b/src/Redis/Client/RedisentClient.php index 9a048e3..93bacf5 100644 --- a/src/Redis/Client/RedisentClient.php +++ b/src/Redis/Client/RedisentClient.php @@ -49,13 +49,6 @@ public static function make($redis): Client return new self($redis); } - public function execute(Command $command) - { - $result = $this->redis->__call($command->getId(), $command->getArguments()); - - return $command->parseResponse($result); - } - public function executeRaw(...$args) { $command = array_shift($args); diff --git a/src/Redis/Client/TinyRedisClient.php b/src/Redis/Client/TinyRedisClient.php index 67c7a69..62194ea 100644 --- a/src/Redis/Client/TinyRedisClient.php +++ b/src/Redis/Client/TinyRedisClient.php @@ -55,13 +55,6 @@ public function pipeline(Command ...$commands): array }, $commands); } - public function execute(Command $command) - { - $result = $this->redis->__call($command->getId(), $command->getArguments()); - - return $command->parseResponse($result); - } - public function executeRaw(...$args) { $command = array_shift($args); diff --git a/tests/Redis/Client/ClientFacadeTest.php b/tests/Redis/Client/ClientFacadeTest.php index ae5e1cd..4463c92 100644 --- a/tests/Redis/Client/ClientFacadeTest.php +++ b/tests/Redis/Client/ClientFacadeTest.php @@ -41,6 +41,7 @@ * @uses \MacFJA\RediSearch\Redis\Client\RediskaClient * @uses \MacFJA\RediSearch\Redis\Client\CredisClient * @uses \MacFJA\RediSearch\Redis\Client\TinyRedisClient + * @uses \MacFJA\RediSearch\Redis\Client\ReactRedisClient * * @internal */ diff --git a/tests/integration/DockerTest.php b/tests/integration/DockerTest.php index fb4dc9c..5f1df34 100644 --- a/tests/integration/DockerTest.php +++ b/tests/integration/DockerTest.php @@ -59,6 +59,7 @@ * @covers \MacFJA\RediSearch\Redis\Client\ClientFacade * @covers \MacFJA\RediSearch\Redis\Client\CredisClient * @covers \MacFJA\RediSearch\Redis\Client\PredisClient + * @covers \MacFJA\RediSearch\Redis\Client\ReactRedisClient * @covers \MacFJA\RediSearch\Redis\Client\RedisentClient * @covers \MacFJA\RediSearch\Redis\Client\RediskaClient * @covers \MacFJA\RediSearch\Redis\Client\TinyRedisClient @@ -277,24 +278,26 @@ public function dataProvider(string $testName): array { if (in_array($testName, ['testIntegration', 'testAddDocument'])) { return [ - [static function () { return Client\PredisClient::make(new \Predis\Client(['scheme' => 'tcp', 'host' => 'localhost', 'port' => '16379', 'db' => 0])); }], - [static function () { return Client\RediskaClient::make(new Rediska(['servers' => [['host' => 'localhost', 'port' => '16379', 'db' => 0]]])); }], - [static function () { return Client\RedisentClient::make(new \redisent\Redis('redis://localhost:16379')); }], - [static function () { return Client\CheprasovRedisClient::make(new \RedisClient\Client\Version\RedisClient6x0(['server' => 'localhost:16379', 'database' => 0])); }], - [static function () { return Client\AmpRedisClient::make(new \Amp\Redis\Redis(new RemoteExecutor(Config::fromUri('redis://localhost:16379')))); }], - [static function () { return Client\TinyRedisClient::make(new TinyRedisClient('localhost:16379')); }], - [static function () { return Client\CredisClient::make(new Credis_Client('localhost', 16379, null, '', 0)); }], + 'Predis' => [static function () { return Client\PredisClient::make(new \Predis\Client(['scheme' => 'tcp', 'host' => 'localhost', 'port' => '16379', 'db' => 0])); }], + 'Rediska' => [static function () { return Client\RediskaClient::make(new Rediska(['servers' => [['host' => 'localhost', 'port' => '16379', 'db' => 0]]])); }], + 'Redisent' => [static function () { return Client\RedisentClient::make(new \redisent\Redis('redis://localhost:16379')); }], + 'CheprasovRedis' => [static function () { return Client\CheprasovRedisClient::make(new \RedisClient\Client\Version\RedisClient6x0(['server' => 'localhost:16379', 'database' => 0])); }], + 'AmpRedis' => [static function () { return Client\AmpRedisClient::make(new \Amp\Redis\Redis(new RemoteExecutor(Config::fromUri('redis://localhost:16379')))); }], + 'TinyRedis' => [static function () { return Client\TinyRedisClient::make(new TinyRedisClient('localhost:16379')); }], + 'Credis' => [static function () { return Client\CredisClient::make(new Credis_Client('localhost', 16379, null, '', 0)); }], + 'ReactRedis' => [static function () { return Client\ReactRedisClient::make((new \Clue\React\Redis\Factory())->createClient('localhost:16379')); }], ]; } return [ - [Client\PredisClient::class, static function () { return new \Predis\Client(['scheme' => 'tcp', 'host' => 'localhost', 'port' => '16379', 'db' => 0]); }], - [Client\RediskaClient::class, static function () { return new Rediska(['servers' => [['host' => 'localhost', 'port' => '16379', 'db' => 0]]]); }], - [Client\RedisentClient::class, static function () { return new \redisent\Redis('redis://localhost:16379'); }], - [Client\CheprasovRedisClient::class, static function () { return new \RedisClient\Client\Version\RedisClient6x0(['server' => 'localhost:16379', 'database' => 0]); }], - [Client\AmpRedisClient::class, static function () { return new \Amp\Redis\Redis(new RemoteExecutor(Config::fromUri('redis://localhost:16379'))); }], - [Client\TinyRedisClient::class, static function () { return new TinyRedisClient('localhost:16379'); }], - [Client\CredisClient::class, static function () { return new Credis_Client('localhost', 16379, null, '', 0); }], + 'Predis' => [Client\PredisClient::class, static function () { return new \Predis\Client(['scheme' => 'tcp', 'host' => 'localhost', 'port' => '16379', 'db' => 0]); }], + 'Rediska' => [Client\RediskaClient::class, static function () { return new Rediska(['servers' => [['host' => 'localhost', 'port' => '16379', 'db' => 0]]]); }], + 'Redisent' => [Client\RedisentClient::class, static function () { return new \redisent\Redis('redis://localhost:16379'); }], + 'CheprasovRedis' => [Client\CheprasovRedisClient::class, static function () { return new \RedisClient\Client\Version\RedisClient6x0(['server' => 'localhost:16379', 'database' => 0]); }], + 'AmpRedis' => [Client\AmpRedisClient::class, static function () { return new \Amp\Redis\Redis(new RemoteExecutor(Config::fromUri('redis://localhost:16379'))); }], + 'TinyRedis' => [Client\TinyRedisClient::class, static function () { return new TinyRedisClient('localhost:16379'); }], + 'Credis' => [Client\CredisClient::class, static function () { return new Credis_Client('localhost', 16379, null, '', 0); }], + 'ReactRedis' => [Client\ReactRedisClient::class, static function () { return (new \Clue\React\Redis\Factory())->createClient('localhost:16379'); }], ]; }