mirror of
https://github.com/predis/predis.git
synced 2026-09-11 10:57:00 +00:00
Added support for container commands (#1049)
* Added support for container commands FUNCTION LOAD, FUNCTION DELETE and FCALL * Changed ContainerInterface and AbstractContainer * Re-implement logic of abstract methods --------- Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
This commit is contained in:
committed by
GitHub
parent
38e955bc26
commit
98222f47b0
@@ -17,6 +17,8 @@ use InvalidArgumentException;
|
||||
use IteratorAggregate;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\RawCommand;
|
||||
use Predis\Command\Redis\Container\ContainerFactory;
|
||||
use Predis\Command\Redis\Container\ContainerInterface;
|
||||
use Predis\Command\ScriptCommand;
|
||||
use Predis\Configuration\Options;
|
||||
use Predis\Configuration\OptionsInterface;
|
||||
@@ -31,6 +33,7 @@ use Predis\Response\ResponseInterface;
|
||||
use Predis\Response\ServerException;
|
||||
use Predis\Transaction\MultiExec as MultiExecTransaction;
|
||||
use ReturnTypeWillChange;
|
||||
use RuntimeException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
@@ -310,6 +313,34 @@ class Client implements ClientInterface, IteratorAggregate
|
||||
return $this->commands->create($commandID, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return ContainerFactory::create($this, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
throw new RuntimeException('Not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
throw new RuntimeException('Not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@ use Predis\Command\Argument\Geospatial\FromInterface;
|
||||
use Predis\Command\Argument\Server\LimitOffsetCount;
|
||||
use Predis\Command\Argument\Server\To;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\Redis\Container\FunctionContainer;
|
||||
|
||||
/**
|
||||
* Interface defining a client-side context such as a pipeline or transaction.
|
||||
@@ -55,6 +56,7 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this decr($key)
|
||||
* @method $this decrby($key, $decrement)
|
||||
* @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1)
|
||||
* @method $this fcall(string $function, array $keys, ...$args)
|
||||
* @method $this get($key)
|
||||
* @method $this getbit($key, $offset)
|
||||
* @method $this getex(string $key, $modifier = '', $value = false)
|
||||
@@ -200,6 +202,9 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null)
|
||||
* @method $this geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false)
|
||||
* @method $this geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
|
||||
*
|
||||
* Container commands
|
||||
* @property FunctionContainer $function
|
||||
*/
|
||||
interface ClientContextInterface
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ use Predis\Command\Argument\Server\LimitOffsetCount;
|
||||
use Predis\Command\Argument\Server\To;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\FactoryInterface;
|
||||
use Predis\Command\Redis\Container\FunctionContainer;
|
||||
use Predis\Configuration\OptionsInterface;
|
||||
use Predis\Connection\ConnectionInterface;
|
||||
use Predis\Response\Status;
|
||||
@@ -64,6 +65,7 @@ use Predis\Response\Status;
|
||||
* @method int decr(string $key)
|
||||
* @method int decrby(string $key, int $decrement)
|
||||
* @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1)
|
||||
* @method mixed fcall(string $function, array $keys, ...$args)
|
||||
* @method string|null get(string $key)
|
||||
* @method int getbit(string $key, $offset)
|
||||
* @method int|null getex(string $key, $modifier = '', $value = false)
|
||||
@@ -218,6 +220,9 @@ use Predis\Response\Status;
|
||||
* @method array georadiusbymember(string $key, $member, $radius, $unit, array $options = null)
|
||||
* @method array geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false)
|
||||
* @method int geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
|
||||
*
|
||||
* Container commands
|
||||
* @property FunctionContainer $function
|
||||
*/
|
||||
interface ClientInterface
|
||||
{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis\Container;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
abstract class AbstractContainer implements ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var ClientInterface
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
public function __construct(ClientInterface $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function __call($subcommandID, $arguments)
|
||||
{
|
||||
array_unshift($arguments, strtoupper($subcommandID));
|
||||
|
||||
return $this->client->executeCommand(
|
||||
$this->client->createCommand($this->getContainerCommandId(), $arguments)
|
||||
);
|
||||
}
|
||||
|
||||
abstract public function getContainerCommandId(): string;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis\Container;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ContainerFactory
|
||||
{
|
||||
private const CONTAINER_NAMESPACE = "Predis\Command\Redis\Container";
|
||||
|
||||
/**
|
||||
* Mappings for class names that corresponds to PHP reserved words.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $specialMappings = [
|
||||
'FUNCTION' => FunctionContainer::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates container command.
|
||||
*
|
||||
* @param ClientInterface $client
|
||||
* @param string $containerCommandID
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public static function create(ClientInterface $client, string $containerCommandID): ContainerInterface
|
||||
{
|
||||
$containerCommandID = strtoupper($containerCommandID);
|
||||
|
||||
if (class_exists($containerClass = self::CONTAINER_NAMESPACE . '\\' . $containerCommandID)) {
|
||||
return new $containerClass($client);
|
||||
}
|
||||
|
||||
if (array_key_exists($containerCommandID, self::$specialMappings)) {
|
||||
$containerClass = self::$specialMappings[$containerCommandID];
|
||||
|
||||
return new $containerClass($client);
|
||||
}
|
||||
|
||||
throw new UnexpectedValueException('Given command is not supported.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis\Container;
|
||||
|
||||
interface ContainerInterface
|
||||
{
|
||||
/**
|
||||
* Creates Redis container command with subcommand as virtual method name
|
||||
* and sends a request to the server.
|
||||
*
|
||||
* @param $subcommandID
|
||||
* @param $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($subcommandID, $arguments);
|
||||
|
||||
/**
|
||||
* Returns containerCommandId of specific container command.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContainerCommandId(): string;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis\Container;
|
||||
|
||||
use Predis\Response\Status;
|
||||
|
||||
/**
|
||||
* @method string load(string $functionCode, bool $replace = 'false')
|
||||
* @method Status delete(string $libraryName)
|
||||
*/
|
||||
class FunctionContainer extends AbstractContainer
|
||||
{
|
||||
public function getContainerCommandId(): string
|
||||
{
|
||||
return 'functions';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Command\Command as RedisCommand;
|
||||
use Predis\Command\Traits\Keys;
|
||||
|
||||
/**
|
||||
* @see https://redis.io/commands/fcall/
|
||||
*
|
||||
* Invoke a function.
|
||||
*/
|
||||
class FCALL extends RedisCommand
|
||||
{
|
||||
use Keys;
|
||||
|
||||
protected static $keysArgumentPositionOffset = 1;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return 'FCALL';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Command\Command as RedisCommand;
|
||||
use Predis\Command\Strategy\StrategyResolverInterface;
|
||||
use Predis\Command\Strategy\SubcommandStrategyResolver;
|
||||
|
||||
/**
|
||||
* @see https://redis.io/commands/?name=function
|
||||
*
|
||||
* Container command corresponds to any FUNCTION *.
|
||||
* Represents any FUNCTION command with subcommand as first argument.
|
||||
*/
|
||||
class FUNCTIONS extends RedisCommand
|
||||
{
|
||||
/**
|
||||
* @var StrategyResolverInterface
|
||||
*/
|
||||
private $strategyResolver;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->strategyResolver = new SubcommandStrategyResolver();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return 'FUNCTION';
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$strategy = $this->strategyResolver->resolve('functions', $arguments[0]);
|
||||
$arguments = $strategy->processArguments($arguments);
|
||||
|
||||
parent::setArguments($arguments);
|
||||
$this->filterArguments();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
use Predis\Command\Redis\FUNCTIONS;
|
||||
|
||||
/**
|
||||
* Command factory for mainline Redis servers.
|
||||
*
|
||||
@@ -29,6 +31,8 @@ class RedisFactory extends Factory
|
||||
'ECHO' => 'Predis\Command\Redis\ECHO_',
|
||||
'EVAL' => 'Predis\Command\Redis\EVAL_',
|
||||
'OBJECT' => 'Predis\Command\Redis\OBJECT_',
|
||||
// Class name corresponds to PHP reserved word "function", added mapping to bypass restrictions
|
||||
'FUNCTION' => FUNCTIONS::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy\ContainerCommands\Functions;
|
||||
|
||||
use Predis\Command\Strategy\SubcommandStrategyInterface;
|
||||
|
||||
class DeleteStrategy implements SubcommandStrategyInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function processArguments(array $arguments): array
|
||||
{
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy\ContainerCommands\Functions;
|
||||
|
||||
use Predis\Command\Strategy\SubcommandStrategyInterface;
|
||||
|
||||
class LoadStrategy implements SubcommandStrategyInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processArguments(array $arguments): array
|
||||
{
|
||||
if (count($arguments) <= 2) {
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
$processedArguments = [$arguments[0]];
|
||||
$replace = array_pop($arguments);
|
||||
|
||||
if (is_bool($replace) && $replace) {
|
||||
$processedArguments[] = 'REPLACE';
|
||||
} elseif (!is_bool($replace)) {
|
||||
$processedArguments[] = $replace;
|
||||
}
|
||||
|
||||
$processedArguments[] = $arguments[1];
|
||||
|
||||
return $processedArguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy;
|
||||
|
||||
interface StrategyResolverInterface
|
||||
{
|
||||
/**
|
||||
* Resolves subcommand strategy.
|
||||
*
|
||||
* @param string $commandId
|
||||
* @param string $subcommandId
|
||||
* @return SubcommandStrategyInterface
|
||||
*/
|
||||
public function resolve(string $commandId, string $subcommandId): SubcommandStrategyInterface;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy;
|
||||
|
||||
interface SubcommandStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Process arguments for given subcommand.
|
||||
*
|
||||
* @param array $arguments
|
||||
* @return array
|
||||
*/
|
||||
public function processArguments(array $arguments): array;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SubcommandStrategyResolver implements StrategyResolverInterface
|
||||
{
|
||||
private const CONTAINER_COMMANDS_NAMESPACE = 'Predis\Command\Strategy\ContainerCommands';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function resolve(string $commandId, string $subcommandId): SubcommandStrategyInterface
|
||||
{
|
||||
$subcommandStrategyClass = ucfirst(strtolower($subcommandId)) . 'Strategy';
|
||||
$commandDirectoryName = ucfirst(strtolower($commandId));
|
||||
|
||||
if (class_exists(
|
||||
$containerCommandClass = self::CONTAINER_COMMANDS_NAMESPACE . '\\' . $commandDirectoryName . '\\' . $subcommandStrategyClass
|
||||
)) {
|
||||
return new $containerCommandClass();
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Non-existing container command given');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis\Container;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\ClientInterface;
|
||||
use Predis\Command\CommandInterface;
|
||||
|
||||
class AbstractContainerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var AbstractContainer
|
||||
*/
|
||||
private $testClass;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $arguments;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $expectedValue;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|ClientInterface
|
||||
*/
|
||||
private $mockClient;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|CommandInterface
|
||||
*/
|
||||
private $mockCommand;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->arguments = ['arg1', 'arg2'];
|
||||
$this->expectedValue = ['value'];
|
||||
$this->mockCommand = $this->getMockBuilder(CommandInterface::class)->getMock();
|
||||
$this->mockClient = $this->getMockBuilder(ClientInterface::class)->getMock();
|
||||
|
||||
$this->testClass = new class($this->mockClient) extends AbstractContainer {
|
||||
public function getContainerCommandId(): string
|
||||
{
|
||||
return 'test';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testGetContainerId(): void
|
||||
{
|
||||
$this->assertSame('test', $this->testClass->getContainerCommandId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCallReturnsValidCommandResponse(): void
|
||||
{
|
||||
$modifiedArguments = ['TEST', ['arg1', 'arg2']];
|
||||
|
||||
$this->mockClient
|
||||
->expects($this->once())
|
||||
->method('createCommand')
|
||||
->with($this->equalTo('test'), $modifiedArguments)
|
||||
->willReturn($this->mockCommand);
|
||||
|
||||
$this->mockClient
|
||||
->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->mockCommand)
|
||||
->willReturn($this->expectedValue);
|
||||
|
||||
$this->assertSame($this->expectedValue, $this->testClass->test($this->arguments));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis\Container;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\ClientInterface;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ContainerFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|ClientInterface
|
||||
*/
|
||||
private $mockClient;
|
||||
|
||||
/**
|
||||
* @var ContainerFactory
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $expectedContainer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->mockClient = $this->getMockBuilder(ClientInterface::class)->getMock();
|
||||
$this->expectedContainer = new FunctionContainer($this->mockClient);
|
||||
$this->factory = new ContainerFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCreatesReturnsExistingCommandContainerClass(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
$this->expectedContainer,
|
||||
$this->factory::create($this->mockClient, 'function')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testThrowsExceptionOnNonExistingCommand(): void
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage('Given command is not supported.');
|
||||
|
||||
$this->factory::create($this->mockClient, 'foobar');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Response\ServerException;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-scripting
|
||||
*/
|
||||
class FCALL_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return FCALL::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'FCALL';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @dataProvider argumentsProvider
|
||||
*/
|
||||
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($actualArguments);
|
||||
|
||||
$this->assertSame($expectedArguments, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse(): void
|
||||
{
|
||||
$this->assertSame(1, $this->getCommand()->parseResponse(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @dataProvider functionsProvider
|
||||
* @param string $function
|
||||
* @param array $functionArguments
|
||||
* @param $expectedResponse
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0.0
|
||||
*/
|
||||
public function testInvokeGivenFunction(
|
||||
string $function,
|
||||
array $functionArguments,
|
||||
$expectedResponse
|
||||
): void {
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertSame('mylib', $redis->function->load($function));
|
||||
|
||||
$actualResponse = $redis->fcall(...$functionArguments);
|
||||
$this->assertSame($expectedResponse, $actualResponse);
|
||||
$this->assertEquals('OK', $redis->function->delete('mylib'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0.0
|
||||
*/
|
||||
public function testThrowsExceptionOnNonExistingFunctionGiven(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->expectException(ServerException::class);
|
||||
$this->expectExceptionMessage('ERR Function not found');
|
||||
|
||||
$redis->fcall('function', []);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with default arguments' => [
|
||||
['function', []],
|
||||
['function', 0],
|
||||
],
|
||||
'with provided keys' => [
|
||||
['function', ['key1', 'key2']],
|
||||
['function', 2, 'key1', 'key2'],
|
||||
],
|
||||
'with provided keys and arguments' => [
|
||||
['function', ['key1', 'key2'], 'arg1', 'arg2'],
|
||||
['function', 2, 'key1', 'key2', 'arg1', 'arg2'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function functionsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with default arguments' => [
|
||||
"#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return 'hello' end)",
|
||||
['myfunc', []],
|
||||
'hello',
|
||||
],
|
||||
'with provided keys' => [
|
||||
"#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return keys[1] end)",
|
||||
['myfunc', ['key1']],
|
||||
'key1',
|
||||
],
|
||||
'with provided keys and arguments' => [
|
||||
"#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return keys[1] .. ' ' .. args[1] end)",
|
||||
['myfunc', ['key1'], 'arg1'],
|
||||
'key1 arg1',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Response\ServerException;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-scripting
|
||||
*/
|
||||
class FUNCTIONS_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $libName = 'mylib';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return FUNCTIONS::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'FUNCTION';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testLoadFilterArguments(): void
|
||||
{
|
||||
$arguments = ['LOAD', 'function-code', true];
|
||||
$expected = ['LOAD', 'function-code', 'REPLACE'];
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSameValues($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testDeleteFilterArguments(): void
|
||||
{
|
||||
$arguments = ['DELETE', 'libraryName'];
|
||||
$expected = ['DELETE', 'libraryName'];
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSameValues($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse(): void
|
||||
{
|
||||
$this->assertSame(1, $this->getCommand()->parseResponse(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0.0
|
||||
*/
|
||||
public function testLoadFunctionAddFunctionIntoGivenLibrary(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$actualResponse = $redis->function->load(
|
||||
"#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
|
||||
);
|
||||
|
||||
$this->assertSame('mylib', $actualResponse);
|
||||
$this->assertSame('arg1', $redis->fcall('myfunc', [], 'arg1'));
|
||||
$this->assertEquals('OK', $redis->function->delete($this->libName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0.0
|
||||
*/
|
||||
public function testLoadFunctionOverridesExistingFunctionWithReplaceArgumentGiven(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$actualResponse = $redis->function->load(
|
||||
"#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
|
||||
);
|
||||
|
||||
$this->assertSame($this->libName, $actualResponse);
|
||||
$this->assertSame('arg1', $redis->fcall('myfunc', [], 'arg1'));
|
||||
|
||||
$overriddenResponse = $redis->function->load(
|
||||
"#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[2] end)",
|
||||
true
|
||||
);
|
||||
|
||||
$this->assertSame($this->libName, $overriddenResponse);
|
||||
$this->assertSame('arg2', $redis->fcall('myfunc', [], 'arg1', 'arg2'));
|
||||
$this->assertEquals('OK', $redis->function->delete($this->libName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0.0
|
||||
*/
|
||||
public function testLoadFunctionThrowsErrorOnAlreadyExistingLibraryGiven(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$actualResponse = $redis->function->load(
|
||||
"#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
|
||||
);
|
||||
|
||||
$this->assertSame($this->libName, $actualResponse);
|
||||
|
||||
$this->expectException(ServerException::class);
|
||||
$this->expectExceptionMessage("ERR Library '{$this->libName}' already exists");
|
||||
|
||||
try {
|
||||
$redis->function->load(
|
||||
"#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
|
||||
);
|
||||
} finally {
|
||||
$this->assertEquals('OK', $redis->function->delete($this->libName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0.0
|
||||
*/
|
||||
public function testDeleteFunctionRemovesAlreadyExistingLibrary(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$actualResponse = $redis->function->load(
|
||||
"#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
|
||||
);
|
||||
|
||||
$this->assertSame($this->libName, $actualResponse);
|
||||
$this->assertEquals('OK', $redis->function->delete($this->libName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0.0
|
||||
*/
|
||||
public function testDeleteFunctionThrowsErrorOnNonExistingLibrary(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->expectException(ServerException::class);
|
||||
$this->expectExceptionMessage('ERR Library not found');
|
||||
|
||||
$redis->function->delete($this->libName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy\ContainerCommands\Functions;
|
||||
|
||||
use PredisTestCase;
|
||||
|
||||
class DeleteStrategyTest extends PredisTestCase
|
||||
{
|
||||
/**
|
||||
* @var DeleteStrategy
|
||||
*/
|
||||
private $strategy;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->strategy = new DeleteStrategy();
|
||||
}
|
||||
|
||||
public function testProcessArguments(): void
|
||||
{
|
||||
$this->assertSame(['arg1', 'arg2'], $this->strategy->processArguments(['arg1', 'arg2']));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy\ContainerCommands\Functions;
|
||||
|
||||
use PredisTestCase;
|
||||
|
||||
class LoadStrategyTest extends PredisTestCase
|
||||
{
|
||||
/**
|
||||
* @var LoadStrategy
|
||||
*/
|
||||
private $strategy;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->strategy = new LoadStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @param array $actualArguments
|
||||
* @param array $expectedArguments
|
||||
* @return void
|
||||
*/
|
||||
public function testProcessArgumentsReturnsCorrectArguments(
|
||||
array $actualArguments,
|
||||
array $expectedArguments
|
||||
): void {
|
||||
$this->assertSame($expectedArguments, $this->strategy->processArguments($actualArguments));
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with less then or equal 2 arguments' => [
|
||||
['arg1', 'arg2'],
|
||||
['arg1', 'arg2'],
|
||||
],
|
||||
'with last argument equals true' => [
|
||||
['arg1', 'arg2', true],
|
||||
['arg1', 'REPLACE', 'arg2'],
|
||||
],
|
||||
'with last argument equals false' => [
|
||||
['arg1', 'arg2', false],
|
||||
['arg1', 'arg2'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Strategy;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\Command\Strategy\ContainerCommands\Functions\LoadStrategy;
|
||||
|
||||
class SubcommandStrategyResolverTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var StrategyResolverInterface
|
||||
*/
|
||||
private $resolver;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->resolver = new SubcommandStrategyResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testResolveCorrectStrategy(): void
|
||||
{
|
||||
$expectedStrategy = new LoadStrategy();
|
||||
|
||||
$this->assertEquals($expectedStrategy, $this->resolver->resolve('functions', 'load'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testResolveThrowsExceptionOnNonExistingStrategy(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Non-existing container command given');
|
||||
|
||||
$this->resolver->resolve('foo', 'bar');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user