From f4a76e0b9713ee00fcd97738db9aad2470bfaef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bok?= Date: Tue, 25 Feb 2025 22:26:37 +0100 Subject: [PATCH] [Cache] Add `\Relay\Cluster` support --- .../Component/Cache/Adapter/RedisAdapter.php | 2 +- .../Cache/Adapter/RedisTagAwareAdapter.php | 6 +- src/Symfony/Component/Cache/CHANGELOG.md | 5 + .../Adapter/AbstractRedisAdapterTestCase.php | 3 +- .../RedisTagAwareRelayClusterAdapterTest.php | 40 + .../Tests/Adapter/RelayClusterAdapterTest.php | 68 + .../Cache/Tests/Traits/RedisProxiesTest.php | 50 + .../Component/Cache/Traits/RedisTrait.php | 109 +- .../Cache/Traits/RelayClusterProxy.php | 1203 +++++++++++++++++ .../Component/Lock/Store/RedisStore.php | 7 +- .../Store/AbstractRedisStoreTestCase.php | 7 +- .../Tests/Store/RelayClusterStoreTest.php | 48 + .../Component/Semaphore/Store/RedisStore.php | 5 +- .../Store/AbstractRedisStoreTestCase.php | 3 +- .../Tests/Store/RelayClusterStoreTest.php | 36 + 15 files changed, 1572 insertions(+), 20 deletions(-) create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/RelayClusterAdapterTest.php create mode 100644 src/Symfony/Component/Cache/Traits/RelayClusterProxy.php create mode 100644 src/Symfony/Component/Lock/Tests/Store/RelayClusterStoreTest.php create mode 100644 src/Symfony/Component/Semaphore/Tests/Store/RelayClusterStoreTest.php diff --git a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php index e33f2f65fc927..f31f0d7def5f2 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php @@ -18,7 +18,7 @@ class RedisAdapter extends AbstractAdapter { use RedisTrait; - public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) + public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay|\Relay\Cluster $redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) { $this->init($redis, $namespace, $defaultLifetime, $marshaller); } diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index 7b282375ce7fd..a887f29abfb0a 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -60,7 +60,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter private string $redisEvictionPolicy; public function __construct( - \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + \Redis|Relay|\Relay\Cluster|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, private string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null, @@ -69,7 +69,7 @@ public function __construct( throw new InvalidArgumentException(\sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); } - $isRelay = $redis instanceof Relay; + $isRelay = $redis instanceof Relay || $redis instanceof \Relay\Cluster; if ($isRelay || \defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { $compression = $redis->getOption($isRelay ? Relay::OPT_COMPRESSION : \Redis::OPT_COMPRESSION); @@ -225,7 +225,7 @@ protected function doInvalidate(array $tagIds): bool $results = $this->pipeline(function () use ($tagIds, $lua) { if ($this->redis instanceof \Predis\ClientInterface) { $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; - } elseif (\is_array($prefix = $this->redis->getOption($this->redis instanceof Relay ? Relay::OPT_PREFIX : \Redis::OPT_PREFIX) ?? '')) { + } elseif (\is_array($prefix = $this->redis->getOption(($this->redis instanceof Relay || $this->redis instanceof \Relay\Cluster) ? Relay::OPT_PREFIX : \Redis::OPT_PREFIX) ?? '')) { $prefix = current($prefix); } diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 038915c46ff54..b92cd754c7746 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.3 +--- + + * Add support for `\Relay\Cluster` in `RedisAdapter` + 7.2 --- diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php index c83365cc73f35..03a0f87312fa2 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTestCase.php @@ -13,6 +13,7 @@ use Psr\Cache\CacheItemPoolInterface; use Relay\Relay; +use Relay\Cluster as RelayCluster; use Symfony\Component\Cache\Adapter\RedisAdapter; abstract class AbstractRedisAdapterTestCase extends AdapterTestCase @@ -23,7 +24,7 @@ abstract class AbstractRedisAdapterTestCase extends AdapterTestCase 'testDefaultLifeTime' => 'Testing expiration slows down the test suite', ]; - protected static \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; + protected static \Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php new file mode 100644 index 0000000000000..4939d2dfe1466 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Adapter; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter; +use Symfony\Component\Cache\Traits\RelayClusterProxy; + +/** + * @requires extension relay + * + * @group integration + */ +class RedisTagAwareRelayClusterAdapterTest extends RelayClusterAdapterTest +{ + use TagAwareTestTrait; + + protected function setUp(): void + { + parent::setUp(); + $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; + } + + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface + { + $this->assertInstanceOf(RelayClusterProxy::class, self::$redis); + $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); + + return $adapter; + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RelayClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RelayClusterAdapterTest.php new file mode 100644 index 0000000000000..1f9718a6f4b4d --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/RelayClusterAdapterTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Adapter; + +use Relay\Relay; +use Relay\Cluster as RelayCluster; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\RelayClusterProxy; + +/** + * @requires extension relay + * + * @group integration + */ +class RelayClusterAdapterTest extends AbstractRedisAdapterTestCase +{ + public static function setUpBeforeClass(): void + { + if (!class_exists(RelayCluster::class)) { + self::markTestSkipped('The Relay\Cluster class is required.'); + } + if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) { + self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + + self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['lazy' => true, 'redis_cluster' => true, 'class' => RelayCluster::class]); + self::$redis->setOption(Relay::OPT_PREFIX, 'prefix_'); + } + + public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface + { + $this->assertInstanceOf(RelayClusterProxy::class, self::$redis); + $adapter = new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); + + return $adapter; + } + + /** + * @dataProvider provideFailedCreateConnection + */ + public function testFailedCreateConnection(string $dsn) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Relay cluster connection failed:'); + RedisAdapter::createConnection($dsn); + } + + public static function provideFailedCreateConnection(): array + { + return [ + ['redis://localhost:1234?redis_cluster=1&class=Relay\Cluster'], + ['redis://foo@localhost?redis_cluster=1&class=Relay\Cluster'], + ['redis://localhost/123?redis_cluster=1&class=Relay\Cluster'], + ]; + } +} diff --git a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php index 0be4060227faa..47767a88227fe 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php +++ b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php @@ -13,7 +13,9 @@ use PHPUnit\Framework\TestCase; use Relay\Relay; +use Relay\Cluster as RelayCluster; use Symfony\Component\Cache\Traits\RedisProxyTrait; +use Symfony\Component\Cache\Traits\RelayClusterProxy; use Symfony\Component\Cache\Traits\RelayProxy; use Symfony\Component\VarExporter\LazyProxyTrait; use Symfony\Component\VarExporter\ProxyHelper; @@ -121,4 +123,52 @@ public function testRelayProxy() $this->assertEquals($expectedProxy, $proxy); } + + + /** + * @requires extension relay + */ + public function testRelayClusterProxy() + { + $proxy = file_get_contents(\dirname(__DIR__, 2).'/Traits/RelayClusterProxy.php'); + $proxy = substr($proxy, 0, 2 + strpos($proxy, '}')); + $expectedProxy = $proxy; + $methods = []; + $expectedMethods = []; + + foreach ((new \ReflectionClass(RelayClusterProxy::class))->getMethods() as $method) { + if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name) || $method->isStatic()) { + continue; + } + + $return = '__construct' === $method->name || $method->getReturnType() instanceof \ReflectionNamedType && 'void' === (string) $method->getReturnType() ? '' : 'return '; + $expectedMethods[$method->name] = "\n ".ProxyHelper::exportSignature($method, false, $args)."\n".<<initializeLazyObject()->{$method->name}({$args}); + } + + EOPHP; + } + + foreach ((new \ReflectionClass(RelayCluster::class))->getMethods() as $method) { + if ('reset' === $method->name || method_exists(RedisProxyTrait::class, $method->name) || $method->isStatic()) { + continue; + } + $return = '__construct' === $method->name || $method->getReturnType() instanceof \ReflectionNamedType && 'void' === (string) $method->getReturnType() ? '' : 'return '; + $methods[$method->name] = "\n ".ProxyHelper::exportSignature($method, false, $args)."\n".<<initializeLazyObject()->{$method->name}({$args}); + } + + EOPHP; + } + + uksort($methods, 'strnatcmp'); + $proxy .= implode('', $methods)."}\n"; + + uksort($expectedMethods, 'strnatcmp'); + $expectedProxy .= implode('', $expectedMethods)."}\n"; + + $this->assertEquals($expectedProxy, $proxy); + } } diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index f6bb9bbe101f1..480e4f77ad021 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -21,6 +21,7 @@ use Predis\Response\ErrorInterface; use Predis\Response\Status; use Relay\Relay; +use Relay\Cluster as RelayCluster; use Relay\Sentinel; use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; @@ -41,19 +42,21 @@ trait RedisTrait 'persistent_id' => null, 'timeout' => 30, 'read_timeout' => 0, + 'command_timeout' => 0, 'retry_interval' => 0, 'tcp_keepalive' => 0, 'lazy' => null, 'redis_cluster' => false, + 'relay_cluster_context' => [], 'redis_sentinel' => null, 'dbindex' => 0, 'failover' => 'none', 'ssl' => null, // see https://php.net/context.ssl ]; - private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; + private \Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; private MarshallerInterface $marshaller; - private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller): void + private function init(\Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller): void { parent::__construct($namespace, $defaultLifetime); @@ -85,7 +88,7 @@ private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInter * * @throws InvalidArgumentException when the DSN is invalid */ - public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay|RelayCluster { if (str_starts_with($dsn, 'redis:')) { $scheme = 'redis'; @@ -187,14 +190,18 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra if (isset($params['lazy'])) { $params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); } - $params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN); + $params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN); if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); } $class = $params['class'] ?? match (true) { - $params['redis_cluster'] => \extension_loaded('redis') ? \RedisCluster::class : \Predis\Client::class, + $params['redis_cluster'] => match (true) { + \extension_loaded('redis') => \RedisCluster::class, + \extension_loaded('relay') => RelayCluster::class, + default => \Predis\Client::class, + }, isset($params['redis_sentinel']) => match (true) { \extension_loaded('redis') => \Redis::class, \extension_loaded('relay') => Relay::class, @@ -348,6 +355,66 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra if (0 < $params['tcp_keepalive'] && (!$isRedisExt || \defined('Redis::OPT_TCP_KEEPALIVE'))) { $redis->setOption($isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } + } elseif (is_a($class, RelayCluster::class, true)) { + if (version_compare(phpversion('relay'), '0.10.0', '<')) { + throw new InvalidArgumentException('Using RelayCluster is supported from ext-relay 0.10.0 or higher.'); + } + + $initializer = static function () use ($class, $params, $hosts) { + foreach ($hosts as $i => $host) { + $hosts[$i] = match ($host['scheme']) { + 'tcp' => $host['host'].':'.$host['port'], + 'tls' => 'tls://'.$host['host'].':'.$host['port'], + default => $host['path'], + }; + } + + try { + $relayClusterContext = $params['relay_cluster_context']; + + foreach (['allow_self_signed', 'verify_peer_name','verify_peer'] as $contextStreamBoolField) { + if(isset($relayClusterContext['stream'][$contextStreamBoolField])) { + $relayClusterContext['stream'][$contextStreamBoolField] = filter_var($relayClusterContext['stream'][$contextStreamBoolField], \FILTER_VALIDATE_BOOL); + } + } + + foreach (['use-cache', 'client-tracking','throw-on-error','client-invalidations','reply-literal','persistent'] as $contextBoolField) { + if(isset($relayClusterContext[$contextBoolField])) { + $relayClusterContext[$contextBoolField] = filter_var($relayClusterContext[$contextBoolField], \FILTER_VALIDATE_BOOL); + } + } + + foreach (['max-retries', 'serializer','compression','compression-level'] as $contextIntField) { + if(isset($relayClusterContext[$contextIntField])) { + $relayClusterContext[$contextIntField] = filter_var($relayClusterContext[$contextIntField], \FILTER_VALIDATE_INT); + } + } + + $relayCluster = new $class( + name: null, + seeds: $hosts, + connect_timeout: $params['timeout'], + command_timeout: $params['command_timeout'], + persistent: (bool) $params['persistent'], + auth: $params['auth'] ?? null, + context: $relayClusterContext + ); + } catch (\Relay\Exception $e) { + throw new InvalidArgumentException('Relay cluster connection failed: '.$e->getMessage()); + } + + if (0 < $params['tcp_keepalive']) { + $relayCluster->setOption(Relay::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + } + + if (0 < $params['read_timeout']) { + $relayCluster->setOption(Relay::OPT_READ_TIMEOUT, $params['read_timeout']); + } + + return $relayCluster; + }; + + $redis = $params['lazy'] ? RelayClusterProxy::createLazyProxy($initializer) : $initializer(); } elseif (is_a($class, \RedisCluster::class, true)) { $initializer = static function () use ($isRedisExt, $class, $params, $hosts) { foreach ($hosts as $i => $host) { @@ -478,6 +545,35 @@ protected function doClear(string $namespace): bool } $cleared = true; + + if ($this->redis instanceof RelayCluster) { + $prefix = Relay::SCAN_PREFIX & $this->redis->getOption(Relay::OPT_SCAN) ? '' : $this->redis->getOption(Relay::OPT_PREFIX); + $prefixLen = \strlen($prefix); + $pattern = $prefix.$namespace.'*'; + foreach ($this->redis->_masters() as $ipAndPort) { + $address = implode(':', $ipAndPort); + $cursor = null; + do { + $keys = $this->redis->scan($cursor, $address, $pattern, 1000); + if (isset($keys[1]) && \is_array($keys[1])) { + $cursor = $keys[0]; + $keys = $keys[1]; + } + + if ($keys) { + if ($prefixLen) { + foreach ($keys as $i => $key) { + $keys[$i] = substr($key, $prefixLen); + } + } + $this->doDelete($keys); + } + } while ($cursor); + } + + return $cleared; + } + $hosts = $this->getHosts(); $host = reset($hosts); if ($host instanceof \Predis\Client) { @@ -605,8 +701,9 @@ private function pipeline(\Closure $generator, ?object $redis = null): \Generato $ids = []; $redis ??= $this->redis; - if ($redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && ($redis->getConnection() instanceof RedisCluster || $redis->getConnection() instanceof Predis2RedisCluster))) { + if ($redis instanceof \RedisCluster || $redis instanceof \Relay\Cluster || ($redis instanceof \Predis\ClientInterface && ($redis->getConnection() instanceof RedisCluster || $redis->getConnection() instanceof Predis2RedisCluster))) { // phpredis & predis don't support pipelining with RedisCluster + // \Relay\Cluster does not support multi with pipeline mode // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining // see https://github.com/nrk/predis/issues/267#issuecomment-123781423 $results = []; diff --git a/src/Symfony/Component/Cache/Traits/RelayClusterProxy.php b/src/Symfony/Component/Cache/Traits/RelayClusterProxy.php new file mode 100644 index 0000000000000..92466e56ae874 --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/RelayClusterProxy.php @@ -0,0 +1,1203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Relay\Cluster; +use Relay\Relay; +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +/** + * @internal + */ +class RelayClusterProxy extends \Relay\Cluster implements ResetInterface, LazyObjectInterface +{ + use RedisProxyTrait { + resetLazyObject as reset; + } + + public function __construct( + string|null $name, + array|null $seeds = null, + int|float $connect_timeout = 0, + int|float $command_timeout = 0, + bool $persistent = false, + #[\SensitiveParameter] mixed $auth = null, + array|null $context = null + ) { + $this->initializeLazyObject()->__construct(...\func_get_args()); + } + + public function close(): bool + { + return $this->initializeLazyObject()->close(...\func_get_args()); + } + + public function listen(?callable $callback): bool + { + return $this->initializeLazyObject()->listen(...\func_get_args()); + } + + public function onFlushed(?callable $callback): bool + { + return $this->initializeLazyObject()->onFlushed(...\func_get_args()); + } + + public function onInvalidated(?callable $callback, ?string $pattern = null): bool + { + return $this->initializeLazyObject()->onInvalidated(...\func_get_args()); + } + + public function dispatchEvents(): false|int + { + return $this->initializeLazyObject()->dispatchEvents(...\func_get_args()); + } + + public function dump(mixed $key): \Relay\Cluster|string|false + { + return $this->initializeLazyObject()->dump(...\func_get_args()); + } + + public function getOption(int $option): mixed + { + return $this->initializeLazyObject()->getOption(...\func_get_args()); + } + + public function setOption(int $option, mixed $value): bool + { + return $this->initializeLazyObject()->setOption(...\func_get_args()); + } + + public function getTransferredBytes(): array|false + { + return $this->initializeLazyObject()->getTransferredBytes(...\func_get_args()); + } + + public function getrange(mixed $key, int $start, int $end): \Relay\Cluster|string|false + { + return $this->initializeLazyObject()->getrange(...\func_get_args()); + } + + public function addIgnorePatterns(string ...$pattern): int + { + return $this->initializeLazyObject()->addIgnorePatterns(...\func_get_args()); + } + + public function addAllowPatterns(string ...$pattern): int + { + return $this->initializeLazyObject()->addAllowPatterns(...\func_get_args()); + } + + public function _serialize(mixed $value): string + { + return $this->initializeLazyObject()->_serialize(...\func_get_args()); + } + + public function _unserialize(string $value): mixed + { + return $this->initializeLazyObject()->_unserialize(...\func_get_args()); + } + + public function _compress(string $value): string + { + return $this->initializeLazyObject()->_compress(...\func_get_args()); + } + + public function _uncompress(string $value): string + { + return $this->initializeLazyObject()->_uncompress(...\func_get_args()); + } + + public function _pack(mixed $value): string + { + return $this->initializeLazyObject()->_pack(...\func_get_args()); + } + + public function _unpack(string $value): mixed + { + return $this->initializeLazyObject()->_unpack(...\func_get_args()); + } + + public function _prefix(mixed $value): string + { + return $this->initializeLazyObject()->_prefix(...\func_get_args()); + } + + public function getLastError(): ?string + { + return $this->initializeLazyObject()->getLastError(...\func_get_args()); + } + + public function clearLastError(): bool + { + return $this->initializeLazyObject()->clearLastError(...\func_get_args()); + } + + public function clearTransferredBytes(): bool + { + return $this->initializeLazyObject()->clearTransferredBytes(...\func_get_args()); + } + + public function endpointId(): array|false + { + return $this->initializeLazyObject()->endpointId(...\func_get_args()); + } + + + public function rawCommand(array|string $key_or_address, string $cmd, mixed ...$args): mixed + { + return $this->initializeLazyObject()->rawCommand(...\func_get_args()); + } + + public function cluster(array|string $key_or_address, string $operation, mixed ...$args): mixed + { + return $this->initializeLazyObject()->cluster(...\func_get_args()); + } + + public function info(array|string $key_or_address, string ...$sections): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->info(...\func_get_args()); + } + + public function flushdb(array|string $key_or_address, bool|null $sync = null): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->flushdb(...\func_get_args()); + } + + public function flushall(array|string $key_or_address, bool|null $sync = null): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->flushall(...\func_get_args()); + } + + public function dbsize(array|string $key_or_address): \Relay\Cluster|int|false + { + return $this->initializeLazyObject()->dbsize(...\func_get_args()); + } + + public function waitaof(array|string $key_or_address, int $numlocal, int $numremote, int $timeout): \Relay\Relay|array|false + { + return $this->initializeLazyObject()->waitaof(...\func_get_args()); + } + + public function restore(mixed $key, int $ttl, string $value, array|null $options = null): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->restore(...\func_get_args()); + } + + public function echo(array|string $key_or_address, string $message): \Relay\Cluster|string|false + { + return $this->initializeLazyObject()->echo(...\func_get_args()); + } + + public function ping(array|string $key_or_address, string|null $message = null): \Relay\Cluster|bool|string + { + return $this->initializeLazyObject()->ping(...\func_get_args()); + } + + public function idleTime(): int + { + return $this->initializeLazyObject()->idleTime(...\func_get_args()); + } + + public function randomkey(array|string $key_or_address): \Relay\Cluster|bool|string + { + return $this->initializeLazyObject()->randomkey(...\func_get_args()); + } + + public function time(array|string $key_or_address): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->time(...\func_get_args()); + } + + public function bgrewriteaof(array|string $key_or_address): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->bgrewriteaof(...\func_get_args()); + } + + public function lastsave(array|string $key_or_address): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->lastsave(...\func_get_args()); + } + + public function lcs(mixed $key1, mixed $key2, array|null $options = null): mixed + { + return $this->initializeLazyObject()->lcs(...\func_get_args()); + } + + public function bgsave(array|string $key_or_address, bool $schedule = false): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->bgsave(...\func_get_args()); + } + + public function save(array|string $key_or_address): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->save(...\func_get_args()); + } + + public function role(array|string $key_or_address): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->role(...\func_get_args()); + } + + public function ttl(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->ttl(...\func_get_args()); + } + + public function pttl(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->pttl(...\func_get_args()); + } + + public function exists(mixed ...$keys): \Relay\Cluster|bool|int + { + return $this->initializeLazyObject()->exists(...\func_get_args()); + } + + public function eval(mixed $script, array $args = [], int $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval(...\func_get_args()); + } + + public function eval_ro(mixed $script, array $args = [], int $num_keys = 0): mixed + { + return $this->initializeLazyObject()->eval_ro(...\func_get_args()); + } + + public function evalsha(string $sha, array $args = [], int $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha(...\func_get_args()); + } + + public function evalsha_ro(string $sha, array $args = [], int $num_keys = 0): mixed + { + return $this->initializeLazyObject()->evalsha_ro(...\func_get_args()); + } + + public function client(array|string $key_or_address, string $operation, mixed ...$args): mixed + { + return $this->initializeLazyObject()->client(...\func_get_args()); + } + + public function geoadd(mixed $key, float $lng, float $lat, string $member, mixed ...$other_triples_and_options): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->geoadd(...\func_get_args()); + } + + public function geodist(mixed $key, string $src, string $dst, string|null $unit = null): \Relay\Cluster|float|false + { + return $this->initializeLazyObject()->geodist(...\func_get_args()); + } + + public function geohash(mixed $key, string $member, string ...$other_members): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->geohash(...\func_get_args()); + } + + public function georadius(mixed $key, float $lng, float $lat, float $radius, string $unit, array $options = []): mixed + { + return $this->initializeLazyObject()->georadius(...\func_get_args()); + } + + public function georadiusbymember(mixed $key, string $member, float $radius, string $unit, array $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro(mixed $key, string $member, float $radius, string $unit, array $options = []): mixed + { + return $this->initializeLazyObject()->georadiusbymember_ro(...\func_get_args()); + } + + public function georadius_ro(mixed $key, float $lng, float $lat, float $radius, string $unit, array $options = []): mixed + { + return $this->initializeLazyObject()->georadius_ro(...\func_get_args()); + } + + public function geosearchstore(mixed $dstkey, mixed $srckey, array|string $position, array|int|float $shape, string $unit, array $options = []): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->geosearchstore(...\func_get_args()); + } + + public function geosearch(mixed $key, array|string $position, array|int|float $shape, string $unit, array $options = []): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->geosearch(...\func_get_args()); + } + + public function get(mixed $key): mixed + { + return $this->initializeLazyObject()->get(...\func_get_args()); + } + + public function getset(mixed $key, mixed $value): mixed + { + return $this->initializeLazyObject()->getset(...\func_get_args()); + } + + public function setrange(mixed $key, int $start, mixed $value): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->setrange(...\func_get_args()); + } + + public function getbit(mixed $key, int $pos): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->getbit(...\func_get_args()); + } + + public function bitcount(mixed $key, int $start = 0, int $end = -1, bool $by_bit = false): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->bitcount(...\func_get_args()); + } + + public function config(array|string $key_or_address, string $operation, mixed ...$args): mixed + { + return $this->initializeLazyObject()->config(...\func_get_args()); + } + + public function command(mixed ...$args): \Relay\Cluster|array|false|int + { + return $this->initializeLazyObject()->command(...\func_get_args()); + } + + public function bitop(string $operation, string $dstkey, string $srckey, string ...$other_keys): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->bitop(...\func_get_args()); + } + + public function bitpos(mixed $key, int $bit, ?int $start = null, ?int $end = null, bool $by_bit = false): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->bitpos(...\func_get_args()); + } + + public function blmove(mixed $srckey, mixed $dstkey, string $srcpos, string $dstpos, float $timeout): \Relay\Cluster|string|null|false + { + return $this->initializeLazyObject()->blmove(...\func_get_args()); + } + + public function lmove(mixed $srckey, mixed $dstkey, string $srcpos, string $dstpos): Cluster|string|null|false { + return $this->initializeLazyObject()->lmove(...\func_get_args()); + } + + public function setbit(mixed $key, int $pos, int $value): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->setbit(...\func_get_args()); + } + + public function acl(array|string $key_or_address, string $operation, string ...$args): mixed + { + return $this->initializeLazyObject()->acl(...\func_get_args()); + } + + public function append(mixed $key, mixed $value): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->append(...\func_get_args()); + } + + public function set(mixed $key, mixed $value, mixed $options = null): \Relay\Cluster|string|bool + { + return $this->initializeLazyObject()->set(...\func_get_args()); + } + + public function getex(mixed $key, ?array $options = null): mixed + { + return $this->initializeLazyObject()->getex(...\func_get_args()); + } + + public function setex(mixed $key, int $seconds, mixed $value): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->setex(...\func_get_args()); + } + + public function pfadd(mixed $key, array $elements): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->pfadd(...\func_get_args()); + } + + public function pfcount(mixed $key): \Relay\Cluster|int|false + { + return $this->initializeLazyObject()->pfcount(...\func_get_args()); + } + + public function pfmerge(string $dstkey, array $srckeys): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->pfmerge(...\func_get_args()); + } + + public function psetex(mixed $key, int $milliseconds, mixed $value): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->psetex(...\func_get_args()); + } + + public function publish(string $channel, string $message): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->publish(...\func_get_args()); + } + + public function pubsub(array|string $key_or_address, string $operation, mixed ...$args): mixed + { + return $this->initializeLazyObject()->pubsub(...\func_get_args()); + } + + public function setnx(mixed $key, mixed $value): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->setnx(...\func_get_args()); + } + + public function mget(array $keys): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->mget(...\func_get_args()); + } + + public function mset(array $kvals): \Relay\Cluster|array|bool + { + return $this->initializeLazyObject()->mset(...\func_get_args()); + } + + public function msetnx(array $kvals): \Relay\Cluster|array|bool + { + return $this->initializeLazyObject()->msetnx(...\func_get_args()); + } + + public function rename(mixed $key, mixed $newkey): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->rename(...\func_get_args()); + } + + public function renamenx(mixed $key, mixed $newkey): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->renamenx(...\func_get_args()); + } + + public function del(mixed ...$keys): \Relay\Cluster|bool|int + { + return $this->initializeLazyObject()->del(...\func_get_args()); + } + + public function unlink(mixed ...$keys): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->unlink(...\func_get_args()); + } + + public function expire(mixed $key, int $seconds, string|null $mode = null): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->expire(...\func_get_args()); + } + + public function pexpire(mixed $key, int $milliseconds): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->pexpire(...\func_get_args()); + } + + public function expireat(mixed $key, int $timestamp): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->expireat(...\func_get_args()); + } + + public function expiretime(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->expiretime(...\func_get_args()); + } + + public function pexpireat(mixed $key, int $timestamp_ms): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->pexpireat(...\func_get_args()); + } + + public static function flushMemory(?string $endpointId = null, ?int $db = null): bool + { + return \Relay\Cluster::flushMemory(...\func_get_args()); + } + + public function pexpiretime(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->pexpiretime(...\func_get_args()); + } + + public function persist(mixed $key): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->persist(...\func_get_args()); + } + + public function type(mixed $key): \Relay\Cluster|bool|int|string + { + return $this->initializeLazyObject()->type(...\func_get_args()); + } + + public function lrange(mixed $key, int $start, int $stop): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->lrange(...\func_get_args()); + } + + public function lpush(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->lpush(...\func_get_args()); + } + + public function rpush(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->rpush(...\func_get_args()); + } + + public function lpushx(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->lpushx(...\func_get_args()); + } + + public function rpushx(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->rpushx(...\func_get_args()); + } + + public function lset(mixed $key, int $index, mixed $member): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->lset(...\func_get_args()); + } + + public function lpop(mixed $key, int $count = 1): mixed + { + return $this->initializeLazyObject()->lpop(...\func_get_args()); + } + + public function lpos(mixed $key, mixed $value, array|null $options = null): mixed + { + return $this->initializeLazyObject()->lpos(...\func_get_args()); + } + + public function rpop(mixed $key, int $count = 1): mixed + { + return $this->initializeLazyObject()->rpop(...\func_get_args()); + } + + public function rpoplpush(mixed $srckey, mixed $dstkey): mixed + { + return $this->initializeLazyObject()->rpoplpush(...\func_get_args()); + } + + public function brpoplpush(mixed $srckey, mixed $dstkey, float $timeout): mixed + { + return $this->initializeLazyObject()->brpoplpush(...\func_get_args()); + } + + public function blpop(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): \Relay\Cluster|array|false|null + { + return $this->initializeLazyObject()->blpop(...\func_get_args()); + } + + public function blmpop(float $timeout, array $keys, string $from, int $count = 1): mixed + { + return $this->initializeLazyObject()->blmpop(...\func_get_args()); + } + + public function bzmpop(float $timeout, array $keys, string $from, int $count = 1): \Relay\Cluster|array|false|null + { + return $this->initializeLazyObject()->bzmpop(...\func_get_args()); + } + + public function lmpop(array $keys, string $from, int $count = 1): mixed + { + return $this->initializeLazyObject()->lmpop(...\func_get_args()); + } + + public function zmpop(array $keys, string $from, int $count = 1): \Relay\Cluster|array|false|null + { + return $this->initializeLazyObject()->zmpop(...\func_get_args()); + } + + public function brpop(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): \Relay\Cluster|array|false|null + { + return $this->initializeLazyObject()->brpop(...\func_get_args()); + } + + public function bzpopmax(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): \Relay\Cluster|array|false|null + { + return $this->initializeLazyObject()->bzpopmax(...\func_get_args()); + } + + public function bzpopmin(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): \Relay\Cluster|array|false|null + { + return $this->initializeLazyObject()->bzpopmin(...\func_get_args()); + } + + public function object(string $op, mixed $key): mixed + { + return $this->initializeLazyObject()->object(...\func_get_args()); + } + + public function geopos(mixed $key, mixed ...$members): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->geopos(...\func_get_args()); + } + + public function lrem(mixed $key, mixed $member, int $count = 0): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->lrem(...\func_get_args()); + } + + public function lindex(mixed $key, int $index): mixed + { + return $this->initializeLazyObject()->lindex(...\func_get_args()); + } + + public function linsert(mixed $key, string $op, mixed $pivot, mixed $element): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->linsert(...\func_get_args()); + } + + public function ltrim(mixed $key, int $start, int $end): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->ltrim(...\func_get_args()); + } + + public static function maxMemory(): int { + return \Relay\Cluster::maxMemory(); + } + + public function hget(mixed $key, mixed $member): mixed + { + return $this->initializeLazyObject()->hget(...\func_get_args()); + } + + public function hstrlen(mixed $key, mixed $member): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->hstrlen(...\func_get_args()); + } + + public function hgetall(mixed $key): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->hgetall(...\func_get_args()); + } + + public function hkeys(mixed $key): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->hkeys(...\func_get_args()); + } + + public function hvals(mixed $key): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->hvals(...\func_get_args()); + } + + public function hmget(mixed $key, array $members): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->hmget(...\func_get_args()); + } + + public function hmset(mixed $key, array $members): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->hmset(...\func_get_args()); + } + + public function hexists(mixed $key, mixed $member): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->hexists(...\func_get_args()); + } + + public function hrandfield(mixed $key, array|null $options = null): \Relay\Cluster|array|string|false + { + return $this->initializeLazyObject()->hrandfield(...\func_get_args()); + } + + public function hsetnx(mixed $key, mixed $member, mixed $value): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->hsetnx(...\func_get_args()); + } + + public function hset(mixed $key, mixed ...$keys_and_vals): \Relay\Cluster|int|false + { + return $this->initializeLazyObject()->hset(...\func_get_args()); + } + + public function hdel(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->hdel(...\func_get_args()); + } + + public function hincrby(mixed $key, mixed $member, int $value): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->hincrby(...\func_get_args()); + } + + public function hincrbyfloat(mixed $key, mixed $member, float $value): \Relay\Cluster|bool|float + { + return $this->initializeLazyObject()->hincrbyfloat(...\func_get_args()); + } + + public function incr(mixed $key, int $by = 1): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->incr(...\func_get_args()); + } + + public function decr(mixed $key, int $by = 1): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->decr(...\func_get_args()); + } + + public function incrby(mixed $key, int $value): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->incrby(...\func_get_args()); + } + + public function decrby(mixed $key, int $value): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->decrby(...\func_get_args()); + } + + public function incrbyfloat(mixed $key, float $value): \Relay\Cluster|false|float + { + return $this->initializeLazyObject()->incrbyfloat(...\func_get_args()); + } + + public function sdiff(mixed $key, mixed ...$other_keys): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->sdiff(...\func_get_args()); + } + + public function sdiffstore(mixed $key, mixed ...$other_keys): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->sdiffstore(...\func_get_args()); + } + + public function sinter(mixed $key, mixed ...$other_keys): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->sinter(...\func_get_args()); + } + + public function sintercard(array $keys, int $limit = -1): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->sintercard(...\func_get_args()); + } + + public function sinterstore(mixed $key, mixed ...$other_keys): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->sinterstore(...\func_get_args()); + } + + public function sunion(mixed $key, mixed ...$other_keys): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->sunion(...\func_get_args()); + } + + public function sunionstore(mixed $key, mixed ...$other_keys): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->sunionstore(...\func_get_args()); + } + + public function subscribe(array $channels, callable $callback): bool + { + return $this->initializeLazyObject()->subscribe(...\func_get_args()); + } + + public function unsubscribe(array $channels = []): bool + { + return $this->initializeLazyObject()->unsubscribe(...\func_get_args()); + } + + public function psubscribe(array $patterns, callable $callback): bool + { + return $this->initializeLazyObject()->psubscribe(...\func_get_args()); + } + + public function punsubscribe(array $patterns = []): bool + { + return $this->initializeLazyObject()->punsubscribe(...\func_get_args()); + } + + public function ssubscribe(array $channels, callable $callback): bool + { + return $this->initializeLazyObject()->ssubscribe(...\func_get_args()); + } + + public function sunsubscribe(array $channels = []): bool + { + return $this->initializeLazyObject()->sunsubscribe(...\func_get_args()); + } + + public function touch(array|string $key_or_array, mixed ...$more_keys): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->touch(...\func_get_args()); + } + + public function multi(int $mode = Relay::MULTI): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->multi(...\func_get_args()); + } + + public function exec(): array|false + { + return $this->initializeLazyObject()->exec(...\func_get_args()); + } + + public function watch(mixed $key, mixed ...$other_keys): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->watch(...\func_get_args()); + } + + public function unwatch(): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->unwatch(...\func_get_args()); + } + + public function discard(): bool + { + return $this->initializeLazyObject()->discard(...\func_get_args()); + } + + public function getMode(bool $masked = false): int + { + return $this->initializeLazyObject()->getMode(...\func_get_args()); + } + + public function scan(mixed &$iterator, array|string $key_or_address, mixed $match = null, int $count = 0, string|null $type = null): array|false + { + return $this->initializeLazyObject()->scan($iterator, ...\array_slice(\func_get_args(), 1)); + } + + public function hscan(mixed $key, mixed &$iterator, mixed $match = null, int $count = 0): array|false + { + return $this->initializeLazyObject()->hscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function sscan(mixed $key, mixed &$iterator, mixed $match = null, int $count = 0): array|false + { + return $this->initializeLazyObject()->sscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscan(mixed $key, mixed &$iterator, mixed $match = null, int $count = 0): array|false + { + return $this->initializeLazyObject()->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); + } + + public function zscore(mixed $key, mixed $member): \Relay\Cluster|float|false + { + return $this->initializeLazyObject()->zscore(...\func_get_args()); + } + + public function keys(mixed $pattern): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->keys(...\func_get_args()); + } + + public function slowlog(array|string $key_or_address, string $operation, mixed ...$args): \Relay\Cluster|array|bool|int + { + return $this->initializeLazyObject()->slowlog(...\func_get_args()); + } + + public function xadd(mixed $key, string $id, array $values, int $maxlen = 0, bool $approx = false, bool $nomkstream = false): Cluster|string|false + { + return $this->initializeLazyObject()->xadd(...\func_get_args()); + } + + public function smembers(mixed $key): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->smembers(...\func_get_args()); + } + + public function sismember(mixed $key, mixed $member): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->sismember(...\func_get_args()); + } + + public function smismember(mixed $key, mixed ...$members): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->smismember(...\func_get_args()); + } + + public function srem(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->srem(...\func_get_args()); + } + + public function sadd(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->sadd(...\func_get_args()); + } + + public function sort(mixed $key, array $options = []): \Relay\Cluster|array|false|int + { + return $this->initializeLazyObject()->sort(...\func_get_args()); + } + + public function sort_ro(mixed $key, array $options = []): \Relay\Cluster|array|false|int + { + return $this->initializeLazyObject()->sort_ro(...\func_get_args()); + } + + public function smove(mixed $srckey, mixed $dstkey, mixed $member): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->smove(...\func_get_args()); + } + + public function spop(mixed $key, int $count = 1): mixed + { + return $this->initializeLazyObject()->spop(...\func_get_args()); + } + + public function srandmember(mixed $key, int $count = 1): mixed + { + return $this->initializeLazyObject()->srandmember(...\func_get_args()); + } + + public function scard(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->scard(...\func_get_args()); + } + + public function script(array|string $key_or_address, string $operation, string ...$args): mixed + { + return $this->initializeLazyObject()->script(...\func_get_args()); + } + + public function strlen(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->strlen(...\func_get_args()); + } + + public function hlen(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->hlen(...\func_get_args()); + } + + public function llen(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->llen(...\func_get_args()); + } + + public function xack(mixed $key, string $group, array $ids): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->xack(...\func_get_args()); + } + + public function xclaim(mixed $key, string $group, string $consumer, int $min_idle, array $ids, array $options): \Relay\Cluster|array|bool + { + return $this->initializeLazyObject()->xclaim(...\func_get_args()); + } + + public function xautoclaim(mixed $key, string $group, string $consumer, int $min_idle, string $start, int $count = -1, bool $justid = false): \Relay\Cluster|array|bool + { + return $this->initializeLazyObject()->xautoclaim(...\func_get_args()); + } + + public function xlen(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->xlen(...\func_get_args()); + } + + public function xgroup(string $operation, mixed $key = null, ?string $group = null, ?string $id_or_consumer = null, bool $mkstream = false, int $entries_read = -2): mixed + { + return $this->initializeLazyObject()->xgroup(...\func_get_args()); + } + + public function xdel(mixed $key, array $ids): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->xdel(...\func_get_args()); + } + + public function xinfo(string $operation, string|null $arg1 = null, string|null $arg2 = null, int $count = -1): mixed + { + return $this->initializeLazyObject()->xinfo(...\func_get_args()); + } + + public function xpending(mixed $key, string $group, string|null $start = null, string|null $end = null, int $count = -1, string|null $consumer = null, int $idle = 0): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->xpending(...\func_get_args()); + } + + public function xrange(mixed $key, string $start, string $end, int $count = -1): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->xrange(...\func_get_args()); + } + + public function xread(array $streams, int $count = -1, int $block = -1): \Relay\Cluster|array|bool|null + { + return $this->initializeLazyObject()->xread(...\func_get_args()); + } + + public function xreadgroup(mixed $key, string $consumer, array $streams, int $count = 1, int $block = 1): \Relay\Cluster|array|bool|null + { + return $this->initializeLazyObject()->xreadgroup(...\func_get_args()); + } + + public function xrevrange(mixed $key, string $end, string $start, int $count = -1): \Relay\Cluster|array|bool + { + return $this->initializeLazyObject()->xrevrange(...\func_get_args()); + } + + public function xtrim(mixed $key, string $threshold, bool $approx = false, bool $minid = false, int $limit = -1): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->xtrim(...\func_get_args()); + } + + public function zadd(mixed $key, mixed ...$args): mixed + { + return $this->initializeLazyObject()->zadd(...\func_get_args()); + } + + public function zrandmember(mixed $key, array|null $options = null): mixed + { + return $this->initializeLazyObject()->zrandmember(...\func_get_args()); + } + + public function zrange(mixed $key, string $start, string $end, mixed $options = null): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zrange(...\func_get_args()); + } + + public function zrevrange(mixed $key, int $start, int $end, mixed $options = null): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zrevrange(...\func_get_args()); + } + + public function zrangebyscore(mixed $key, mixed $start, mixed $end, mixed $options = null): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zrangebyscore(...\func_get_args()); + } + + public function zrevrangebyscore(mixed $key, mixed $start, mixed $end, mixed $options = null): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zrevrangebyscore(...\func_get_args()); + } + + public function zrevrank(mixed $key, mixed $rank, bool $withscore = false): Cluster|array|int|false + { + return $this->initializeLazyObject()->zrevrank(...\func_get_args()); + } + + public function zrangestore(mixed $dstkey, mixed $srckey, mixed $start, mixed $end, mixed $options = null): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zrangestore(...\func_get_args()); + } + + public function zrank(mixed $key, mixed $rank, bool $withscore = false): Cluster|array|int|false + { + return $this->initializeLazyObject()->zrank(...\func_get_args()); + } + + public function zrangebylex(mixed $key, mixed $min, mixed $max, int $offset = -1, int $count = -1): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zrangebylex(...\func_get_args()); + } + + public function zrevrangebylex(mixed $key, mixed $max, mixed $min, int $offset = -1, int $count = -1): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zrevrangebylex(...\func_get_args()); + } + + public function zrem(mixed $key, mixed ...$args): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zrem(...\func_get_args()); + } + + public function zremrangebylex(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank(mixed $key, int $start, int $end): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zremrangebyscore(...\func_get_args()); + } + + public function zcard(mixed $key): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zcard(...\func_get_args()); + } + + public function zcount(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zcount(...\func_get_args()); + } + + public function zdiff(array $keys, array|null $options = null): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zdiff(...\func_get_args()); + } + + public function zdiffstore(mixed $dstkey, array $keys): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zdiffstore(...\func_get_args()); + } + + public function zincrby(mixed $key, float $score, mixed $member): \Relay\Cluster|false|float + { + return $this->initializeLazyObject()->zincrby(...\func_get_args()); + } + + public function zlexcount(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zlexcount(...\func_get_args()); + } + + public function zmscore(mixed $key, mixed ...$members): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zmscore(...\func_get_args()); + } + + public function zinter(array $keys, array|null $weights = null, mixed $options = null): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zinter(...\func_get_args()); + } + + public function zintercard(array $keys, int $limit = -1): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zintercard(...\func_get_args()); + } + + public function zinterstore(mixed $dstkey, array $keys, array|null $weights = null, mixed $options = null): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zinterstore(...\func_get_args()); + } + + public function zunion(array $keys, array|null $weights = null, mixed $options = null): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zunion(...\func_get_args()); + } + + public function zunionstore(mixed $dstkey, array $keys, array|null $weights = null, mixed $options = null): \Relay\Cluster|false|int + { + return $this->initializeLazyObject()->zunionstore(...\func_get_args()); + } + + public function zpopmin(mixed $key, int $count = 1): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zpopmin(...\func_get_args()); + } + + public function zpopmax(mixed $key, int $count = 1): \Relay\Cluster|array|false + { + return $this->initializeLazyObject()->zpopmax(...\func_get_args()); + } + + public function _getKeys(): array|false + { + return $this->initializeLazyObject()->_getKeys(...\func_get_args()); + } + + public function _masters(): array + { + return $this->initializeLazyObject()->_masters(...\func_get_args()); + } + + public function copy(mixed $srckey, mixed $dstkey, array|null $options = null): \Relay\Cluster|bool + { + return $this->initializeLazyObject()->copy(...\func_get_args()); + } +} diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index f2d8a5e9766fb..6185154ef2466 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -14,6 +14,7 @@ use Predis\Response\Error; use Predis\Response\ServerException; use Relay\Relay; +use Relay\Cluster as RelayCluster; use Symfony\Component\Lock\Exception\InvalidTtlException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\LockStorageException; @@ -38,7 +39,7 @@ class RedisStore implements SharedLockStoreInterface * @param float $initialTtl The expiration delay of locks in seconds */ public function __construct( - private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, private float $initialTtl = 300.0, ) { if ($initialTtl <= 0) { @@ -231,14 +232,14 @@ private function evaluate(string $script, string $resource, array $args): mixed { $scriptSha = sha1($script); - if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof RelayCluster || $this->redis instanceof \RedisCluster) { $this->redis->clearLastError(); $result = $this->redis->evalSha($scriptSha, array_merge([$resource], $args), 1); if (null !== ($err = $this->redis->getLastError()) && str_starts_with($err, self::NO_SCRIPT_ERROR_MESSAGE_PREFIX)) { $this->redis->clearLastError(); - if ($this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \RedisCluster || $this->redis instanceof RelayCluster) { foreach ($this->redis->_masters() as $master) { $this->redis->script($master, 'LOAD', $script); } diff --git a/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTestCase.php b/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTestCase.php index 050c4815f7078..af175b1922d07 100644 --- a/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTestCase.php +++ b/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Tests\Store; use Relay\Relay; +use Relay\Cluster as RelayCluster; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; @@ -30,7 +31,7 @@ protected function getClockDelay(): int return 250000; } - abstract protected function getRedisConnection(): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface; + abstract protected function getRedisConnection(): \Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predis\ClientInterface; public function getStore(): PersistingStoreInterface { @@ -55,7 +56,7 @@ public function testBackwardCompatibility() class Symfony51Store { - private \Redis|Relay|\RedisCluster|\RedisArray|\Predis\ClientInterface $redis; + private \Redis|Relay|RelayCluster|\RedisCluster|\RedisArray|\Predis\ClientInterface $redis; public function __construct($redis) { @@ -85,7 +86,7 @@ public function exists(Key $key) private function evaluate(string $script, string $resource, array $args) { - if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof RelayCluster || $this->redis instanceof \RedisCluster) { return $this->redis->eval($script, array_merge([$resource], $args), 1); } diff --git a/src/Symfony/Component/Lock/Tests/Store/RelayClusterStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/RelayClusterStoreTest.php new file mode 100644 index 0000000000000..a22f6f997276c --- /dev/null +++ b/src/Symfony/Component/Lock/Tests/Store/RelayClusterStoreTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Store; + +use Relay\Cluster as RelayCluster; +use Symfony\Component\Lock\Tests\Store\AbstractRedisStoreTestCase; + +/** + * @requires extension relay + * + * @group integration + */ +class RelayClusterStoreTest extends AbstractRedisStoreTestCase +{ + protected function setUp(): void + { + $relayCluster = $this->getRedisConnection(); + + foreach ($relayCluster->_masters() as $hostAndPort) { + $relayCluster->flushdb($hostAndPort); + } + } + + public static function setUpBeforeClass(): void + { + if (!class_exists(RelayCluster::class)) { + self::markTestSkipped('The Relay\Cluster class is required.'); + } + + if (getenv('REDIS_CLUSTER_HOSTS') === false) { + self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + } + + protected function getRedisConnection(): RelayCluster + { + return new RelayCluster('', explode(' ', getenv('REDIS_CLUSTER_HOSTS'))); + } +} diff --git a/src/Symfony/Component/Semaphore/Store/RedisStore.php b/src/Symfony/Component/Semaphore/Store/RedisStore.php index 9e0b52de18865..bb21b5e4cc4b1 100644 --- a/src/Symfony/Component/Semaphore/Store/RedisStore.php +++ b/src/Symfony/Component/Semaphore/Store/RedisStore.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Semaphore\Store; use Relay\Relay; +use Relay\Cluster as RelayCluster; use Symfony\Component\Semaphore\Exception\InvalidArgumentException; use Symfony\Component\Semaphore\Exception\SemaphoreAcquiringException; use Symfony\Component\Semaphore\Exception\SemaphoreExpiredException; @@ -27,7 +28,7 @@ class RedisStore implements PersistingStoreInterface { public function __construct( - private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, ) { } @@ -158,7 +159,7 @@ public function exists(Key $key): bool private function evaluate(string $script, string $resource, array $args): mixed { - if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof RelayCluster || $this->redis instanceof \RedisCluster) { return $this->redis->eval($script, array_merge([$resource], $args), 1); } diff --git a/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTestCase.php b/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTestCase.php index f68ab7f5f3289..f89a877a611ba 100644 --- a/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTestCase.php +++ b/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Semaphore\Tests\Store; use Relay\Relay; +use Relay\Cluster as RelayCluster; use Symfony\Component\Semaphore\PersistingStoreInterface; use Symfony\Component\Semaphore\Store\RedisStore; @@ -20,7 +21,7 @@ */ abstract class AbstractRedisStoreTestCase extends AbstractStoreTestCase { - abstract protected function getRedisConnection(): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface; + abstract protected function getRedisConnection(): \Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predis\ClientInterface; public function getStore(): PersistingStoreInterface { diff --git a/src/Symfony/Component/Semaphore/Tests/Store/RelayClusterStoreTest.php b/src/Symfony/Component/Semaphore/Tests/Store/RelayClusterStoreTest.php new file mode 100644 index 0000000000000..d14159b7f0f1f --- /dev/null +++ b/src/Symfony/Component/Semaphore/Tests/Store/RelayClusterStoreTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Semaphore\Tests\Store; + +use Relay\Cluster as RelayCluster; + +/** + * @requires extension relay + */ +class RelayClusterStoreTest extends AbstractRedisStoreTestCase +{ + public static function setUpBeforeClass(): void + { + if (!class_exists(RelayCluster::class)) { + self::markTestSkipped('The Relay\Cluster class is required.'); + } + + if (getenv('REDIS_CLUSTER_HOSTS') === false) { + self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + } + + protected function getRedisConnection(): RelayCluster + { + return new RelayCluster('', explode(' ', getenv('REDIS_CLUSTER_HOSTS'))); + } +}