From 916032b874b9881c6b91cf9ab05c2cb60459156d Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski <dariusz.ruminski@gmail.com> Date: Wed, 11 Dec 2024 14:08:35 +0100 Subject: [PATCH 01/12] chore: PHP CS Fixer fixes --- Tests/Traits/RedisProxiesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Traits/RedisProxiesTest.php b/Tests/Traits/RedisProxiesTest.php index 1e17b47..0be4060 100644 --- a/Tests/Traits/RedisProxiesTest.php +++ b/Tests/Traits/RedisProxiesTest.php @@ -34,7 +34,7 @@ public function testRedisProxy($class) $expected = substr($proxy, 0, 2 + strpos($proxy, '}')); $methods = []; - foreach ((new \ReflectionClass(sprintf('Symfony\Component\Cache\Traits\\%s%dProxy', $class, $version)))->getMethods() as $method) { + foreach ((new \ReflectionClass(\sprintf('Symfony\Component\Cache\Traits\\%s%dProxy', $class, $version)))->getMethods() as $method) { if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name)) { continue; } From 9c60a16fd4a859f4ac5ff056101f7514aacc6e86 Mon Sep 17 00:00:00 2001 From: ywisax <p4ckits@gmail.com> Date: Fri, 20 Dec 2024 01:55:36 +0800 Subject: [PATCH 02/12] Update PhpFilesAdapter.php, remove goto statement The goto statement can make the code's flow difficult to follow. PHP code is typically expected to have a more linear and understandable structure. For example, when using goto, it can jump from one part of the code to another in a non - sequential manner. This can lead to confusion for developers who are trying to understand the program's logic, especially in larger codebases. --- Adapter/PhpFilesAdapter.php | 94 ++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/Adapter/PhpFilesAdapter.php b/Adapter/PhpFilesAdapter.php index e550276..efb4344 100644 --- a/Adapter/PhpFilesAdapter.php +++ b/Adapter/PhpFilesAdapter.php @@ -104,65 +104,65 @@ protected function doFetch(array $ids): iterable } $values = []; - begin: - $getExpiry = false; - - foreach ($ids as $id) { - if (null === $value = $this->values[$id] ?? null) { - $missingIds[] = $id; - } elseif ('N;' === $value) { - $values[$id] = null; - } elseif (!\is_object($value)) { - $values[$id] = $value; - } elseif (!$value instanceof LazyValue) { - $values[$id] = $value(); - } elseif (false === $values[$id] = include $value->file) { - unset($values[$id], $this->values[$id]); - $missingIds[] = $id; + while (true) { + $getExpiry = false; + + foreach ($ids as $id) { + if (null === $value = $this->values[$id] ?? null) { + $missingIds[] = $id; + } elseif ('N;' === $value) { + $values[$id] = null; + } elseif (!\is_object($value)) { + $values[$id] = $value; + } elseif (!$value instanceof LazyValue) { + $values[$id] = $value(); + } elseif (false === $values[$id] = include $value->file) { + unset($values[$id], $this->values[$id]); + $missingIds[] = $id; + } + if (!$this->appendOnly) { + unset($this->values[$id]); + } } - if (!$this->appendOnly) { - unset($this->values[$id]); + + if (!$missingIds) { + return $values; } - } - if (!$missingIds) { - return $values; - } + set_error_handler($this->includeHandler); + try { + $getExpiry = true; - set_error_handler($this->includeHandler); - try { - $getExpiry = true; + foreach ($missingIds as $k => $id) { + try { + $file = $this->files[$id] ??= $this->getFile($id); - foreach ($missingIds as $k => $id) { - try { - $file = $this->files[$id] ??= $this->getFile($id); + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } - if (isset(self::$valuesCache[$file])) { - [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; - } elseif (\is_array($expiresAt = include $file)) { - if ($this->appendOnly) { - self::$valuesCache[$file] = $expiresAt; + [$expiresAt, $this->values[$id]] = $expiresAt; + } elseif ($now < $expiresAt) { + $this->values[$id] = new LazyValue($file); } - [$expiresAt, $this->values[$id]] = $expiresAt; - } elseif ($now < $expiresAt) { - $this->values[$id] = new LazyValue($file); - } - - if ($now >= $expiresAt) { - unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); + if ($now >= $expiresAt) { + unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); + } + } catch (\ErrorException $e) { + unset($missingIds[$k]); } - } catch (\ErrorException $e) { - unset($missingIds[$k]); } + } finally { + restore_error_handler(); } - } finally { - restore_error_handler(); - } - $ids = $missingIds; - $missingIds = []; - goto begin; + $ids = $missingIds; + $missingIds = []; + } } protected function doHave(string $id): bool From ff1c4cc7d65b52f2ec238e5b3d0fdbab67380354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bok?= <jiri.bok@protonmail.com> Date: Tue, 25 Feb 2025 22:26:37 +0100 Subject: [PATCH 03/12] [Cache] Add `\Relay\Cluster` support --- Adapter/RedisAdapter.php | 2 +- Adapter/RedisTagAwareAdapter.php | 6 +- CHANGELOG.md | 5 + .../Adapter/AbstractRedisAdapterTestCase.php | 3 +- .../RedisTagAwareRelayClusterAdapterTest.php | 40 + Tests/Adapter/RelayClusterAdapterTest.php | 68 + Tests/Traits/RedisProxiesTest.php | 50 + Traits/RedisTrait.php | 109 +- Traits/RelayClusterProxy.php | 1203 +++++++++++++++++ 9 files changed, 1475 insertions(+), 11 deletions(-) create mode 100644 Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php create mode 100644 Tests/Adapter/RelayClusterAdapterTest.php create mode 100644 Traits/RelayClusterProxy.php diff --git a/Adapter/RedisAdapter.php b/Adapter/RedisAdapter.php index e33f2f6..f31f0d7 100644 --- a/Adapter/RedisAdapter.php +++ b/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/Adapter/RedisTagAwareAdapter.php b/Adapter/RedisTagAwareAdapter.php index 7b28237..a887f29 100644 --- a/Adapter/RedisTagAwareAdapter.php +++ b/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/CHANGELOG.md b/CHANGELOG.md index 038915c..b92cd75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.3 +--- + + * Add support for `\Relay\Cluster` in `RedisAdapter` + 7.2 --- diff --git a/Tests/Adapter/AbstractRedisAdapterTestCase.php b/Tests/Adapter/AbstractRedisAdapterTestCase.php index c83365c..03a0f87 100644 --- a/Tests/Adapter/AbstractRedisAdapterTestCase.php +++ b/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/Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php b/Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php new file mode 100644 index 0000000..4939d2d --- /dev/null +++ b/Tests/Adapter/RedisTagAwareRelayClusterAdapterTest.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * 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/Tests/Adapter/RelayClusterAdapterTest.php b/Tests/Adapter/RelayClusterAdapterTest.php new file mode 100644 index 0000000..1f9718a --- /dev/null +++ b/Tests/Adapter/RelayClusterAdapterTest.php @@ -0,0 +1,68 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * 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/Tests/Traits/RedisProxiesTest.php b/Tests/Traits/RedisProxiesTest.php index 0be4060..47767a8 100644 --- a/Tests/Traits/RedisProxiesTest.php +++ b/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".<<<EOPHP + { + {$return}\$this->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".<<<EOPHP + { + {$return}\$this->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/Traits/RedisTrait.php b/Traits/RedisTrait.php index f6bb9bb..480e4f7 100644 --- a/Traits/RedisTrait.php +++ b/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/Traits/RelayClusterProxy.php b/Traits/RelayClusterProxy.php new file mode 100644 index 0000000..92466e5 --- /dev/null +++ b/Traits/RelayClusterProxy.php @@ -0,0 +1,1203 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * 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()); + } +} From 32c7382d54c95b5cb8d575a009fb5164c726c742 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas <nicolas.grekas@gmail.com> Date: Wed, 26 Feb 2025 18:04:06 +0100 Subject: [PATCH 04/12] Add support for `valkey:` / `valkeys:` schemes --- Adapter/AbstractAdapter.php | 4 +- CHANGELOG.md | 2 + Tests/Adapter/RedisAdapterSentinelTest.php | 8 +-- Traits/RedisTrait.php | 75 +++++++++++++--------- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/Adapter/AbstractAdapter.php b/Adapter/AbstractAdapter.php index c03868d..8cd2218 100644 --- a/Adapter/AbstractAdapter.php +++ b/Adapter/AbstractAdapter.php @@ -111,7 +111,7 @@ public static function createSystemCache(string $namespace, int $defaultLifetime public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): mixed { - if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) { + if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:') || str_starts_with($dsn, 'valkey:') || str_starts_with($dsn, 'valkeys:')) { return RedisAdapter::createConnection($dsn, $options); } if (str_starts_with($dsn, 'memcached:')) { @@ -128,7 +128,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra return PdoAdapter::createConnection($dsn, $options); } - throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:", "couchbase:", "mysql:", "oci:", "pgsql:", "sqlsrv:" nor "sqlite:".'); + throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "valkey[s]:", "memcached:", "couchbase:", "mysql:", "oci:", "pgsql:", "sqlsrv:" nor "sqlite:".'); } public function commit(): bool diff --git a/CHANGELOG.md b/CHANGELOG.md index b92cd75..59195f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG --- * Add support for `\Relay\Cluster` in `RedisAdapter` + * Add support for `valkey:` / `valkeys:` schemes + * Rename options "redis_cluster" and "redis_sentinel" to "cluster" and "sentinel" respectively 7.2 --- diff --git a/Tests/Adapter/RedisAdapterSentinelTest.php b/Tests/Adapter/RedisAdapterSentinelTest.php index 6dc13b8..9103eec 100644 --- a/Tests/Adapter/RedisAdapterSentinelTest.php +++ b/Tests/Adapter/RedisAdapterSentinelTest.php @@ -32,15 +32,15 @@ public static function setUpBeforeClass(): void self::markTestSkipped('REDIS_SENTINEL_SERVICE env var is not defined.'); } - self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']&timeout=0&retry_interval=0&read_timeout=0', ['redis_sentinel' => $service, 'prefix' => 'prefix_']); + self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']&timeout=0&retry_interval=0&read_timeout=0', ['sentinel' => $service, 'prefix' => 'prefix_']); } public function testInvalidDSNHasBothClusterAndSentinel() { - $dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&redis_cluster=1&redis_sentinel=mymaster'; + $dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&cluster=1&sentinel=mymaster'; $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); + $this->expectExceptionMessage('Cannot use both "cluster" and "sentinel" at the same time.'); RedisAdapter::createConnection($dsn); } @@ -51,6 +51,6 @@ public function testExceptionMessageWhenFailingToRetrieveMasterInformation() $dsn = 'redis:?host['.str_replace(' ', ']&host[', $hosts).']'; $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Failed to retrieve master information from sentinel "invalid-masterset-name".'); - AbstractAdapter::createConnection($dsn, ['redis_sentinel' => 'invalid-masterset-name']); + AbstractAdapter::createConnection($dsn, ['sentinel' => 'invalid-masterset-name']); } } diff --git a/Traits/RedisTrait.php b/Traits/RedisTrait.php index 480e4f7..09a8e23 100644 --- a/Traits/RedisTrait.php +++ b/Traits/RedisTrait.php @@ -46,9 +46,10 @@ trait RedisTrait 'retry_interval' => 0, 'tcp_keepalive' => 0, 'lazy' => null, - 'redis_cluster' => false, - 'relay_cluster_context' => [], 'redis_sentinel' => null, + 'cluster' => false, + 'sentinel' => null, + 'relay_cluster_context' => [], 'dbindex' => 0, 'failover' => 'none', 'ssl' => null, // see https://php.net/context.ssl @@ -90,13 +91,13 @@ private function init(\Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predi */ public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay|RelayCluster { - if (str_starts_with($dsn, 'redis:')) { - $scheme = 'redis'; - } elseif (str_starts_with($dsn, 'rediss:')) { - $scheme = 'rediss'; - } else { - throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:".'); - } + $scheme = match (true) { + str_starts_with($dsn, 'redis:') => 'redis', + str_starts_with($dsn, 'rediss:') => 'rediss', + str_starts_with($dsn, 'valkey:') => 'valkey', + str_starts_with($dsn, 'valkeys:') => 'valkeys', + default => throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:" nor "valkey[s]:".'), + }; if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) { throw new CacheException('Cannot find the "redis" extension nor the "predis/predis" package.'); @@ -124,7 +125,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $query = $hosts = []; - $tls = 'rediss' === $scheme; + $tls = 'rediss' === $scheme || 'valkeys' === $scheme; $tcpScheme = $tls ? 'tls' : 'tcp'; if (isset($params['query'])) { @@ -177,32 +178,41 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $params += $query + $options + self::$defaultConnectionOptions; - if (isset($params['redis_sentinel']) && isset($params['sentinel_master'])) { - throw new InvalidArgumentException('Cannot use both "redis_sentinel" and "sentinel_master" at the same time.'); + $aliases = [ + 'sentinel_master' => 'sentinel', + 'redis_sentinel' => 'sentinel', + 'redis_cluster' => 'cluster', + ]; + foreach ($aliases as $alias => $key) { + $params[$key] = match (true) { + \array_key_exists($key, $query) => $query[$key], + \array_key_exists($alias, $query) => $query[$alias], + \array_key_exists($key, $options) => $options[$key], + \array_key_exists($alias, $options) => $options[$alias], + default => $params[$key], + }; } - $params['redis_sentinel'] ??= $params['sentinel_master'] ?? null; - - if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + if (isset($params['sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".'); } if (isset($params['lazy'])) { $params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); } + $params['cluster'] = filter_var($params['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.'); + if ($params['cluster'] && isset($params['sentinel'])) { + throw new InvalidArgumentException('Cannot use both "cluster" and "sentinel" at the same time.'); } $class = $params['class'] ?? match (true) { - $params['redis_cluster'] => match (true) { + $params['cluster'] => match (true) { \extension_loaded('redis') => \RedisCluster::class, \extension_loaded('relay') => RelayCluster::class, default => \Predis\Client::class, }, - isset($params['redis_sentinel']) => match (true) { + isset($params['sentinel']) => match (true) { \extension_loaded('redis') => \Redis::class, \extension_loaded('relay') => Relay::class, default => \Predis\Client::class, @@ -213,7 +223,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra default => \Predis\Client::class, }; - if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + if (isset($params['sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { throw new CacheException(\sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and neither ext-redis >= 5.2 nor ext-relay have been found.', $class)); } @@ -237,7 +247,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $host = 'tls://'.$host; } - if (!isset($params['redis_sentinel'])) { + if (!isset($params['sentinel'])) { break; } @@ -263,15 +273,15 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $sentinel = @new $sentinelClass($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); } - if ($address = @$sentinel->getMasterAddrByName($params['redis_sentinel'])) { + if ($address = @$sentinel->getMasterAddrByName($params['sentinel'])) { [$host, $port] = $address; } } catch (\RedisException|\Relay\Exception $redisException) { } } while (++$hostIndex < \count($hosts) && !$address); - if (isset($params['redis_sentinel']) && !$address) { - throw new InvalidArgumentException(\sprintf('Failed to retrieve master information from sentinel "%s".', $params['redis_sentinel']), previous: $redisException ?? null); + if (isset($params['sentinel']) && !$address) { + throw new InvalidArgumentException(\sprintf('Failed to retrieve master information from sentinel "%s".', $params['sentinel']), previous: $redisException ?? null); } try { @@ -446,11 +456,14 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $redis = $params['lazy'] ? RedisClusterProxy::createLazyProxy($initializer) : $initializer(); } elseif (is_a($class, \Predis\ClientInterface::class, true)) { - if ($params['redis_cluster']) { + if ($params['cluster']) { $params['cluster'] = 'redis'; - } elseif (isset($params['redis_sentinel'])) { + } else { + unset($params['cluster']); + } + if (isset($params['sentinel'])) { $params['replication'] = 'sentinel'; - $params['service'] = $params['redis_sentinel']; + $params['service'] = $params['sentinel']; } $params += ['parameters' => []]; $params['parameters'] += [ @@ -478,7 +491,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra } } - if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) { + if (1 === \count($hosts) && !isset($params['cluster']) & !isset($params['sentinel'])) { $hosts = $hosts[0]; } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) { $params['replication'] = true; @@ -486,8 +499,8 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra } $params['exceptions'] = false; - $redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions)); - if (isset($params['redis_sentinel'])) { + $redis = new $class($hosts, array_diff_key($params, array_diff_key(self::$defaultConnectionOptions, ['cluster' => null]))); + if (isset($params['sentinel'])) { $redis->getConnection()->setSentinelTimeout($params['timeout']); } } elseif (class_exists($class, false)) { From 02867f81756a00a6d24ece686f8c851c7f47c9d2 Mon Sep 17 00:00:00 2001 From: Christian Flothmann <christian.flothmann@open.de> Date: Sun, 2 Mar 2025 16:03:52 +0100 Subject: [PATCH 05/12] replace assertEmpty() with stricter assertions --- Tests/Adapter/DoctrineDbalAdapterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Adapter/DoctrineDbalAdapterTest.php b/Tests/Adapter/DoctrineDbalAdapterTest.php index 79752f3..db20b0f 100644 --- a/Tests/Adapter/DoctrineDbalAdapterTest.php +++ b/Tests/Adapter/DoctrineDbalAdapterTest.php @@ -117,7 +117,7 @@ public function testConfigureSchemaTableExists() $adapter = new DoctrineDbalAdapter($connection); $adapter->configureSchema($schema, $connection, fn () => true); $table = $schema->getTable('cache_items'); - $this->assertEmpty($table->getColumns(), 'The table was not overwritten'); + $this->assertSame([], $table->getColumns(), 'The table was not overwritten'); } /** From 317aa1ad6be9ff1058988e1305b0b1a420c18b38 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski <dariusz.ruminski@gmail.com> Date: Sat, 8 Mar 2025 00:16:33 +0100 Subject: [PATCH 06/12] chore: PHP CS Fixer fixes --- Tests/Adapter/AbstractRedisAdapterTestCase.php | 2 +- Tests/Adapter/PredisReplicationAdapterTest.php | 4 ++-- Tests/Adapter/RelayClusterAdapterTest.php | 4 ++-- Tests/Traits/RedisProxiesTest.php | 3 +-- Traits/RedisTrait.php | 14 +++++++------- Traits/RelayClusterProxy.php | 13 +++++++------ 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Tests/Adapter/AbstractRedisAdapterTestCase.php b/Tests/Adapter/AbstractRedisAdapterTestCase.php index 03a0f87..c139cc9 100644 --- a/Tests/Adapter/AbstractRedisAdapterTestCase.php +++ b/Tests/Adapter/AbstractRedisAdapterTestCase.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Psr\Cache\CacheItemPoolInterface; -use Relay\Relay; use Relay\Cluster as RelayCluster; +use Relay\Relay; use Symfony\Component\Cache\Adapter\RedisAdapter; abstract class AbstractRedisAdapterTestCase extends AdapterTestCase diff --git a/Tests/Adapter/PredisReplicationAdapterTest.php b/Tests/Adapter/PredisReplicationAdapterTest.php index 28af1b5..b987723 100644 --- a/Tests/Adapter/PredisReplicationAdapterTest.php +++ b/Tests/Adapter/PredisReplicationAdapterTest.php @@ -27,9 +27,9 @@ public static function setUpBeforeClass(): void $hosts = explode(' ', getenv('REDIS_REPLICATION_HOSTS')); $lastArrayKey = array_key_last($hosts); $hostTable = []; - foreach($hosts as $key => $host) { + foreach ($hosts as $key => $host) { $hostInformation = array_combine(['host', 'port'], explode(':', $host)); - if($lastArrayKey === $key) { + if ($lastArrayKey === $key) { $hostInformation['role'] = 'master'; } $hostTable[] = $hostInformation; diff --git a/Tests/Adapter/RelayClusterAdapterTest.php b/Tests/Adapter/RelayClusterAdapterTest.php index 1f9718a..56363f8 100644 --- a/Tests/Adapter/RelayClusterAdapterTest.php +++ b/Tests/Adapter/RelayClusterAdapterTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Cache\Tests\Adapter; -use Relay\Relay; -use Relay\Cluster as RelayCluster; use Psr\Cache\CacheItemPoolInterface; +use Relay\Cluster as RelayCluster; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\Cache\Exception\InvalidArgumentException; diff --git a/Tests/Traits/RedisProxiesTest.php b/Tests/Traits/RedisProxiesTest.php index 47767a8..051275d 100644 --- a/Tests/Traits/RedisProxiesTest.php +++ b/Tests/Traits/RedisProxiesTest.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Cache\Tests\Traits; use PHPUnit\Framework\TestCase; -use Relay\Relay; use Relay\Cluster as RelayCluster; +use Relay\Relay; use Symfony\Component\Cache\Traits\RedisProxyTrait; use Symfony\Component\Cache\Traits\RelayClusterProxy; use Symfony\Component\Cache\Traits\RelayProxy; @@ -124,7 +124,6 @@ public function testRelayProxy() $this->assertEquals($expectedProxy, $proxy); } - /** * @requires extension relay */ diff --git a/Traits/RedisTrait.php b/Traits/RedisTrait.php index 09a8e23..51c59aa 100644 --- a/Traits/RedisTrait.php +++ b/Traits/RedisTrait.php @@ -20,8 +20,8 @@ use Predis\Connection\Replication\ReplicationInterface as Predis2ReplicationInterface; use Predis\Response\ErrorInterface; use Predis\Response\Status; -use Relay\Relay; use Relay\Cluster as RelayCluster; +use Relay\Relay; use Relay\Sentinel; use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; @@ -382,20 +382,20 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra try { $relayClusterContext = $params['relay_cluster_context']; - foreach (['allow_self_signed', 'verify_peer_name','verify_peer'] as $contextStreamBoolField) { - if(isset($relayClusterContext['stream'][$contextStreamBoolField])) { + 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])) { + 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])) { + foreach (['max-retries', 'serializer', 'compression', 'compression-level'] as $contextIntField) { + if (isset($relayClusterContext[$contextIntField])) { $relayClusterContext[$contextIntField] = filter_var($relayClusterContext[$contextIntField], \FILTER_VALIDATE_INT); } } diff --git a/Traits/RelayClusterProxy.php b/Traits/RelayClusterProxy.php index 92466e5..6d12434 100644 --- a/Traits/RelayClusterProxy.php +++ b/Traits/RelayClusterProxy.php @@ -37,7 +37,7 @@ public function __construct( int|float $command_timeout = 0, bool $persistent = false, #[\SensitiveParameter] mixed $auth = null, - array|null $context = null + array|null $context = null, ) { $this->initializeLazyObject()->__construct(...\func_get_args()); } @@ -157,7 +157,6 @@ 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()); @@ -383,12 +382,13 @@ public function bitpos(mixed $key, int $bit, ?int $start = null, ?int $end = nul 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 + public function blmove(mixed $srckey, mixed $dstkey, string $srcpos, string $dstpos, float $timeout): \Relay\Cluster|string|false|null { return $this->initializeLazyObject()->blmove(...\func_get_args()); } - public function lmove(mixed $srckey, mixed $dstkey, string $srcpos, string $dstpos): Cluster|string|null|false { + public function lmove(mixed $srckey, mixed $dstkey, string $srcpos, string $dstpos): Cluster|string|false|null + { return $this->initializeLazyObject()->lmove(...\func_get_args()); } @@ -662,7 +662,8 @@ public function ltrim(mixed $key, int $start, int $end): \Relay\Cluster|bool return $this->initializeLazyObject()->ltrim(...\func_get_args()); } - public static function maxMemory(): int { + public static function maxMemory(): int + { return \Relay\Cluster::maxMemory(); } @@ -1083,7 +1084,7 @@ public function zrangestore(mixed $dstkey, mixed $srckey, mixed $start, mixed $e public function zrank(mixed $key, mixed $rank, bool $withscore = false): Cluster|array|int|false { - return $this->initializeLazyObject()->zrank(...\func_get_args()); + 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 From 3dd8df484ecc6cf29b0797051a296d57c4f38ea6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann <christian.flothmann@open.de> Date: Thu, 13 Mar 2025 12:44:16 +0100 Subject: [PATCH 07/12] remove legacy "redis_sentinel" option (it was replaced "sentinel") --- Traits/RedisTrait.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Traits/RedisTrait.php b/Traits/RedisTrait.php index 51c59aa..3d1a0d9 100644 --- a/Traits/RedisTrait.php +++ b/Traits/RedisTrait.php @@ -46,7 +46,6 @@ trait RedisTrait 'retry_interval' => 0, 'tcp_keepalive' => 0, 'lazy' => null, - 'redis_sentinel' => null, 'cluster' => false, 'sentinel' => null, 'relay_cluster_context' => [], From 382df8bd6ae784144926e95da0dbb120bf27fdd5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas <nicolas.grekas@gmail.com> Date: Wed, 5 Mar 2025 10:13:22 +0100 Subject: [PATCH 08/12] [Cache] Code cleanup --- Tests/Adapter/PredisAdapterTest.php | 4 +- Traits/RedisTrait.php | 80 +++++++++++++---------------- 2 files changed, 38 insertions(+), 46 deletions(-) diff --git a/Tests/Adapter/PredisAdapterTest.php b/Tests/Adapter/PredisAdapterTest.php index d9afd85..730bde7 100644 --- a/Tests/Adapter/PredisAdapterTest.php +++ b/Tests/Adapter/PredisAdapterTest.php @@ -42,7 +42,7 @@ public function testCreateConnection() 'scheme' => 'tcp', 'host' => $redisHost[0], 'port' => (int) ($redisHost[1] ?? 6379), - 'persistent' => 0, + 'persistent' => false, 'timeout' => 3, 'read_write_timeout' => 0, 'tcp_nodelay' => true, @@ -74,7 +74,7 @@ public function testCreateSslConnection() 'host' => $redisHost[0], 'port' => (int) ($redisHost[1] ?? 6379), 'ssl' => ['verify_peer' => '0'], - 'persistent' => 0, + 'persistent' => false, 'timeout' => 3, 'read_write_timeout' => 0, 'tcp_nodelay' => true, diff --git a/Traits/RedisTrait.php b/Traits/RedisTrait.php index 3d1a0d9..55e7cea 100644 --- a/Traits/RedisTrait.php +++ b/Traits/RedisTrait.php @@ -38,17 +38,17 @@ trait RedisTrait { private static array $defaultConnectionOptions = [ 'class' => null, - 'persistent' => 0, + 'persistent' => false, 'persistent_id' => null, 'timeout' => 30, 'read_timeout' => 0, - 'command_timeout' => 0, 'retry_interval' => 0, 'tcp_keepalive' => 0, 'lazy' => null, 'cluster' => false, + 'cluster_command_timeout' => 0, + 'cluster_relay_context' => [], 'sentinel' => null, - 'relay_cluster_context' => [], 'dbindex' => 0, 'failover' => 'none', 'ssl' => null, // see https://php.net/context.ssl @@ -196,10 +196,11 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".'); } - if (isset($params['lazy'])) { - $params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); + foreach (['lazy', 'persistent', 'cluster'] as $option) { + if (!\is_bool($params[$option] ?? false)) { + $params[$option] = filter_var($params[$option], \FILTER_VALIDATE_BOOLEAN); + } } - $params['cluster'] = filter_var($params['cluster'], \FILTER_VALIDATE_BOOLEAN); if ($params['cluster'] && isset($params['sentinel'])) { throw new InvalidArgumentException('Cannot use both "cluster" and "sentinel" at the same time.'); @@ -285,24 +286,9 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra try { $extra = [ - 'stream' => $params['ssl'] ?? null, - ]; - $booleanStreamOptions = [ - 'allow_self_signed', - 'capture_peer_cert', - 'capture_peer_cert_chain', - 'disable_compression', - 'SNI_enabled', - 'verify_peer', - 'verify_peer_name', + 'stream' => self::filterSslOptions($params['ssl'] ?? []) ?: null, ]; - foreach ($extra['stream'] ?? [] as $streamOption => $value) { - if (\in_array($streamOption, $booleanStreamOptions, true) && \is_string($value)) { - $extra['stream'][$streamOption] = filter_var($value, \FILTER_VALIDATE_BOOL); - } - } - if (isset($params['auth'])) { $extra['auth'] = $params['auth']; } @@ -379,34 +365,27 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra } 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); - } + $context = $params['cluster_relay_context']; + $context['stream'] = self::filterSslOptions($params['ssl'] ?? []) ?: null; + + foreach ($context as $name => $value) { + match ($name) { + 'use-cache', 'client-tracking', 'throw-on-error', 'client-invalidations', 'reply-literal', 'persistent', + => $context[$name] = filter_var($value, \FILTER_VALIDATE_BOOLEAN), + 'max-retries', 'serializer', 'compression', 'compression-level', + => $context[$name] = filter_var($value, \FILTER_VALIDATE_INT), + default => null, + }; } $relayCluster = new $class( name: null, seeds: $hosts, connect_timeout: $params['timeout'], - command_timeout: $params['command_timeout'], - persistent: (bool) $params['persistent'], + command_timeout: $params['cluster_command_timeout'], + persistent: $params['persistent'], auth: $params['auth'] ?? null, - context: $relayClusterContext + context: $context, ); } catch (\Relay\Exception $e) { throw new InvalidArgumentException('Relay cluster connection failed: '.$e->getMessage()); @@ -435,7 +414,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra } try { - $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []); + $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []); } catch (\RedisClusterException $e) { throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } @@ -796,4 +775,17 @@ private function getHosts(): array return $hosts; } + + private static function filterSslOptions(array $options): array + { + foreach ($options as $name => $value) { + match ($name) { + 'allow_self_signed', 'capture_peer_cert', 'capture_peer_cert_chain', 'disable_compression', 'SNI_enabled', 'verify_peer', 'verify_peer_name', + => $options[$name] = filter_var($value, \FILTER_VALIDATE_BOOLEAN), + default => null, + }; + } + + return $options; + } } From 82dfb7abdfd28f3227a5dd40544ae72c5bccdcba Mon Sep 17 00:00:00 2001 From: Nicolas Grekas <nicolas.grekas@gmail.com> Date: Wed, 19 Feb 2025 15:04:36 +0100 Subject: [PATCH 09/12] [Cache] Enable namespace-based invalidation by prefixing keys with backend-native namespace separators --- Adapter/AbstractAdapter.php | 23 +++++-- Adapter/AbstractTagAwareAdapter.php | 38 ++++++++--- Adapter/ArrayAdapter.php | 41 ++++++++++-- Adapter/ChainAdapter.php | 21 +++++- Adapter/DoctrineDbalAdapter.php | 6 +- Adapter/NullAdapter.php | 8 ++- Adapter/PdoAdapter.php | 6 +- Adapter/ProxyAdapter.php | 30 +++++++-- Adapter/RedisTagAwareAdapter.php | 2 +- Adapter/TagAwareAdapter.php | 21 +++++- Adapter/TraceableAdapter.php | 29 ++++++++- CHANGELOG.md | 1 + Exception/BadMethodCallException.php | 25 ++++++++ Tests/Adapter/AdapterTestCase.php | 28 ++++++++ Tests/Adapter/PhpArrayAdapterTest.php | 2 + .../PhpArrayAdapterWithFallbackTest.php | 2 + Tests/Adapter/TagAwareTestTrait.php | 23 +++++++ Tests/Psr16CacheProxyTest.php | 4 +- Traits/AbstractAdapterTrait.php | 64 +++++++++++++------ composer.json | 2 +- 20 files changed, 317 insertions(+), 59 deletions(-) create mode 100644 Exception/BadMethodCallException.php diff --git a/Adapter/AbstractAdapter.php b/Adapter/AbstractAdapter.php index 8cd2218..2b4bc8b 100644 --- a/Adapter/AbstractAdapter.php +++ b/Adapter/AbstractAdapter.php @@ -19,11 +19,12 @@ use Symfony\Component\Cache\Traits\AbstractAdapterTrait; use Symfony\Component\Cache\Traits\ContractsTrait; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; /** * @author Nicolas Grekas <p@tchwork.com> */ -abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, NamespacedPoolInterface, LoggerAwareInterface, ResettableInterface { use AbstractAdapterTrait; use ContractsTrait; @@ -37,7 +38,19 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg protected function __construct(string $namespace = '', int $defaultLifetime = 0) { - $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR; + if ('' !== $namespace) { + if (str_contains($namespace, static::NS_SEPARATOR)) { + if (str_contains($namespace, static::NS_SEPARATOR.static::NS_SEPARATOR)) { + throw new InvalidArgumentException(\sprintf('Cache namespace "%s" contains empty sub-namespace.', $namespace)); + } + CacheItem::validateKey(str_replace(static::NS_SEPARATOR, '', $namespace)); + } else { + CacheItem::validateKey($namespace); + } + $this->namespace = $namespace.static::NS_SEPARATOR; + } + $this->rootNamespace = $this->namespace; + $this->defaultLifetime = $defaultLifetime; if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { throw new InvalidArgumentException(\sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); @@ -118,7 +131,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra return MemcachedAdapter::createConnection($dsn, $options); } if (str_starts_with($dsn, 'couchbase:')) { - if (class_exists('CouchbaseBucket') && CouchbaseBucketAdapter::isSupported()) { + if (class_exists(\CouchbaseBucket::class) && CouchbaseBucketAdapter::isSupported()) { return CouchbaseBucketAdapter::createConnection($dsn, $options); } @@ -159,7 +172,7 @@ public function commit(): bool $v = $values[$id]; $type = get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); - CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->rootNamespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); } } else { foreach ($values as $id => $v) { @@ -182,7 +195,7 @@ public function commit(): bool $ok = false; $type = get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); - CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->rootNamespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); } } diff --git a/Adapter/AbstractTagAwareAdapter.php b/Adapter/AbstractTagAwareAdapter.php index 822c30f..23db2b6 100644 --- a/Adapter/AbstractTagAwareAdapter.php +++ b/Adapter/AbstractTagAwareAdapter.php @@ -17,6 +17,7 @@ use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\AbstractAdapterTrait; use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\NamespacedPoolInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; /** @@ -30,16 +31,33 @@ * * @internal */ -abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface +abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, AdapterInterface, NamespacedPoolInterface, LoggerAwareInterface, ResettableInterface { use AbstractAdapterTrait; use ContractsTrait; + /** + * @internal + */ + protected const NS_SEPARATOR = ':'; + private const TAGS_PREFIX = "\1tags\1"; protected function __construct(string $namespace = '', int $defaultLifetime = 0) { - $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':'; + if ('' !== $namespace) { + if (str_contains($namespace, static::NS_SEPARATOR)) { + if (str_contains($namespace, static::NS_SEPARATOR.static::NS_SEPARATOR)) { + throw new InvalidArgumentException(\sprintf('Cache namespace "%s" contains empty sub-namespace.', $namespace)); + } + CacheItem::validateKey(str_replace(static::NS_SEPARATOR, '', $namespace)); + } else { + CacheItem::validateKey($namespace); + } + $this->namespace = $namespace.static::NS_SEPARATOR; + } + $this->rootNamespace = $this->namespace; + $this->defaultLifetime = $defaultLifetime; if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) { throw new InvalidArgumentException(\sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace)); @@ -70,7 +88,7 @@ static function ($key, $value, $isHit) { CacheItem::class ); self::$mergeByLifetime ??= \Closure::bind( - static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) { + static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime, $rootNamespace) { $byLifetime = []; $now = microtime(true); $expiredIds = []; @@ -102,10 +120,10 @@ static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) $value['tag-operations'] = ['add' => [], 'remove' => []]; $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? []; foreach (array_diff_key($value['tags'], $oldTags) as $addedTag) { - $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag); + $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag, $rootNamespace); } foreach (array_diff_key($oldTags, $value['tags']) as $removedTag) { - $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag); + $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag, $rootNamespace); } $value['tags'] = array_keys($value['tags']); @@ -168,7 +186,7 @@ protected function doDeleteYieldTags(array $ids): iterable public function commit(): bool { $ok = true; - $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, $this->getId(...), self::TAGS_PREFIX, $this->defaultLifetime); + $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, $this->getId(...), self::TAGS_PREFIX, $this->defaultLifetime, $this->rootNamespace); $retry = $this->deferred = []; if ($expiredIds) { @@ -195,7 +213,7 @@ public function commit(): bool $v = $values[$id]; $type = get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); - CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->rootNamespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); } } else { foreach ($values as $id => $v) { @@ -219,7 +237,7 @@ public function commit(): bool $ok = false; $type = get_debug_type($v); $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); - CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->rootNamespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); } } @@ -244,7 +262,7 @@ public function deleteItems(array $keys): bool try { foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) { foreach ($tags as $tag) { - $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id; + $tagData[$this->getId(self::TAGS_PREFIX.$tag, $this->rootNamespace)][] = $id; } } } catch (\Exception) { @@ -283,7 +301,7 @@ public function invalidateTags(array $tags): bool $tagIds = []; foreach (array_unique($tags) as $tag) { - $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag); + $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag, $this->rootNamespace); } try { diff --git a/Adapter/ArrayAdapter.php b/Adapter/ArrayAdapter.php index 7b92387..7deb9dc 100644 --- a/Adapter/ArrayAdapter.php +++ b/Adapter/ArrayAdapter.php @@ -19,6 +19,7 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\ResettableInterface; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; /** * An in-memory cache storage. @@ -27,13 +28,14 @@ * * @author Nicolas Grekas <p@tchwork.com> */ -class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface +class ArrayAdapter implements AdapterInterface, CacheInterface, NamespacedPoolInterface, LoggerAwareInterface, ResettableInterface { use LoggerAwareTrait; private array $values = []; private array $tags = []; private array $expiries = []; + private array $subPools = []; private static \Closure $createCacheItem; @@ -226,16 +228,38 @@ public function clear(string $prefix = ''): bool } } - if ($this->values) { - return true; - } + return true; + } + + foreach ($this->subPools as $pool) { + $pool->clear(); } - $this->values = $this->tags = $this->expiries = []; + $this->subPools = $this->values = $this->tags = $this->expiries = []; return true; } + public function withSubNamespace(string $namespace): static + { + CacheItem::validateKey($namespace); + + $subPools = $this->subPools; + + if (isset($subPools[$namespace])) { + return $subPools[$namespace]; + } + + $this->subPools = []; + $clone = clone $this; + $clone->clear(); + + $subPools[$namespace] = $clone; + $this->subPools = $subPools; + + return $clone; + } + /** * Returns all cached values, with cache miss as null. */ @@ -263,6 +287,13 @@ public function reset(): void $this->clear(); } + public function __clone() + { + foreach ($this->subPools as $i => $pool) { + $this->subPools[$i] = clone $pool; + } + } + private function generateItems(array $keys, float $now, \Closure $f): \Generator { foreach ($keys as $i => $key) { diff --git a/Adapter/ChainAdapter.php b/Adapter/ChainAdapter.php index 09fcfdc..c27faeb 100644 --- a/Adapter/ChainAdapter.php +++ b/Adapter/ChainAdapter.php @@ -14,11 +14,13 @@ use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\BadMethodCallException; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\ContractsTrait; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; use Symfony\Contracts\Service\ResetInterface; /** @@ -29,7 +31,7 @@ * * @author Kévin Dunglas <dunglas@gmail.com> */ -class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +class ChainAdapter implements AdapterInterface, CacheInterface, NamespacedPoolInterface, PruneableInterface, ResettableInterface { use ContractsTrait; @@ -280,6 +282,23 @@ public function prune(): bool return $pruned; } + public function withSubNamespace(string $namespace): static + { + $clone = clone $this; + $adapters = []; + + foreach ($this->adapters as $adapter) { + if (!$adapter instanceof NamespacedPoolInterface) { + throw new BadMethodCallException('All adapters must implement NamespacedPoolInterface to support namespaces.'); + } + + $adapters[] = $adapter->withSubNamespace($namespace); + } + $clone->adapters = $adapters; + + return $clone; + } + public function reset(): void { foreach ($this->adapters as $adapter) { diff --git a/Adapter/DoctrineDbalAdapter.php b/Adapter/DoctrineDbalAdapter.php index c69c777..7c5379e 100644 --- a/Adapter/DoctrineDbalAdapter.php +++ b/Adapter/DoctrineDbalAdapter.php @@ -335,17 +335,17 @@ protected function doSave(array $values, int $lifetime): array|bool /** * @internal */ - protected function getId(mixed $key): string + protected function getId(mixed $key, ?string $namespace = null): string { if ('pgsql' !== $this->platformName ??= $this->getPlatformName()) { - return parent::getId($key); + return parent::getId($key, $namespace); } if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { $key = rawurlencode($key); } - return parent::getId($key); + return parent::getId($key, $namespace); } private function getPlatformName(): string diff --git a/Adapter/NullAdapter.php b/Adapter/NullAdapter.php index d5d2ef6..35553ea 100644 --- a/Adapter/NullAdapter.php +++ b/Adapter/NullAdapter.php @@ -14,11 +14,12 @@ use Psr\Cache\CacheItemInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; /** * @author Titouan Galopin <galopintitouan@gmail.com> */ -class NullAdapter implements AdapterInterface, CacheInterface +class NullAdapter implements AdapterInterface, CacheInterface, NamespacedPoolInterface { private static \Closure $createCacheItem; @@ -94,6 +95,11 @@ public function delete(string $key): bool return $this->deleteItem($key); } + public function withSubNamespace(string $namespace): static + { + return clone $this; + } + private function generateItems(array $keys): \Generator { $f = self::$createCacheItem; diff --git a/Adapter/PdoAdapter.php b/Adapter/PdoAdapter.php index 525e2c6..7d6cb2d 100644 --- a/Adapter/PdoAdapter.php +++ b/Adapter/PdoAdapter.php @@ -348,17 +348,17 @@ protected function doSave(array $values, int $lifetime): array|bool /** * @internal */ - protected function getId(mixed $key): string + protected function getId(mixed $key, ?string $namespace = null): string { if ('pgsql' !== $this->getDriver()) { - return parent::getId($key); + return parent::getId($key, $namespace); } if (str_contains($key, "\0") || str_contains($key, '%') || !preg_match('//u', $key)) { $key = rawurlencode($key); } - return parent::getId($key); + return parent::getId($key, $namespace); } private function getConnection(): \PDO diff --git a/Adapter/ProxyAdapter.php b/Adapter/ProxyAdapter.php index 5621226..d692dbf 100644 --- a/Adapter/ProxyAdapter.php +++ b/Adapter/ProxyAdapter.php @@ -19,11 +19,12 @@ use Symfony\Component\Cache\Traits\ContractsTrait; use Symfony\Component\Cache\Traits\ProxyTrait; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; /** * @author Nicolas Grekas <p@tchwork.com> */ -class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +class ProxyAdapter implements AdapterInterface, NamespacedPoolInterface, CacheInterface, PruneableInterface, ResettableInterface { use ContractsTrait; use ProxyTrait; @@ -38,12 +39,17 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0) { - $this->pool = $pool; - $this->poolHash = spl_object_hash($pool); if ('' !== $namespace) { - \assert('' !== CacheItem::validateKey($namespace)); - $this->namespace = $namespace; + if ($pool instanceof NamespacedPoolInterface) { + $pool = $pool->withSubNamespace($namespace); + $this->namespace = $namespace = ''; + } else { + \assert('' !== CacheItem::validateKey($namespace)); + $this->namespace = $namespace; + } } + $this->pool = $pool; + $this->poolHash = spl_object_hash($pool); $this->namespaceLen = \strlen($namespace); $this->defaultLifetime = $defaultLifetime; self::$createCacheItem ??= \Closure::bind( @@ -158,6 +164,20 @@ public function commit(): bool return $this->pool->commit(); } + public function withSubNamespace(string $namespace): static + { + $clone = clone $this; + + if ($clone->pool instanceof NamespacedPoolInterface) { + $clone->pool = $clone->pool->withSubNamespace($namespace); + } else { + $clone->namespace .= CacheItem::validateKey($namespace); + $clone->namespaceLen = \strlen($clone->namespace); + } + + return $clone; + } + private function doSave(CacheItemInterface $item, string $method): bool { if (!$item instanceof CacheItem) { diff --git a/Adapter/RedisTagAwareAdapter.php b/Adapter/RedisTagAwareAdapter.php index a887f29..779c4d9 100644 --- a/Adapter/RedisTagAwareAdapter.php +++ b/Adapter/RedisTagAwareAdapter.php @@ -159,7 +159,7 @@ protected function doDeleteYieldTags(array $ids): iterable foreach ($results as $id => $result) { if ($result instanceof \RedisException || $result instanceof \Relay\Exception || $result instanceof ErrorInterface) { - CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]); + CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->rootNamespace)), 'exception' => $result]); continue; } diff --git a/Adapter/TagAwareAdapter.php b/Adapter/TagAwareAdapter.php index 53c9890..70927cf 100644 --- a/Adapter/TagAwareAdapter.php +++ b/Adapter/TagAwareAdapter.php @@ -16,9 +16,11 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\BadMethodCallException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\NamespacedPoolInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; /** @@ -33,7 +35,7 @@ * @author Nicolas Grekas <p@tchwork.com> * @author Sergey Belyshkin <sbelyshkin@gmail.com> */ -class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface +class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, NamespacedPoolInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface { use ContractsTrait; use LoggerAwareTrait; @@ -277,6 +279,23 @@ public function commit(): bool return $ok; } + /** + * @throws BadMethodCallException When the item pool is not a NamespacedPoolInterface + */ + public function withSubNamespace(string $namespace): static + { + if (!$this->pool instanceof NamespacedPoolInterface) { + throw new BadMethodCallException(\sprintf('Cannot call "%s::withSubNamespace()": this class doesn\'t implement "%s".', get_debug_type($this->pool), NamespacedPoolInterface::class)); + } + + $knownTagVersions = &$this->knownTagVersions; // ensures clones share the same array + $clone = clone $this; + $clone->deferred = []; + $clone->pool = $this->pool->withSubNamespace($namespace); + + return $clone; + } + public function prune(): bool { return $this->pool instanceof PruneableInterface && $this->pool->prune(); diff --git a/Adapter/TraceableAdapter.php b/Adapter/TraceableAdapter.php index 8fe6cf3..43628e4 100644 --- a/Adapter/TraceableAdapter.php +++ b/Adapter/TraceableAdapter.php @@ -13,9 +13,11 @@ use Psr\Cache\CacheItemInterface; use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\BadMethodCallException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; use Symfony\Contracts\Service\ResetInterface; /** @@ -25,8 +27,9 @@ * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Nicolas Grekas <p@tchwork.com> */ -class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface +class TraceableAdapter implements AdapterInterface, CacheInterface, NamespacedPoolInterface, PruneableInterface, ResettableInterface { + private string $namespace = ''; private array $calls = []; public function __construct( @@ -34,10 +37,13 @@ public function __construct( ) { } + /** + * @throws BadMethodCallException When the item pool is not a CacheInterface + */ public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed { if (!$this->pool instanceof CacheInterface) { - throw new \BadMethodCallException(\sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); + throw new BadMethodCallException(\sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); } $isHit = true; @@ -225,11 +231,29 @@ public function getPool(): AdapterInterface return $this->pool; } + /** + * @throws BadMethodCallException When the item pool is not a NamespacedPoolInterface + */ + public function withSubNamespace(string $namespace): static + { + if (!$this->pool instanceof NamespacedPoolInterface) { + throw new BadMethodCallException(\sprintf('Cannot call "%s::withSubNamespace()": this class doesn\'t implement "%s".', get_debug_type($this->pool), NamespacedPoolInterface::class)); + } + + $calls = &$this->calls; // ensures clones share the same array + $clone = clone $this; + $clone->namespace .= CacheItem::validateKey($namespace).':'; + $clone->pool = $this->pool->withSubNamespace($namespace); + + return $clone; + } + protected function start(string $name): TraceableAdapterEvent { $this->calls[] = $event = new TraceableAdapterEvent(); $event->name = $name; $event->start = microtime(true); + $event->namespace = $this->namespace; return $event; } @@ -246,4 +270,5 @@ class TraceableAdapterEvent public array|bool $result; public int $hits = 0; public int $misses = 0; + public string $namespace; } diff --git a/CHANGELOG.md b/CHANGELOG.md index 59195f0..d7b1824 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add support for `\Relay\Cluster` in `RedisAdapter` * Add support for `valkey:` / `valkeys:` schemes + * Add support for namespace-based invalidation * Rename options "redis_cluster" and "redis_sentinel" to "cluster" and "sentinel" respectively 7.2 diff --git a/Exception/BadMethodCallException.php b/Exception/BadMethodCallException.php new file mode 100644 index 0000000..d81f9d2 --- /dev/null +++ b/Exception/BadMethodCallException.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; + +if (interface_exists(SimpleCacheInterface::class)) { + class BadMethodCallException extends \BadMethodCallException implements Psr6CacheInterface, SimpleCacheInterface + { + } +} else { + class BadMethodCallException extends \BadMethodCallException implements Psr6CacheInterface + { + } +} diff --git a/Tests/Adapter/AdapterTestCase.php b/Tests/Adapter/AdapterTestCase.php index da43029..35eefb5 100644 --- a/Tests/Adapter/AdapterTestCase.php +++ b/Tests/Adapter/AdapterTestCase.php @@ -18,6 +18,7 @@ use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Contracts\Cache\CallbackInterface; +use Symfony\Contracts\Cache\NamespacedPoolInterface; abstract class AdapterTestCase extends CachePoolTest { @@ -350,6 +351,33 @@ public function testNumericKeysWorkAfterMemoryLeakPrevention() $this->assertEquals('value-50', $cache->getItem((string) 50)->get()); } + + public function testNamespaces() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = $this->createCachePool(0, __FUNCTION__); + + $this->assertInstanceOf(NamespacedPoolInterface::class, $cache); + + $derived = $cache->withSubNamespace('derived'); + + $item = $derived->getItem('foo'); + $derived->save($item->set('Foo')); + + $this->assertFalse($cache->getItem('foo')->isHit()); + + $item = $cache->getItem('bar'); + $cache->save($item->set('Bar')); + + $this->assertFalse($derived->getItem('bar')->isHit()); + $this->assertTrue($cache->getItem('bar')->isHit()); + + $derived = $cache->withSubNamespace('derived'); + $this->assertTrue($derived->getItem('foo')->isHit()); + } } class NotUnserializable diff --git a/Tests/Adapter/PhpArrayAdapterTest.php b/Tests/Adapter/PhpArrayAdapterTest.php index 5bbe4d1..5bc2bfe 100644 --- a/Tests/Adapter/PhpArrayAdapterTest.php +++ b/Tests/Adapter/PhpArrayAdapterTest.php @@ -57,6 +57,8 @@ class PhpArrayAdapterTest extends AdapterTestCase 'testDefaultLifeTime' => 'PhpArrayAdapter does not allow configuring a default lifetime.', 'testPrune' => 'PhpArrayAdapter just proxies', + + 'testNamespaces' => 'PhpArrayAdapter does not support namespaces.', ]; protected static string $file; diff --git a/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php b/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php index d20ffd5..0f92aee 100644 --- a/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php +++ b/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php @@ -28,6 +28,8 @@ class PhpArrayAdapterWithFallbackTest extends AdapterTestCase 'testDeleteItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.', 'testDeleteItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.', 'testPrune' => 'PhpArrayAdapter just proxies', + + 'testNamespaces' => 'PhpArrayAdapter does not support namespaces.', ]; protected static string $file; diff --git a/Tests/Adapter/TagAwareTestTrait.php b/Tests/Adapter/TagAwareTestTrait.php index 8ec1297..9894ba0 100644 --- a/Tests/Adapter/TagAwareTestTrait.php +++ b/Tests/Adapter/TagAwareTestTrait.php @@ -183,4 +183,27 @@ public function testRefreshAfterExpires() $cacheItem = $pool->getItem('test'); $this->assertTrue($cacheItem->isHit()); } + + public function testNamespacesAndTags() + { + $pool = $this->createCachePool(); + $pool->clear(); + + $item = $pool->getItem('foo'); + $item->tag('baz'); + $pool->save($item); + + $derived = $pool->withSubNamespace('derived'); + $item = $derived->getItem('bar'); + $item->tag('baz'); + $derived->save($item); + + $this->assertTrue($pool->getItem('foo')->isHit()); + $this->assertTrue($derived->getItem('bar')->isHit()); + + $pool->invalidateTags(['baz']); + + $this->assertFalse($pool->getItem('foo')->isHit()); + $this->assertFalse($derived->getItem('bar')->isHit()); + } } diff --git a/Tests/Psr16CacheProxyTest.php b/Tests/Psr16CacheProxyTest.php index c3d2d8d..fa771cf 100644 --- a/Tests/Psr16CacheProxyTest.php +++ b/Tests/Psr16CacheProxyTest.php @@ -45,12 +45,12 @@ public function createSimpleCache(int $defaultLifetime = 0): CacheInterface public function testProxy() { $pool = new ArrayAdapter(); - $cache = new Psr16Cache(new ProxyAdapter($pool, 'my-namespace.')); + $cache = new Psr16Cache(new ProxyAdapter($pool, 'my-namespace')); $this->assertNull($cache->get('some-key')); $this->assertTrue($cache->set('some-other-key', 'value')); - $item = $pool->getItem('my-namespace.some-other-key', 'value'); + $item = $pool->withSubNamespace('my-namespace')->getItem('some-other-key', 'value'); $this->assertTrue($item->isHit()); $this->assertSame('value', $item->get()); } diff --git a/Traits/AbstractAdapterTrait.php b/Traits/AbstractAdapterTrait.php index 6a71674..ac8dc97 100644 --- a/Traits/AbstractAdapterTrait.php +++ b/Traits/AbstractAdapterTrait.php @@ -35,6 +35,7 @@ trait AbstractAdapterTrait */ private static \Closure $mergeByLifetime; + private readonly string $rootNamespace; private string $namespace = ''; private int $defaultLifetime; private string $namespaceVersion = ''; @@ -106,15 +107,16 @@ public function clear(string $prefix = ''): bool { $this->deferred = []; if ($cleared = $this->versioningIsEnabled) { + $rootNamespace = $this->rootNamespace ??= $this->namespace; if ('' === $namespaceVersionToClear = $this->namespaceVersion) { - foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + foreach ($this->doFetch([static::NS_SEPARATOR.$rootNamespace]) as $v) { $namespaceVersionToClear = $v; } } - $namespaceToClear = $this->namespace.$namespaceVersionToClear; + $namespaceToClear = $rootNamespace.$namespaceVersionToClear; $namespaceVersion = self::formatNamespaceVersion(mt_rand()); try { - $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0); + $e = $this->doSave([static::NS_SEPARATOR.$rootNamespace => $namespaceVersion], 0); } catch (\Exception $e) { } if (true !== $e && [] !== $e) { @@ -247,6 +249,16 @@ public function saveDeferred(CacheItemInterface $item): bool return true; } + public function withSubNamespace(string $namespace): static + { + $this->rootNamespace ??= $this->namespace; + + $clone = clone $this; + $clone->namespace .= CacheItem::validateKey($namespace).static::NS_SEPARATOR; + + return $clone; + } + /** * Enables/disables versioning of items. * @@ -318,19 +330,24 @@ private function generateItems(iterable $items, array &$keys): \Generator /** * @internal */ - protected function getId(mixed $key): string + protected function getId(mixed $key, ?string $namespace = null): string { - if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { + $namespace ??= $this->namespace; + + if ('' !== $this->namespaceVersion) { + $namespace .= $this->namespaceVersion; + } elseif ($this->versioningIsEnabled) { + $rootNamespace = $this->rootNamespace ??= $this->namespace; $this->ids = []; $this->namespaceVersion = '1'.static::NS_SEPARATOR; try { - foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) { + foreach ($this->doFetch([static::NS_SEPARATOR.$rootNamespace]) as $v) { $this->namespaceVersion = $v; } $e = true; if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) { $this->namespaceVersion = self::formatNamespaceVersion(time()); - $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0); + $e = $this->doSave([static::NS_SEPARATOR.$rootNamespace => $this->namespaceVersion], 0); } } catch (\Exception $e) { } @@ -338,25 +355,34 @@ protected function getId(mixed $key): string $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.'); CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); } + + $namespace .= $this->namespaceVersion; } if (\is_string($key) && isset($this->ids[$key])) { - return $this->namespace.$this->namespaceVersion.$this->ids[$key]; - } - \assert('' !== CacheItem::validateKey($key)); - $this->ids[$key] = $key; + $id = $this->ids[$key]; + } else { + \assert('' !== CacheItem::validateKey($key)); + $this->ids[$key] = $key; - if (\count($this->ids) > 1000) { - $this->ids = \array_slice($this->ids, 500, null, true); // stop memory leak if there are many keys - } + if (\count($this->ids) > 1000) { + $this->ids = \array_slice($this->ids, 500, null, true); // stop memory leak if there are many keys + } + + if (null === $this->maxIdLength) { + return $namespace.$key; + } + if (\strlen($id = $namespace.$key) <= $this->maxIdLength) { + return $id; + } - if (null === $this->maxIdLength) { - return $this->namespace.$this->namespaceVersion.$key; - } - if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { // Use xxh128 to favor speed over security, which is not an issue here $this->ids[$key] = $id = substr_replace(base64_encode(hash('xxh128', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2)); - $id = $this->namespace.$this->namespaceVersion.$id; + } + $id = $namespace.$id; + + if (null !== $this->maxIdLength && \strlen($id) > $this->maxIdLength) { + return base64_encode(hash('xxh128', $id, true)); } return $id; diff --git a/composer.json b/composer.json index bdb461b..c89d667 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "php": ">=8.2", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^2.5|^3", + "symfony/cache-contracts": "^3.6", "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", "symfony/var-exporter": "^6.4|^7.0" From d68bb7363a6a6a7ef621ad8756f0ddd7f2d6b930 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas <nicolas.grekas@gmail.com> Date: Sat, 15 Mar 2025 14:10:48 +0100 Subject: [PATCH 10/12] Various cleanups --- Tests/Traits/RedisProxiesTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/Traits/RedisProxiesTest.php b/Tests/Traits/RedisProxiesTest.php index 051275d..50f784d 100644 --- a/Tests/Traits/RedisProxiesTest.php +++ b/Tests/Traits/RedisProxiesTest.php @@ -17,7 +17,6 @@ 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; class RedisProxiesTest extends TestCase @@ -37,7 +36,7 @@ public function testRedisProxy($class) $methods = []; foreach ((new \ReflectionClass(\sprintf('Symfony\Component\Cache\Traits\\%s%dProxy', $class, $version)))->getMethods() as $method) { - if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name)) { + if ('reset' === $method->name || method_exists(RedisProxyTrait::class, $method->name)) { continue; } $return = '__construct' === $method->name || $method->getReturnType() instanceof \ReflectionNamedType && 'void' === (string) $method->getReturnType() ? '' : 'return '; @@ -89,7 +88,7 @@ public function testRelayProxy() $expectedMethods = []; foreach ((new \ReflectionClass(RelayProxy::class))->getMethods() as $method) { - if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name) || $method->isStatic()) { + if ('reset' === $method->name || method_exists(RedisProxyTrait::class, $method->name) || $method->isStatic()) { continue; } @@ -136,7 +135,7 @@ public function testRelayClusterProxy() $expectedMethods = []; foreach ((new \ReflectionClass(RelayClusterProxy::class))->getMethods() as $method) { - if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name) || $method->isStatic()) { + if ('reset' === $method->name || method_exists(RedisProxyTrait::class, $method->name) || $method->isStatic()) { continue; } From 66e51fb8aafb1211064f86570a30cca9d9076d21 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski <dariusz.ruminski@gmail.com> Date: Thu, 13 Mar 2025 23:58:12 +0100 Subject: [PATCH 11/12] chore: PHP CS Fixer fixes --- Traits/RedisTrait.php | 2 +- Traits/RelayClusterProxy.php | 314 +++++++++++++++++------------------ 2 files changed, 158 insertions(+), 158 deletions(-) diff --git a/Traits/RedisTrait.php b/Traits/RedisTrait.php index 55e7cea..19b4b91 100644 --- a/Traits/RedisTrait.php +++ b/Traits/RedisTrait.php @@ -692,7 +692,7 @@ private function pipeline(\Closure $generator, ?object $redis = null): \Generato $ids = []; $redis ??= $this->redis; - if ($redis instanceof \RedisCluster || $redis instanceof \Relay\Cluster || ($redis instanceof \Predis\ClientInterface && ($redis->getConnection() instanceof RedisCluster || $redis->getConnection() instanceof Predis2RedisCluster))) { + if ($redis instanceof \RedisCluster || $redis instanceof RelayCluster || ($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 diff --git a/Traits/RelayClusterProxy.php b/Traits/RelayClusterProxy.php index 6d12434..fd5f08b 100644 --- a/Traits/RelayClusterProxy.php +++ b/Traits/RelayClusterProxy.php @@ -24,7 +24,7 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); /** * @internal */ -class RelayClusterProxy extends \Relay\Cluster implements ResetInterface, LazyObjectInterface +class RelayClusterProxy extends Cluster implements ResetInterface, LazyObjectInterface { use RedisProxyTrait { resetLazyObject as reset; @@ -67,7 +67,7 @@ public function dispatchEvents(): false|int return $this->initializeLazyObject()->dispatchEvents(...\func_get_args()); } - public function dump(mixed $key): \Relay\Cluster|string|false + public function dump(mixed $key): Cluster|string|false { return $this->initializeLazyObject()->dump(...\func_get_args()); } @@ -87,7 +87,7 @@ 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 + public function getrange(mixed $key, int $start, int $end): Cluster|string|false { return $this->initializeLazyObject()->getrange(...\func_get_args()); } @@ -167,42 +167,42 @@ public function cluster(array|string $key_or_address, string $operation, mixed . return $this->initializeLazyObject()->cluster(...\func_get_args()); } - public function info(array|string $key_or_address, string ...$sections): \Relay\Cluster|array|false + public function info(array|string $key_or_address, string ...$sections): 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 + public function flushdb(array|string $key_or_address, bool|null $sync = null): Cluster|bool { return $this->initializeLazyObject()->flushdb(...\func_get_args()); } - public function flushall(array|string $key_or_address, bool|null $sync = null): \Relay\Cluster|bool + public function flushall(array|string $key_or_address, bool|null $sync = null): Cluster|bool { return $this->initializeLazyObject()->flushall(...\func_get_args()); } - public function dbsize(array|string $key_or_address): \Relay\Cluster|int|false + public function dbsize(array|string $key_or_address): 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 + public function waitaof(array|string $key_or_address, int $numlocal, int $numremote, int $timeout): 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 + public function restore(mixed $key, int $ttl, string $value, array|null $options = null): Cluster|bool { return $this->initializeLazyObject()->restore(...\func_get_args()); } - public function echo(array|string $key_or_address, string $message): \Relay\Cluster|string|false + public function echo(array|string $key_or_address, string $message): 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 + public function ping(array|string $key_or_address, string|null $message = null): Cluster|bool|string { return $this->initializeLazyObject()->ping(...\func_get_args()); } @@ -212,22 +212,22 @@ public function idleTime(): int return $this->initializeLazyObject()->idleTime(...\func_get_args()); } - public function randomkey(array|string $key_or_address): \Relay\Cluster|bool|string + public function randomkey(array|string $key_or_address): Cluster|bool|string { return $this->initializeLazyObject()->randomkey(...\func_get_args()); } - public function time(array|string $key_or_address): \Relay\Cluster|array|false + public function time(array|string $key_or_address): Cluster|array|false { return $this->initializeLazyObject()->time(...\func_get_args()); } - public function bgrewriteaof(array|string $key_or_address): \Relay\Cluster|bool + public function bgrewriteaof(array|string $key_or_address): Cluster|bool { return $this->initializeLazyObject()->bgrewriteaof(...\func_get_args()); } - public function lastsave(array|string $key_or_address): \Relay\Cluster|false|int + public function lastsave(array|string $key_or_address): Cluster|false|int { return $this->initializeLazyObject()->lastsave(...\func_get_args()); } @@ -237,32 +237,32 @@ 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 + public function bgsave(array|string $key_or_address, bool $schedule = false): Cluster|bool { return $this->initializeLazyObject()->bgsave(...\func_get_args()); } - public function save(array|string $key_or_address): \Relay\Cluster|bool + public function save(array|string $key_or_address): Cluster|bool { return $this->initializeLazyObject()->save(...\func_get_args()); } - public function role(array|string $key_or_address): \Relay\Cluster|array|false + public function role(array|string $key_or_address): Cluster|array|false { return $this->initializeLazyObject()->role(...\func_get_args()); } - public function ttl(mixed $key): \Relay\Cluster|false|int + public function ttl(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->ttl(...\func_get_args()); } - public function pttl(mixed $key): \Relay\Cluster|false|int + public function pttl(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->pttl(...\func_get_args()); } - public function exists(mixed ...$keys): \Relay\Cluster|bool|int + public function exists(mixed ...$keys): Cluster|bool|int { return $this->initializeLazyObject()->exists(...\func_get_args()); } @@ -292,17 +292,17 @@ public function client(array|string $key_or_address, string $operation, 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 + public function geoadd(mixed $key, float $lng, float $lat, string $member, mixed ...$other_triples_and_options): 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 + public function geodist(mixed $key, string $src, string $dst, string|null $unit = null): Cluster|float|false { return $this->initializeLazyObject()->geodist(...\func_get_args()); } - public function geohash(mixed $key, string $member, string ...$other_members): \Relay\Cluster|array|false + public function geohash(mixed $key, string $member, string ...$other_members): Cluster|array|false { return $this->initializeLazyObject()->geohash(...\func_get_args()); } @@ -327,12 +327,12 @@ public function georadius_ro(mixed $key, float $lng, float $lat, float $radius, 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 + public function geosearchstore(mixed $dstkey, mixed $srckey, array|string $position, array|int|float $shape, string $unit, array $options = []): 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 + public function geosearch(mixed $key, array|string $position, array|int|float $shape, string $unit, array $options = []): Cluster|array|false { return $this->initializeLazyObject()->geosearch(...\func_get_args()); } @@ -347,17 +347,17 @@ 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 + public function setrange(mixed $key, int $start, mixed $value): Cluster|false|int { return $this->initializeLazyObject()->setrange(...\func_get_args()); } - public function getbit(mixed $key, int $pos): \Relay\Cluster|false|int + public function getbit(mixed $key, int $pos): 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 + public function bitcount(mixed $key, int $start = 0, int $end = -1, bool $by_bit = false): Cluster|false|int { return $this->initializeLazyObject()->bitcount(...\func_get_args()); } @@ -367,22 +367,22 @@ public function config(array|string $key_or_address, string $operation, mixed .. return $this->initializeLazyObject()->config(...\func_get_args()); } - public function command(mixed ...$args): \Relay\Cluster|array|false|int + public function command(mixed ...$args): 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 + public function bitop(string $operation, string $dstkey, string $srckey, string ...$other_keys): 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 + public function bitpos(mixed $key, int $bit, ?int $start = null, ?int $end = null, bool $by_bit = false): 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|false|null + public function blmove(mixed $srckey, mixed $dstkey, string $srcpos, string $dstpos, float $timeout): Cluster|string|false|null { return $this->initializeLazyObject()->blmove(...\func_get_args()); } @@ -392,7 +392,7 @@ public function lmove(mixed $srckey, mixed $dstkey, string $srcpos, string $dstp return $this->initializeLazyObject()->lmove(...\func_get_args()); } - public function setbit(mixed $key, int $pos, int $value): \Relay\Cluster|false|int + public function setbit(mixed $key, int $pos, int $value): Cluster|false|int { return $this->initializeLazyObject()->setbit(...\func_get_args()); } @@ -402,12 +402,12 @@ public function acl(array|string $key_or_address, string $operation, string ...$ return $this->initializeLazyObject()->acl(...\func_get_args()); } - public function append(mixed $key, mixed $value): \Relay\Cluster|false|int + public function append(mixed $key, mixed $value): Cluster|false|int { return $this->initializeLazyObject()->append(...\func_get_args()); } - public function set(mixed $key, mixed $value, mixed $options = null): \Relay\Cluster|string|bool + public function set(mixed $key, mixed $value, mixed $options = null): Cluster|string|bool { return $this->initializeLazyObject()->set(...\func_get_args()); } @@ -417,32 +417,32 @@ 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 + public function setex(mixed $key, int $seconds, mixed $value): Cluster|bool { return $this->initializeLazyObject()->setex(...\func_get_args()); } - public function pfadd(mixed $key, array $elements): \Relay\Cluster|false|int + public function pfadd(mixed $key, array $elements): Cluster|false|int { return $this->initializeLazyObject()->pfadd(...\func_get_args()); } - public function pfcount(mixed $key): \Relay\Cluster|int|false + public function pfcount(mixed $key): Cluster|int|false { return $this->initializeLazyObject()->pfcount(...\func_get_args()); } - public function pfmerge(string $dstkey, array $srckeys): \Relay\Cluster|bool + public function pfmerge(string $dstkey, array $srckeys): Cluster|bool { return $this->initializeLazyObject()->pfmerge(...\func_get_args()); } - public function psetex(mixed $key, int $milliseconds, mixed $value): \Relay\Cluster|bool + public function psetex(mixed $key, int $milliseconds, mixed $value): Cluster|bool { return $this->initializeLazyObject()->psetex(...\func_get_args()); } - public function publish(string $channel, string $message): \Relay\Cluster|false|int + public function publish(string $channel, string $message): Cluster|false|int { return $this->initializeLazyObject()->publish(...\func_get_args()); } @@ -452,117 +452,117 @@ public function pubsub(array|string $key_or_address, string $operation, mixed .. return $this->initializeLazyObject()->pubsub(...\func_get_args()); } - public function setnx(mixed $key, mixed $value): \Relay\Cluster|bool + public function setnx(mixed $key, mixed $value): Cluster|bool { return $this->initializeLazyObject()->setnx(...\func_get_args()); } - public function mget(array $keys): \Relay\Cluster|array|false + public function mget(array $keys): Cluster|array|false { return $this->initializeLazyObject()->mget(...\func_get_args()); } - public function mset(array $kvals): \Relay\Cluster|array|bool + public function mset(array $kvals): Cluster|array|bool { return $this->initializeLazyObject()->mset(...\func_get_args()); } - public function msetnx(array $kvals): \Relay\Cluster|array|bool + public function msetnx(array $kvals): Cluster|array|bool { return $this->initializeLazyObject()->msetnx(...\func_get_args()); } - public function rename(mixed $key, mixed $newkey): \Relay\Cluster|bool + public function rename(mixed $key, mixed $newkey): Cluster|bool { return $this->initializeLazyObject()->rename(...\func_get_args()); } - public function renamenx(mixed $key, mixed $newkey): \Relay\Cluster|bool + public function renamenx(mixed $key, mixed $newkey): Cluster|bool { return $this->initializeLazyObject()->renamenx(...\func_get_args()); } - public function del(mixed ...$keys): \Relay\Cluster|bool|int + public function del(mixed ...$keys): Cluster|bool|int { return $this->initializeLazyObject()->del(...\func_get_args()); } - public function unlink(mixed ...$keys): \Relay\Cluster|false|int + public function unlink(mixed ...$keys): Cluster|false|int { return $this->initializeLazyObject()->unlink(...\func_get_args()); } - public function expire(mixed $key, int $seconds, string|null $mode = null): \Relay\Cluster|bool + public function expire(mixed $key, int $seconds, string|null $mode = null): Cluster|bool { return $this->initializeLazyObject()->expire(...\func_get_args()); } - public function pexpire(mixed $key, int $milliseconds): \Relay\Cluster|bool + public function pexpire(mixed $key, int $milliseconds): Cluster|bool { return $this->initializeLazyObject()->pexpire(...\func_get_args()); } - public function expireat(mixed $key, int $timestamp): \Relay\Cluster|bool + public function expireat(mixed $key, int $timestamp): Cluster|bool { return $this->initializeLazyObject()->expireat(...\func_get_args()); } - public function expiretime(mixed $key): \Relay\Cluster|false|int + public function expiretime(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->expiretime(...\func_get_args()); } - public function pexpireat(mixed $key, int $timestamp_ms): \Relay\Cluster|bool + public function pexpireat(mixed $key, int $timestamp_ms): 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()); + return Cluster::flushMemory(...\func_get_args()); } - public function pexpiretime(mixed $key): \Relay\Cluster|false|int + public function pexpiretime(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->pexpiretime(...\func_get_args()); } - public function persist(mixed $key): \Relay\Cluster|bool + public function persist(mixed $key): Cluster|bool { return $this->initializeLazyObject()->persist(...\func_get_args()); } - public function type(mixed $key): \Relay\Cluster|bool|int|string + public function type(mixed $key): Cluster|bool|int|string { return $this->initializeLazyObject()->type(...\func_get_args()); } - public function lrange(mixed $key, int $start, int $stop): \Relay\Cluster|array|false + public function lrange(mixed $key, int $start, int $stop): Cluster|array|false { return $this->initializeLazyObject()->lrange(...\func_get_args()); } - public function lpush(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + public function lpush(mixed $key, mixed $member, mixed ...$members): Cluster|false|int { return $this->initializeLazyObject()->lpush(...\func_get_args()); } - public function rpush(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + public function rpush(mixed $key, mixed $member, mixed ...$members): Cluster|false|int { return $this->initializeLazyObject()->rpush(...\func_get_args()); } - public function lpushx(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + public function lpushx(mixed $key, mixed $member, mixed ...$members): Cluster|false|int { return $this->initializeLazyObject()->lpushx(...\func_get_args()); } - public function rpushx(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + public function rpushx(mixed $key, mixed $member, mixed ...$members): Cluster|false|int { return $this->initializeLazyObject()->rpushx(...\func_get_args()); } - public function lset(mixed $key, int $index, mixed $member): \Relay\Cluster|bool + public function lset(mixed $key, int $index, mixed $member): Cluster|bool { return $this->initializeLazyObject()->lset(...\func_get_args()); } @@ -592,7 +592,7 @@ 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 + public function blpop(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): Cluster|array|false|null { return $this->initializeLazyObject()->blpop(...\func_get_args()); } @@ -602,7 +602,7 @@ public function blmpop(float $timeout, array $keys, string $from, int $count = 1 return $this->initializeLazyObject()->blmpop(...\func_get_args()); } - public function bzmpop(float $timeout, array $keys, string $from, int $count = 1): \Relay\Cluster|array|false|null + public function bzmpop(float $timeout, array $keys, string $from, int $count = 1): Cluster|array|false|null { return $this->initializeLazyObject()->bzmpop(...\func_get_args()); } @@ -612,22 +612,22 @@ 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 + public function zmpop(array $keys, string $from, int $count = 1): 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 + public function brpop(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): 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 + public function bzpopmax(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): 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 + public function bzpopmin(string|array $key, string|float $timeout_or_key, mixed ...$extra_args): Cluster|array|false|null { return $this->initializeLazyObject()->bzpopmin(...\func_get_args()); } @@ -637,12 +637,12 @@ 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 + public function geopos(mixed $key, mixed ...$members): Cluster|array|false { return $this->initializeLazyObject()->geopos(...\func_get_args()); } - public function lrem(mixed $key, mixed $member, int $count = 0): \Relay\Cluster|false|int + public function lrem(mixed $key, mixed $member, int $count = 0): Cluster|false|int { return $this->initializeLazyObject()->lrem(...\func_get_args()); } @@ -652,19 +652,19 @@ 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 + public function linsert(mixed $key, string $op, mixed $pivot, mixed $element): Cluster|false|int { return $this->initializeLazyObject()->linsert(...\func_get_args()); } - public function ltrim(mixed $key, int $start, int $end): \Relay\Cluster|bool + public function ltrim(mixed $key, int $start, int $end): Cluster|bool { return $this->initializeLazyObject()->ltrim(...\func_get_args()); } public static function maxMemory(): int { - return \Relay\Cluster::maxMemory(); + return Cluster::maxMemory(); } public function hget(mixed $key, mixed $member): mixed @@ -672,127 +672,127 @@ 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 + public function hstrlen(mixed $key, mixed $member): Cluster|false|int { return $this->initializeLazyObject()->hstrlen(...\func_get_args()); } - public function hgetall(mixed $key): \Relay\Cluster|array|false + public function hgetall(mixed $key): Cluster|array|false { return $this->initializeLazyObject()->hgetall(...\func_get_args()); } - public function hkeys(mixed $key): \Relay\Cluster|array|false + public function hkeys(mixed $key): Cluster|array|false { return $this->initializeLazyObject()->hkeys(...\func_get_args()); } - public function hvals(mixed $key): \Relay\Cluster|array|false + public function hvals(mixed $key): Cluster|array|false { return $this->initializeLazyObject()->hvals(...\func_get_args()); } - public function hmget(mixed $key, array $members): \Relay\Cluster|array|false + public function hmget(mixed $key, array $members): Cluster|array|false { return $this->initializeLazyObject()->hmget(...\func_get_args()); } - public function hmset(mixed $key, array $members): \Relay\Cluster|bool + public function hmset(mixed $key, array $members): Cluster|bool { return $this->initializeLazyObject()->hmset(...\func_get_args()); } - public function hexists(mixed $key, mixed $member): \Relay\Cluster|bool + public function hexists(mixed $key, mixed $member): Cluster|bool { return $this->initializeLazyObject()->hexists(...\func_get_args()); } - public function hrandfield(mixed $key, array|null $options = null): \Relay\Cluster|array|string|false + public function hrandfield(mixed $key, array|null $options = null): Cluster|array|string|false { return $this->initializeLazyObject()->hrandfield(...\func_get_args()); } - public function hsetnx(mixed $key, mixed $member, mixed $value): \Relay\Cluster|bool + public function hsetnx(mixed $key, mixed $member, mixed $value): Cluster|bool { return $this->initializeLazyObject()->hsetnx(...\func_get_args()); } - public function hset(mixed $key, mixed ...$keys_and_vals): \Relay\Cluster|int|false + public function hset(mixed $key, mixed ...$keys_and_vals): Cluster|int|false { return $this->initializeLazyObject()->hset(...\func_get_args()); } - public function hdel(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + public function hdel(mixed $key, mixed $member, mixed ...$members): Cluster|false|int { return $this->initializeLazyObject()->hdel(...\func_get_args()); } - public function hincrby(mixed $key, mixed $member, int $value): \Relay\Cluster|false|int + public function hincrby(mixed $key, mixed $member, int $value): Cluster|false|int { return $this->initializeLazyObject()->hincrby(...\func_get_args()); } - public function hincrbyfloat(mixed $key, mixed $member, float $value): \Relay\Cluster|bool|float + public function hincrbyfloat(mixed $key, mixed $member, float $value): Cluster|bool|float { return $this->initializeLazyObject()->hincrbyfloat(...\func_get_args()); } - public function incr(mixed $key, int $by = 1): \Relay\Cluster|false|int + public function incr(mixed $key, int $by = 1): Cluster|false|int { return $this->initializeLazyObject()->incr(...\func_get_args()); } - public function decr(mixed $key, int $by = 1): \Relay\Cluster|false|int + public function decr(mixed $key, int $by = 1): Cluster|false|int { return $this->initializeLazyObject()->decr(...\func_get_args()); } - public function incrby(mixed $key, int $value): \Relay\Cluster|false|int + public function incrby(mixed $key, int $value): Cluster|false|int { return $this->initializeLazyObject()->incrby(...\func_get_args()); } - public function decrby(mixed $key, int $value): \Relay\Cluster|false|int + public function decrby(mixed $key, int $value): Cluster|false|int { return $this->initializeLazyObject()->decrby(...\func_get_args()); } - public function incrbyfloat(mixed $key, float $value): \Relay\Cluster|false|float + public function incrbyfloat(mixed $key, float $value): Cluster|false|float { return $this->initializeLazyObject()->incrbyfloat(...\func_get_args()); } - public function sdiff(mixed $key, mixed ...$other_keys): \Relay\Cluster|array|false + public function sdiff(mixed $key, mixed ...$other_keys): Cluster|array|false { return $this->initializeLazyObject()->sdiff(...\func_get_args()); } - public function sdiffstore(mixed $key, mixed ...$other_keys): \Relay\Cluster|false|int + public function sdiffstore(mixed $key, mixed ...$other_keys): Cluster|false|int { return $this->initializeLazyObject()->sdiffstore(...\func_get_args()); } - public function sinter(mixed $key, mixed ...$other_keys): \Relay\Cluster|array|false + public function sinter(mixed $key, mixed ...$other_keys): Cluster|array|false { return $this->initializeLazyObject()->sinter(...\func_get_args()); } - public function sintercard(array $keys, int $limit = -1): \Relay\Cluster|false|int + public function sintercard(array $keys, int $limit = -1): Cluster|false|int { return $this->initializeLazyObject()->sintercard(...\func_get_args()); } - public function sinterstore(mixed $key, mixed ...$other_keys): \Relay\Cluster|false|int + public function sinterstore(mixed $key, mixed ...$other_keys): Cluster|false|int { return $this->initializeLazyObject()->sinterstore(...\func_get_args()); } - public function sunion(mixed $key, mixed ...$other_keys): \Relay\Cluster|array|false + public function sunion(mixed $key, mixed ...$other_keys): Cluster|array|false { return $this->initializeLazyObject()->sunion(...\func_get_args()); } - public function sunionstore(mixed $key, mixed ...$other_keys): \Relay\Cluster|false|int + public function sunionstore(mixed $key, mixed ...$other_keys): Cluster|false|int { return $this->initializeLazyObject()->sunionstore(...\func_get_args()); } @@ -827,12 +827,12 @@ 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 + public function touch(array|string $key_or_array, mixed ...$more_keys): Cluster|false|int { return $this->initializeLazyObject()->touch(...\func_get_args()); } - public function multi(int $mode = Relay::MULTI): \Relay\Cluster|bool + public function multi(int $mode = Relay::MULTI): Cluster|bool { return $this->initializeLazyObject()->multi(...\func_get_args()); } @@ -842,12 +842,12 @@ public function exec(): array|false return $this->initializeLazyObject()->exec(...\func_get_args()); } - public function watch(mixed $key, mixed ...$other_keys): \Relay\Cluster|bool + public function watch(mixed $key, mixed ...$other_keys): Cluster|bool { return $this->initializeLazyObject()->watch(...\func_get_args()); } - public function unwatch(): \Relay\Cluster|bool + public function unwatch(): Cluster|bool { return $this->initializeLazyObject()->unwatch(...\func_get_args()); } @@ -882,17 +882,17 @@ public function zscan(mixed $key, mixed &$iterator, mixed $match = null, int $co return $this->initializeLazyObject()->zscan($key, $iterator, ...\array_slice(\func_get_args(), 2)); } - public function zscore(mixed $key, mixed $member): \Relay\Cluster|float|false + public function zscore(mixed $key, mixed $member): Cluster|float|false { return $this->initializeLazyObject()->zscore(...\func_get_args()); } - public function keys(mixed $pattern): \Relay\Cluster|array|false + public function keys(mixed $pattern): 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 + public function slowlog(array|string $key_or_address, string $operation, mixed ...$args): Cluster|array|bool|int { return $this->initializeLazyObject()->slowlog(...\func_get_args()); } @@ -902,42 +902,42 @@ public function xadd(mixed $key, string $id, array $values, int $maxlen = 0, boo return $this->initializeLazyObject()->xadd(...\func_get_args()); } - public function smembers(mixed $key): \Relay\Cluster|array|false + public function smembers(mixed $key): Cluster|array|false { return $this->initializeLazyObject()->smembers(...\func_get_args()); } - public function sismember(mixed $key, mixed $member): \Relay\Cluster|bool + public function sismember(mixed $key, mixed $member): Cluster|bool { return $this->initializeLazyObject()->sismember(...\func_get_args()); } - public function smismember(mixed $key, mixed ...$members): \Relay\Cluster|array|false + public function smismember(mixed $key, mixed ...$members): Cluster|array|false { return $this->initializeLazyObject()->smismember(...\func_get_args()); } - public function srem(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + public function srem(mixed $key, mixed $member, mixed ...$members): Cluster|false|int { return $this->initializeLazyObject()->srem(...\func_get_args()); } - public function sadd(mixed $key, mixed $member, mixed ...$members): \Relay\Cluster|false|int + public function sadd(mixed $key, mixed $member, mixed ...$members): Cluster|false|int { return $this->initializeLazyObject()->sadd(...\func_get_args()); } - public function sort(mixed $key, array $options = []): \Relay\Cluster|array|false|int + public function sort(mixed $key, array $options = []): Cluster|array|false|int { return $this->initializeLazyObject()->sort(...\func_get_args()); } - public function sort_ro(mixed $key, array $options = []): \Relay\Cluster|array|false|int + public function sort_ro(mixed $key, array $options = []): Cluster|array|false|int { return $this->initializeLazyObject()->sort_ro(...\func_get_args()); } - public function smove(mixed $srckey, mixed $dstkey, mixed $member): \Relay\Cluster|bool + public function smove(mixed $srckey, mixed $dstkey, mixed $member): Cluster|bool { return $this->initializeLazyObject()->smove(...\func_get_args()); } @@ -952,7 +952,7 @@ 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 + public function scard(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->scard(...\func_get_args()); } @@ -962,37 +962,37 @@ public function script(array|string $key_or_address, string $operation, string . return $this->initializeLazyObject()->script(...\func_get_args()); } - public function strlen(mixed $key): \Relay\Cluster|false|int + public function strlen(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->strlen(...\func_get_args()); } - public function hlen(mixed $key): \Relay\Cluster|false|int + public function hlen(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->hlen(...\func_get_args()); } - public function llen(mixed $key): \Relay\Cluster|false|int + public function llen(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->llen(...\func_get_args()); } - public function xack(mixed $key, string $group, array $ids): \Relay\Cluster|false|int + public function xack(mixed $key, string $group, array $ids): 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 + public function xclaim(mixed $key, string $group, string $consumer, int $min_idle, array $ids, array $options): 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 + public function xautoclaim(mixed $key, string $group, string $consumer, int $min_idle, string $start, int $count = -1, bool $justid = false): Cluster|array|bool { return $this->initializeLazyObject()->xautoclaim(...\func_get_args()); } - public function xlen(mixed $key): \Relay\Cluster|false|int + public function xlen(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->xlen(...\func_get_args()); } @@ -1002,7 +1002,7 @@ public function xgroup(string $operation, mixed $key = null, ?string $group = nu return $this->initializeLazyObject()->xgroup(...\func_get_args()); } - public function xdel(mixed $key, array $ids): \Relay\Cluster|false|int + public function xdel(mixed $key, array $ids): Cluster|false|int { return $this->initializeLazyObject()->xdel(...\func_get_args()); } @@ -1012,32 +1012,32 @@ public function xinfo(string $operation, string|null $arg1 = null, string|null $ 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 + 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): 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 + public function xrange(mixed $key, string $start, string $end, int $count = -1): 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 + public function xread(array $streams, int $count = -1, int $block = -1): 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 + public function xreadgroup(mixed $key, string $consumer, array $streams, int $count = 1, int $block = 1): 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 + public function xrevrange(mixed $key, string $end, string $start, int $count = -1): 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 + public function xtrim(mixed $key, string $threshold, bool $approx = false, bool $minid = false, int $limit = -1): Cluster|false|int { return $this->initializeLazyObject()->xtrim(...\func_get_args()); } @@ -1052,22 +1052,22 @@ 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 + public function zrange(mixed $key, string $start, string $end, mixed $options = null): 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 + public function zrevrange(mixed $key, int $start, int $end, mixed $options = null): 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 + public function zrangebyscore(mixed $key, mixed $start, mixed $end, mixed $options = null): 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 + public function zrevrangebyscore(mixed $key, mixed $start, mixed $end, mixed $options = null): Cluster|array|false { return $this->initializeLazyObject()->zrevrangebyscore(...\func_get_args()); } @@ -1077,7 +1077,7 @@ public function zrevrank(mixed $key, mixed $rank, bool $withscore = false): Clus 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 + public function zrangestore(mixed $dstkey, mixed $srckey, mixed $start, mixed $end, mixed $options = null): Cluster|false|int { return $this->initializeLazyObject()->zrangestore(...\func_get_args()); } @@ -1087,102 +1087,102 @@ public function zrank(mixed $key, mixed $rank, bool $withscore = false): Cluster 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 + public function zrangebylex(mixed $key, mixed $min, mixed $max, int $offset = -1, int $count = -1): 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 + public function zrevrangebylex(mixed $key, mixed $max, mixed $min, int $offset = -1, int $count = -1): Cluster|array|false { return $this->initializeLazyObject()->zrevrangebylex(...\func_get_args()); } - public function zrem(mixed $key, mixed ...$args): \Relay\Cluster|false|int + public function zrem(mixed $key, mixed ...$args): Cluster|false|int { return $this->initializeLazyObject()->zrem(...\func_get_args()); } - public function zremrangebylex(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + public function zremrangebylex(mixed $key, mixed $min, mixed $max): Cluster|false|int { return $this->initializeLazyObject()->zremrangebylex(...\func_get_args()); } - public function zremrangebyrank(mixed $key, int $start, int $end): \Relay\Cluster|false|int + public function zremrangebyrank(mixed $key, int $start, int $end): Cluster|false|int { return $this->initializeLazyObject()->zremrangebyrank(...\func_get_args()); } - public function zremrangebyscore(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + public function zremrangebyscore(mixed $key, mixed $min, mixed $max): Cluster|false|int { return $this->initializeLazyObject()->zremrangebyscore(...\func_get_args()); } - public function zcard(mixed $key): \Relay\Cluster|false|int + public function zcard(mixed $key): Cluster|false|int { return $this->initializeLazyObject()->zcard(...\func_get_args()); } - public function zcount(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + public function zcount(mixed $key, mixed $min, mixed $max): Cluster|false|int { return $this->initializeLazyObject()->zcount(...\func_get_args()); } - public function zdiff(array $keys, array|null $options = null): \Relay\Cluster|array|false + public function zdiff(array $keys, array|null $options = null): Cluster|array|false { return $this->initializeLazyObject()->zdiff(...\func_get_args()); } - public function zdiffstore(mixed $dstkey, array $keys): \Relay\Cluster|false|int + public function zdiffstore(mixed $dstkey, array $keys): Cluster|false|int { return $this->initializeLazyObject()->zdiffstore(...\func_get_args()); } - public function zincrby(mixed $key, float $score, mixed $member): \Relay\Cluster|false|float + public function zincrby(mixed $key, float $score, mixed $member): Cluster|false|float { return $this->initializeLazyObject()->zincrby(...\func_get_args()); } - public function zlexcount(mixed $key, mixed $min, mixed $max): \Relay\Cluster|false|int + public function zlexcount(mixed $key, mixed $min, mixed $max): Cluster|false|int { return $this->initializeLazyObject()->zlexcount(...\func_get_args()); } - public function zmscore(mixed $key, mixed ...$members): \Relay\Cluster|array|false + public function zmscore(mixed $key, mixed ...$members): 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 + public function zinter(array $keys, array|null $weights = null, mixed $options = null): Cluster|array|false { return $this->initializeLazyObject()->zinter(...\func_get_args()); } - public function zintercard(array $keys, int $limit = -1): \Relay\Cluster|false|int + public function zintercard(array $keys, int $limit = -1): 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 + public function zinterstore(mixed $dstkey, array $keys, array|null $weights = null, mixed $options = null): 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 + public function zunion(array $keys, array|null $weights = null, mixed $options = null): 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 + public function zunionstore(mixed $dstkey, array $keys, array|null $weights = null, mixed $options = null): Cluster|false|int { return $this->initializeLazyObject()->zunionstore(...\func_get_args()); } - public function zpopmin(mixed $key, int $count = 1): \Relay\Cluster|array|false + public function zpopmin(mixed $key, int $count = 1): Cluster|array|false { return $this->initializeLazyObject()->zpopmin(...\func_get_args()); } - public function zpopmax(mixed $key, int $count = 1): \Relay\Cluster|array|false + public function zpopmax(mixed $key, int $count = 1): Cluster|array|false { return $this->initializeLazyObject()->zpopmax(...\func_get_args()); } @@ -1197,7 +1197,7 @@ 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 + public function copy(mixed $srckey, mixed $dstkey, array|null $options = null): Cluster|bool { return $this->initializeLazyObject()->copy(...\func_get_args()); } From ab54d0c5c7a96128db73a986de793b56937ace62 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas <nicolas.grekas@gmail.com> Date: Fri, 18 Apr 2025 14:51:48 +0200 Subject: [PATCH 12/12] Don't enable tracing unless the profiler is enabled --- Adapter/TraceableAdapter.php | 37 ++++++++++++++++++++++ Adapter/TraceableTagAwareAdapter.php | 7 ++-- DependencyInjection/CacheCollectorPass.php | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Adapter/TraceableAdapter.php b/Adapter/TraceableAdapter.php index 43628e4..3e1bf2b 100644 --- a/Adapter/TraceableAdapter.php +++ b/Adapter/TraceableAdapter.php @@ -34,6 +34,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, NamespacedPo public function __construct( protected AdapterInterface $pool, + protected readonly ?\Closure $disabled = null, ) { } @@ -45,6 +46,9 @@ public function get(string $key, callable $callback, ?float $beta = null, ?array if (!$this->pool instanceof CacheInterface) { throw new BadMethodCallException(\sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class)); } + if ($this->disabled?->__invoke()) { + return $this->pool->get($key, $callback, $beta, $metadata); + } $isHit = true; $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) { @@ -71,6 +75,9 @@ public function get(string $key, callable $callback, ?float $beta = null, ?array public function getItem(mixed $key): CacheItem { + if ($this->disabled?->__invoke()) { + return $this->pool->getItem($key); + } $event = $this->start(__FUNCTION__); try { $item = $this->pool->getItem($key); @@ -88,6 +95,9 @@ public function getItem(mixed $key): CacheItem public function hasItem(mixed $key): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->hasItem($key); + } $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->hasItem($key); @@ -98,6 +108,9 @@ public function hasItem(mixed $key): bool public function deleteItem(mixed $key): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->deleteItem($key); + } $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->deleteItem($key); @@ -108,6 +121,9 @@ public function deleteItem(mixed $key): bool public function save(CacheItemInterface $item): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->save($item); + } $event = $this->start(__FUNCTION__); try { return $event->result[$item->getKey()] = $this->pool->save($item); @@ -118,6 +134,9 @@ public function save(CacheItemInterface $item): bool public function saveDeferred(CacheItemInterface $item): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->saveDeferred($item); + } $event = $this->start(__FUNCTION__); try { return $event->result[$item->getKey()] = $this->pool->saveDeferred($item); @@ -128,6 +147,9 @@ public function saveDeferred(CacheItemInterface $item): bool public function getItems(array $keys = []): iterable { + if ($this->disabled?->__invoke()) { + return $this->pool->getItems($keys); + } $event = $this->start(__FUNCTION__); try { $result = $this->pool->getItems($keys); @@ -151,6 +173,9 @@ public function getItems(array $keys = []): iterable public function clear(string $prefix = ''): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->clear($prefix); + } $event = $this->start(__FUNCTION__); try { if ($this->pool instanceof AdapterInterface) { @@ -165,6 +190,9 @@ public function clear(string $prefix = ''): bool public function deleteItems(array $keys): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->deleteItems($keys); + } $event = $this->start(__FUNCTION__); $event->result['keys'] = $keys; try { @@ -176,6 +204,9 @@ public function deleteItems(array $keys): bool public function commit(): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->commit(); + } $event = $this->start(__FUNCTION__); try { return $event->result = $this->pool->commit(); @@ -189,6 +220,9 @@ public function prune(): bool if (!$this->pool instanceof PruneableInterface) { return false; } + if ($this->disabled?->__invoke()) { + return $this->pool->prune(); + } $event = $this->start(__FUNCTION__); try { return $event->result = $this->pool->prune(); @@ -208,6 +242,9 @@ public function reset(): void public function delete(string $key): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->deleteItem($key); + } $event = $this->start(__FUNCTION__); try { return $event->result[$key] = $this->pool->deleteItem($key); diff --git a/Adapter/TraceableTagAwareAdapter.php b/Adapter/TraceableTagAwareAdapter.php index c85d199..bde27c6 100644 --- a/Adapter/TraceableTagAwareAdapter.php +++ b/Adapter/TraceableTagAwareAdapter.php @@ -18,13 +18,16 @@ */ class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface { - public function __construct(TagAwareAdapterInterface $pool) + public function __construct(TagAwareAdapterInterface $pool, ?\Closure $disabled = null) { - parent::__construct($pool); + parent::__construct($pool, $disabled); } public function invalidateTags(array $tags): bool { + if ($this->disabled?->__invoke()) { + return $this->pool->invalidateTags($tags); + } $event = $this->start(__FUNCTION__); try { return $event->result = $this->pool->invalidateTags($tags); diff --git a/DependencyInjection/CacheCollectorPass.php b/DependencyInjection/CacheCollectorPass.php index ed95740..0b8d6ae 100644 --- a/DependencyInjection/CacheCollectorPass.php +++ b/DependencyInjection/CacheCollectorPass.php @@ -52,7 +52,7 @@ private function addToCollector(string $id, string $name, ContainerBuilder $cont if (!$definition->isPublic() || !$definition->isPrivate()) { $recorder->setPublic($definition->isPublic()); } - $recorder->setArguments([new Reference($innerId = $id.'.recorder_inner')]); + $recorder->setArguments([new Reference($innerId = $id.'.recorder_inner'), new Reference('profiler.is_disabled_state_checker', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)]); foreach ($definition->getMethodCalls() as [$method, $args]) { if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) {