Skip to content

Commit

Permalink
Added support for container commands (predis#1049)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
vladvildanov and Vladyslav Vildanov committed Jan 31, 2023
1 parent 38e955b commit 98222f4
Show file tree
Hide file tree
Showing 22 changed files with 1,051 additions and 0 deletions.
31 changes: 31 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
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;
Expand All @@ -31,6 +33,7 @@
use Predis\Response\ServerException;
use Predis\Transaction\MultiExec as MultiExecTransaction;
use ReturnTypeWillChange;
use RuntimeException;
use Traversable;

/**
Expand Down Expand Up @@ -310,6 +313,34 @@ public function createCommand($commandID, $arguments = [])
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}
*/
Expand Down
5 changes: 5 additions & 0 deletions src/ClientContextInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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.
Expand Down Expand Up @@ -55,6 +56,7 @@
* @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)
Expand Down Expand Up @@ -200,6 +202,9 @@
* @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
{
Expand Down
5 changes: 5 additions & 0 deletions src/ClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand Down Expand Up @@ -64,6 +65,7 @@
* @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)
Expand Down Expand Up @@ -218,6 +220,9 @@
* @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
{
Expand Down
42 changes: 42 additions & 0 deletions src/Command/Redis/Container/AbstractContainer.php
Original file line number Diff line number Diff line change
@@ -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;
}
54 changes: 54 additions & 0 deletions src/Command/Redis/Container/ContainerFactory.php
Original file line number Diff line number Diff line change
@@ -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.');
}
}
33 changes: 33 additions & 0 deletions src/Command/Redis/Container/ContainerInterface.php
Original file line number Diff line number Diff line change
@@ -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;
}
27 changes: 27 additions & 0 deletions src/Command/Redis/Container/FunctionContainer.php
Original file line number Diff line number Diff line change
@@ -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';
}
}
33 changes: 33 additions & 0 deletions src/Command/Redis/FCALL.php
Original file line number Diff line number Diff line change
@@ -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';
}
}
50 changes: 50 additions & 0 deletions src/Command/Redis/FUNCTIONS.php
Original file line number Diff line number Diff line change
@@ -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();
}
}
4 changes: 4 additions & 0 deletions src/Command/RedisFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

namespace Predis\Command;

use Predis\Command\Redis\FUNCTIONS;

/**
* Command factory for mainline Redis servers.
*
Expand All @@ -29,6 +31,8 @@ public function __construct()
'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,
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 98222f4

Please sign in to comment.