diff --git a/.github/patch-types.php b/.github/patch-types.php index eaa085bfae9bb..811f74311eb86 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -54,6 +54,7 @@ case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionUnionTypeWithIntersectionFixture.php'): case false !== strpos($file, '/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyProxy/ReadOnlyClass.php'): + case false !== strpos($file, '/src/Symfony/Component/Cache/Traits/RelayProxy.php'): continue 2; } diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 171eab250982e..618763da4544e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -134,11 +134,19 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: "none" - extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap" + extensions: "json,couchbase-3.2.2,memcached,mongodb-1.12.0,redis,rdkafka,xsl,ldap,msgpack,igbinary" ini-values: date.timezone=UTC,memory_limit=-1,default_socket_timeout=10,session.gc_probability=0,apc.enable_cli=1,zend.assertions=1 php-version: "${{ matrix.php }}" tools: pecl + - name: Install Relay + run: | + curl -L "https://builds.r2.relay.so/dev/relay-dev-php${{ matrix.php }}-debian-x86-64.tar.gz" | tar xz + cd relay-dev-php${{ matrix.php }}-debian-x86-64 + sudo cp relay.ini $(php-config --ini-dir) + sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so + sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so + - name: Display versions run: | php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;' diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index 26d816ccb6c63..e4d8ccfe4c1b8 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -28,6 +28,14 @@ jobs: ini-values: "memory_limit=-1" coverage: none + - name: Install Relay + run: | + curl -L "https://builds.r2.relay.so/dev/relay-dev-php8.1-debian-x86-64.tar.gz" | tar xz + cd relay-dev-php8.1-debian-x86-64 + sudo cp relay.ini $(php-config --ini-dir) + sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so + sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so + - name: Checkout target branch uses: actions/checkout@v3 with: diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index db6a854c1f398..7245326249db2 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -67,6 +67,7 @@ // stop removing spaces on the end of the line in strings ->notPath('Symfony/Component/Messenger/Tests/Command/FailedMessagesShowCommandTest.php') // auto-generated proxies + ->notPath('Symfony/Component/Cache/Traits/RelayProxy.php') ->notPath('Symfony/Component/Cache/Traits/Redis5Proxy.php') ->notPath('Symfony/Component/Cache/Traits/Redis6Proxy.php') ->notPath('Symfony/Component/Cache/Traits/RedisCluster5Proxy.php') diff --git a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php index 9cb8d58991d79..d8e37b1d7b2f3 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php @@ -18,7 +18,7 @@ class RedisAdapter extends AbstractAdapter { use RedisTrait; - public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { $this->init($redis, $namespace, $defaultLifetime, $marshaller); } diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index a004709ba504d..b14c26db80455 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -16,6 +16,7 @@ use Predis\Connection\Aggregate\ReplicationInterface; use Predis\Response\ErrorInterface; use Predis\Response\Status; +use Relay\Relay; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Exception\LogicException; @@ -59,18 +60,19 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter private string $redisEvictionPolicy; private string $namespace; - public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) + public function __construct(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) { throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection()))); } - if (\defined('Redis::OPT_COMPRESSION') && \in_array($redis::class, [\Redis::class, \RedisArray::class, \RedisCluster::class], true)) { - $compression = $redis->getOption(\Redis::OPT_COMPRESSION); + $isRelay = $redis instanceof Relay; + 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); foreach (\is_array($compression) ? $compression : [$compression] as $c) { - if (\Redis::COMPRESSION_NONE !== $c) { - throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); + if ($isRelay ? Relay::COMPRESSION_NONE : \Redis::COMPRESSION_NONE !== $c) { + throw new InvalidArgumentException(sprintf('redis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class)); } } } @@ -154,7 +156,7 @@ protected function doDeleteYieldTags(array $ids): iterable }); foreach ($results as $id => $result) { - if ($result instanceof \RedisException || $result instanceof ErrorInterface) { + 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]); continue; @@ -221,7 +223,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(\Redis::OPT_PREFIX) ?? '')) { + } elseif (\is_array($prefix = $this->redis->getOption($this->redis instanceof Relay ? Relay::OPT_PREFIX : \Redis::OPT_PREFIX) ?? '')) { $prefix = current($prefix); } @@ -242,7 +244,7 @@ protected function doInvalidate(array $tagIds): bool $success = true; foreach ($results as $id => $values) { - if ($values instanceof \RedisException || $values instanceof ErrorInterface) { + if ($values instanceof \RedisException || $values instanceof \Relay\Exception || $values instanceof ErrorInterface) { CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]); $success = false; diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 902d0ef29cb24..635a9408176b9 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for Relay PHP extension for Redis + 6.1 --- diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterSentinelTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterSentinelTest.php new file mode 100644 index 0000000000000..60f506f931871 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterSentinelTest.php @@ -0,0 +1,42 @@ + + * + * 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 PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; +use Relay\Sentinel; +use Symfony\Component\Cache\Adapter\AbstractAdapter; + +/** + * @group integration + */ +class RelayAdapterSentinelTest extends AbstractRedisAdapterTest +{ + public static function setUpBeforeClass(): void + { + if (!class_exists(Sentinel::class)) { + throw new SkippedTestSuiteError('The Relay\Sentinel class is required.'); + } + if (!$hosts = getenv('REDIS_SENTINEL_HOSTS')) { + throw new SkippedTestSuiteError('REDIS_SENTINEL_HOSTS env var is not defined.'); + } + if (!$service = getenv('REDIS_SENTINEL_SERVICE')) { + throw new SkippedTestSuiteError('REDIS_SENTINEL_SERVICE env var is not defined.'); + } + + self::$redis = AbstractAdapter::createConnection( + 'redis:?host['.str_replace(' ', ']&host[', $hosts).']', + ['redis_sentinel' => $service, 'prefix' => 'prefix_', 'class' => Relay::class], + ); + self::assertInstanceOf(Relay::class, self::$redis); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterTest.php new file mode 100644 index 0000000000000..e98fc9bcf69e1 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/RelayAdapterTest.php @@ -0,0 +1,56 @@ + + * + * 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 PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\RelayProxy; + +/** + * @requires extension relay + * + * @group integration + */ +class RelayAdapterTest extends AbstractRedisAdapterTest +{ + public static function setUpBeforeClass(): void + { + try { + new Relay(...explode(':', getenv('REDIS_HOST'))); + } catch (\Relay\Exception $e) { + throw new SkippedTestSuiteError(getenv('REDIS_HOST').': '.$e->getMessage()); + } + self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'), ['lazy' => true, 'class' => Relay::class]); + self::assertInstanceOf(RelayProxy::class, self::$redis); + } + + public function testCreateHostConnection() + { + $redis = RedisAdapter::createConnection('redis://'.getenv('REDIS_HOST').'?class=Relay\Relay'); + $this->assertInstanceOf(Relay::class, $redis); + $this->assertTrue($redis->isConnected()); + $this->assertSame(0, $redis->getDbNum()); + } + + public function testLazyConnection() + { + $redis = RedisAdapter::createConnection('redis://nonexistenthost?class=Relay\Relay&lazy=1'); + $this->assertInstanceOf(RelayProxy::class, $redis); + // no exception until now + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Failed to resolve host address'); + $redis->getHost(); // yep, only here exception is thrown + } +} diff --git a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php index 27fabf700af8a..dbfcef951cae7 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php +++ b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php @@ -12,15 +12,15 @@ namespace Symfony\Component\Cache\Tests\Traits; use PHPUnit\Framework\TestCase; +use Relay\Relay; use Symfony\Component\VarExporter\LazyProxyTrait; use Symfony\Component\VarExporter\ProxyHelper; -/** - * @requires extension redis - */ class RedisProxiesTest extends TestCase { /** + * @requires extension redis + * * @testWith ["Redis"] * ["RedisCluster"] */ @@ -50,6 +50,36 @@ public function testRedis5Proxy($class) } /** + * @requires extension relay + */ + public function testRelayProxy() + { + $proxy = file_get_contents(\dirname(__DIR__, 2).'/Traits/RelayProxy.php'); + $proxy = substr($proxy, 0, 8 + strpos($proxy, "\n ];")); + $methods = []; + + foreach ((new \ReflectionClass(Relay::class))->getMethods() as $method) { + if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name) || $method->isStatic()) { + continue; + } + $return = $method->getReturnType() instanceof \ReflectionNamedType && 'void' === (string) $method->getReturnType() ? '' : 'return '; + $methods[] = "\n ".ProxyHelper::exportSignature($method, false)."\n".<<lazyObjectReal->{$method->name}(...\\func_get_args()); + } + + EOPHP; + } + + uksort($methods, 'strnatcmp'); + $proxy .= implode('', $methods)."}\n"; + + $this->assertStringEqualsFile(\dirname(__DIR__, 2).'/Traits/RelayProxy.php', $proxy); + } + + /** + * @requires extension redis + * * @testWith ["Redis", "redis"] * ["RedisCluster", "redis_cluster"] */ diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 131b9f3268f1e..a226fad13bf7c 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -17,6 +17,8 @@ use Predis\Connection\Aggregate\ReplicationInterface; use Predis\Response\ErrorInterface; use Predis\Response\Status; +use Relay\Relay; +use Relay\Sentinel; use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Marshaller\DefaultMarshaller; @@ -45,10 +47,10 @@ trait RedisTrait 'failover' => 'none', 'ssl' => null, // see https://php.net/context.ssl ]; - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis; private MarshallerInterface $marshaller; - private function init(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) + private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller) { parent::__construct($namespace, $defaultLifetime); @@ -80,7 +82,7 @@ private function init(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $ * * @throws InvalidArgumentException when the DSN is invalid */ - public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface + public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay { if (str_starts_with($dsn, 'redis:')) { $scheme = 'redis'; @@ -165,28 +167,39 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $params += $query + $options + self::$defaultConnectionOptions; - if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) { - throw new CacheException('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher.'); + if (isset($params['redis_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 ($params['redis_cluster'] && isset($params['redis_sentinel'])) { throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); } - if (null === $params['class'] && \extension_loaded('redis')) { - $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) && !isset($params['redis_sentinel']) ? \RedisArray::class : \Redis::class); - } else { - $class = $params['class'] ?? \Predis\Client::class; - - if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class)) { - throw new CacheException(sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and ext-redis >= 5.2 not found.', $class)); - } + $class = $params['class'] ?? match (true) { + $params['redis_cluster'] => \extension_loaded('redis') ? \RedisCluster::class : \Predis\Client::class, + isset($params['redis_sentinel']) => match (true) { + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }, + 1 < \count($hosts) && \extension_loaded('redis') => 1 < \count($hosts) ? \RedisArray::class : \Redis::class, + \extension_loaded('redis') => \Redis::class, + \extension_loaded('relay') => Relay::class, + default => \Predis\Client::class, + }; + + if (isset($params['redis_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)); } - if (is_a($class, \Redis::class, true)) { + $isRedisExt = is_a($class, \Redis::class, true); + $isRelayExt = !$isRedisExt && is_a($class, Relay::class, true); + + if ($isRedisExt || $isRelayExt) { $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect'; - $initializer = static function () use ($class, $connect, $params, $auth, $hosts, $tls) { + $initializer = static function () use ($class, $isRedisExt, $connect, $params, $auth, $hosts, $tls) { + $sentinelClass = $isRedisExt ? \RedisSentinel::class : Sentinel::class; $redis = new $class(); $hostIndex = 0; do { @@ -205,7 +218,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra if (\defined('Redis::OPT_NULL_MULTIBULK_AS_NULL') && isset($params['auth'])) { $extra = [$params['auth']]; } - $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra); + $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'])) { [$host, $port] = $address; @@ -223,7 +236,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra if (isset($params['auth'])) { $extra['auth'] = $params['auth']; } - @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [$extra] : []); + @$redis->{$connect}($host, $port, (float) $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') || !$isRedisExt ? [$extra] : []); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { @@ -243,17 +256,21 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new InvalidArgumentException('Redis connection failed: '.$e.'.'); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + 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']); } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } return $redis; }; - $redis = $params['lazy'] ? RedisProxy::createLazyProxy($initializer) : $initializer(); + if ($params['lazy']) { + $redis = $isRedisExt ? RedisProxy::createLazyProxy($initializer) : RelayProxy::createLazyProxy($initializer); + } else { + $redis = $initializer(); + } } elseif (is_a($class, \RedisArray::class, true)) { foreach ($hosts as $i => $host) { $hosts[$i] = match ($host['scheme']) { @@ -271,11 +288,11 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + 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, \RedisCluster::class, true)) { - $initializer = static function () use ($class, $params, $hosts) { + $initializer = static function () use ($isRedisExt, $class, $params, $hosts) { foreach ($hosts as $i => $host) { $hosts[$i] = match ($host['scheme']) { 'tcp' => $host['host'].':'.$host['port'], @@ -290,8 +307,8 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new InvalidArgumentException('Redis connection failed: '.$e->getMessage()); } - if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { - $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); + 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']); } $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, match ($params['failover']) { 'error' => \RedisCluster::FAILOVER_ERROR, @@ -343,7 +360,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra $redis->getConnection()->setSentinelTimeout($params['timeout']); } } elseif (class_exists($class, false)) { - throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class)); + throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster", "Relay\Relay" nor "Predis\ClientInterface".', $class)); } else { throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); } @@ -413,7 +430,10 @@ protected function doClear(string $namespace): bool $info = $host->info('Server'); $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0']; - if (!$host instanceof \Predis\ClientInterface) { + if ($host instanceof Relay) { + $prefix = Relay::SCAN_PREFIX & $host->getOption(Relay::OPT_SCAN) ? '' : $host->getOption(Relay::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(Relay::OPT_PREFIX) ?? ''); + } elseif (!$host instanceof \Predis\ClientInterface) { $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX); $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); } @@ -549,7 +569,7 @@ private function pipeline(\Closure $generator, object $redis = null): \Generator $results[$k] = $connections[$h][$c]; } } else { - $redis->multi(\Redis::PIPELINE); + $redis->multi($redis instanceof Relay ? Relay::PIPELINE : \Redis::PIPELINE); foreach ($generator() as $command => $args) { $redis->{$command}(...$args); $ids[] = 'eval' === $command ? $args[1][0] : $args[0]; @@ -558,7 +578,7 @@ private function pipeline(\Closure $generator, object $redis = null): \Generator } if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { - $e = new \RedisException($redis->getLastError()); + $e = $redis instanceof Relay ? new \Relay\Exception($redis->getLastError()) : new \RedisException($redis->getLastError()); $results = array_map(fn ($v) => false === $v ? $e : $v, (array) $results); } diff --git a/src/Symfony/Component/Cache/Traits/RelayProxy.php b/src/Symfony/Component/Cache/Traits/RelayProxy.php new file mode 100644 index 0000000000000..d458062163ada --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/RelayProxy.php @@ -0,0 +1,1262 @@ + + * + * 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\Relay; +use Symfony\Component\VarExporter\LazyObjectInterface; +use Symfony\Component\VarExporter\LazyProxyTrait; +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 RelayProxy extends Relay implements ResetInterface, LazyObjectInterface +{ + use LazyProxyTrait { + resetLazyObject as reset; + } + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'lazyObjectReal' => [self::class, 'lazyObjectReal', null], + "\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null], + ]; + + public function __construct($host = null, $port = 6379, $connect_timeout = 0.0, $command_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0) + { + return $this->lazyObjectReal->__construct(...\func_get_args()); + } + + public function connect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return $this->lazyObjectReal->connect(...\func_get_args()); + } + + public function pconnect($host, $port = 6379, $timeout = 0.0, $persistent_id = null, $retry_interval = 0, $read_timeout = 0.0, #[\SensitiveParameter] $context = [], $database = 0): bool + { + return $this->lazyObjectReal->pconnect(...\func_get_args()); + } + + public function close(): bool + { + return $this->lazyObjectReal->close(...\func_get_args()); + } + + public function pclose(): bool + { + return $this->lazyObjectReal->pclose(...\func_get_args()); + } + + public function listen($callback): bool + { + return $this->lazyObjectReal->listen(...\func_get_args()); + } + + public function onFlushed($callback): bool + { + return $this->lazyObjectReal->onFlushed(...\func_get_args()); + } + + public function onInvalidated($callback, $pattern = null): bool + { + return $this->lazyObjectReal->onInvalidated(...\func_get_args()); + } + + public function dispatchEvents(): false|int + { + return $this->lazyObjectReal->dispatchEvents(...\func_get_args()); + } + + public function getOption($option): mixed + { + return $this->lazyObjectReal->getOption(...\func_get_args()); + } + + public function option($option, $value = null): mixed + { + return $this->lazyObjectReal->option(...\func_get_args()); + } + + public function setOption($option, $value): bool + { + return $this->lazyObjectReal->setOption(...\func_get_args()); + } + + public function getTimeout(): false|float + { + return $this->lazyObjectReal->getTimeout(...\func_get_args()); + } + + public function timeout(): false|float + { + return $this->lazyObjectReal->timeout(...\func_get_args()); + } + + public function getReadTimeout(): false|float + { + return $this->lazyObjectReal->getReadTimeout(...\func_get_args()); + } + + public function readTimeout(): false|float + { + return $this->lazyObjectReal->readTimeout(...\func_get_args()); + } + + public function getBytes(): array + { + return $this->lazyObjectReal->getBytes(...\func_get_args()); + } + + public function bytes(): array + { + return $this->lazyObjectReal->bytes(...\func_get_args()); + } + + public function getHost(): false|string + { + return $this->lazyObjectReal->getHost(...\func_get_args()); + } + + public function isConnected(): bool + { + return $this->lazyObjectReal->isConnected(...\func_get_args()); + } + + public function getPort(): false|int + { + return $this->lazyObjectReal->getPort(...\func_get_args()); + } + + public function getAuth(): mixed + { + return $this->lazyObjectReal->getAuth(...\func_get_args()); + } + + public function getDbNum(): mixed + { + return $this->lazyObjectReal->getDbNum(...\func_get_args()); + } + + public function _serialize($value): mixed + { + return $this->lazyObjectReal->_serialize(...\func_get_args()); + } + + public function _unserialize($value): mixed + { + return $this->lazyObjectReal->_unserialize(...\func_get_args()); + } + + public function _compress($value): string + { + return $this->lazyObjectReal->_compress(...\func_get_args()); + } + + public function _uncompress($value): string + { + return $this->lazyObjectReal->_uncompress(...\func_get_args()); + } + + public function _pack($value): string + { + return $this->lazyObjectReal->_pack(...\func_get_args()); + } + + public function _unpack($value): mixed + { + return $this->lazyObjectReal->_unpack(...\func_get_args()); + } + + public function _prefix($value): string + { + return $this->lazyObjectReal->_prefix(...\func_get_args()); + } + + public function getLastError(): ?string + { + return $this->lazyObjectReal->getLastError(...\func_get_args()); + } + + public function clearLastError(): bool + { + return $this->lazyObjectReal->clearLastError(...\func_get_args()); + } + + public function endpointId(): false|string + { + return $this->lazyObjectReal->endpointId(...\func_get_args()); + } + + public function getPersistentID(): false|string + { + return $this->lazyObjectReal->getPersistentID(...\func_get_args()); + } + + public function socketId(): false|string + { + return $this->lazyObjectReal->socketId(...\func_get_args()); + } + + public function rawCommand($cmd, ...$args): mixed + { + return $this->lazyObjectReal->rawCommand(...\func_get_args()); + } + + public function select($db): \Relay\Relay|bool + { + return $this->lazyObjectReal->select(...\func_get_args()); + } + + public function auth(#[\SensitiveParameter] $auth): bool + { + return $this->lazyObjectReal->auth(...\func_get_args()); + } + + public function info(...$sections): \Relay\Relay|array|false + { + return $this->lazyObjectReal->info(...\func_get_args()); + } + + public function flushdb($async = false): \Relay\Relay|bool + { + return $this->lazyObjectReal->flushdb(...\func_get_args()); + } + + public function flushall($async = false): \Relay\Relay|bool + { + return $this->lazyObjectReal->flushall(...\func_get_args()); + } + + public function fcall($name, $argv = [], $keys = [], $handler = null): mixed + { + return $this->lazyObjectReal->fcall(...\func_get_args()); + } + + public function fcall_ro($name, $argv = [], $keys = [], $handler = null): mixed + { + return $this->lazyObjectReal->fcall_ro(...\func_get_args()); + } + + public function function($op, ...$args): mixed + { + return $this->lazyObjectReal->function(...\func_get_args()); + } + + public function dbsize(): \Relay\Relay|false|int + { + return $this->lazyObjectReal->dbsize(...\func_get_args()); + } + + public function dump($key): \Relay\Relay|false|string + { + return $this->lazyObjectReal->dump(...\func_get_args()); + } + + public function replicaof($host = null, $port = 0): \Relay\Relay|bool + { + return $this->lazyObjectReal->replicaof(...\func_get_args()); + } + + public function restore($key, $ttl, $value, $options = null): \Relay\Relay|bool + { + return $this->lazyObjectReal->restore(...\func_get_args()); + } + + public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Relay\Relay|bool + { + return $this->lazyObjectReal->migrate(...\func_get_args()); + } + + public function copy($src, $dst, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->copy(...\func_get_args()); + } + + public function echo($arg): \Relay\Relay|bool|string + { + return $this->lazyObjectReal->echo(...\func_get_args()); + } + + public function ping($arg = null): \Relay\Relay|bool|string + { + return $this->lazyObjectReal->ping(...\func_get_args()); + } + + public function idleTime(): \Relay\Relay|false|int + { + return $this->lazyObjectReal->idleTime(...\func_get_args()); + } + + public function randomkey(): \Relay\Relay|bool|null|string + { + return $this->lazyObjectReal->randomkey(...\func_get_args()); + } + + public function time(): \Relay\Relay|array|false + { + return $this->lazyObjectReal->time(...\func_get_args()); + } + + public function bgrewriteaof(): \Relay\Relay|bool + { + return $this->lazyObjectReal->bgrewriteaof(...\func_get_args()); + } + + public function lastsave(): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lastsave(...\func_get_args()); + } + + public function bgsave(): \Relay\Relay|bool + { + return $this->lazyObjectReal->bgsave(...\func_get_args()); + } + + public function save(): \Relay\Relay|bool + { + return $this->lazyObjectReal->save(...\func_get_args()); + } + + public function role(): \Relay\Relay|array|false + { + return $this->lazyObjectReal->role(...\func_get_args()); + } + + public function ttl($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->ttl(...\func_get_args()); + } + + public function pttl($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->pttl(...\func_get_args()); + } + + public function exists(...$keys): \Relay\Relay|bool|int + { + return $this->lazyObjectReal->exists(...\func_get_args()); + } + + public function eval($script, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->eval(...\func_get_args()); + } + + public function eval_ro($script, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->eval_ro(...\func_get_args()); + } + + public function evalsha($sha, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->evalsha(...\func_get_args()); + } + + public function evalsha_ro($sha, $args = [], $num_keys = 0): mixed + { + return $this->lazyObjectReal->evalsha_ro(...\func_get_args()); + } + + public function client($operation, ...$args): mixed + { + return $this->lazyObjectReal->client(...\func_get_args()); + } + + public function geoadd($key, $lng, $lat, $member, ...$other_triples_and_options): \Relay\Relay|false|int + { + return $this->lazyObjectReal->geoadd(...\func_get_args()); + } + + public function geodist($key, $src, $dst, $unit = null): \Relay\Relay|false|float + { + return $this->lazyObjectReal->geodist(...\func_get_args()); + } + + public function geohash($key, $member, ...$other_members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->geohash(...\func_get_args()); + } + + public function georadius($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadius(...\func_get_args()); + } + + public function georadiusbymember($key, $member, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadiusbymember(...\func_get_args()); + } + + public function georadiusbymember_ro($key, $member, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadiusbymember_ro(...\func_get_args()); + } + + public function georadius_ro($key, $lng, $lat, $radius, $unit, $options = []): mixed + { + return $this->lazyObjectReal->georadius_ro(...\func_get_args()); + } + + public function geosearch($key, $position, $shape, $unit, $options = []): \Relay\Relay|array + { + return $this->lazyObjectReal->geosearch(...\func_get_args()); + } + + public function geosearchstore($dst, $src, $position, $shape, $unit, $options = []): \Relay\Relay|false|int + { + return $this->lazyObjectReal->geosearchstore(...\func_get_args()); + } + + public function get($key): mixed + { + return $this->lazyObjectReal->get(...\func_get_args()); + } + + public function getset($key, $value): mixed + { + return $this->lazyObjectReal->getset(...\func_get_args()); + } + + public function getrange($key, $start, $end): \Relay\Relay|false|string + { + return $this->lazyObjectReal->getrange(...\func_get_args()); + } + + public function setrange($key, $start, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->setrange(...\func_get_args()); + } + + public function getbit($key, $pos): \Relay\Relay|false|int + { + return $this->lazyObjectReal->getbit(...\func_get_args()); + } + + public function bitcount($key, $start = 0, $end = -1, $by_bit = false): \Relay\Relay|false|int + { + return $this->lazyObjectReal->bitcount(...\func_get_args()); + } + + public function config($operation, $key = null, $value = null): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->config(...\func_get_args()); + } + + public function command(...$args): \Relay\Relay|array|false|int + { + return $this->lazyObjectReal->command(...\func_get_args()); + } + + public function bitop($operation, $dstkey, $srckey, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->bitop(...\func_get_args()); + } + + public function bitpos($key, $bit, $start = null, $end = null, $bybit = false): \Relay\Relay|false|int + { + return $this->lazyObjectReal->bitpos(...\func_get_args()); + } + + public function setbit($key, $pos, $val): \Relay\Relay|false|int + { + return $this->lazyObjectReal->setbit(...\func_get_args()); + } + + public function acl($cmd, ...$args): mixed + { + return $this->lazyObjectReal->acl(...\func_get_args()); + } + + public function append($key, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->append(...\func_get_args()); + } + + public function set($key, $value, $options = null): mixed + { + return $this->lazyObjectReal->set(...\func_get_args()); + } + + public function getex($key, $options = null): mixed + { + return $this->lazyObjectReal->getex(...\func_get_args()); + } + + public function getdel($key): mixed + { + return $this->lazyObjectReal->getdel(...\func_get_args()); + } + + public function setex($key, $seconds, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->setex(...\func_get_args()); + } + + public function pfadd($key, $elements): \Relay\Relay|false|int + { + return $this->lazyObjectReal->pfadd(...\func_get_args()); + } + + public function pfcount($key): \Relay\Relay|int + { + return $this->lazyObjectReal->pfcount(...\func_get_args()); + } + + public function pfmerge($dst, $srckeys): \Relay\Relay|bool + { + return $this->lazyObjectReal->pfmerge(...\func_get_args()); + } + + public function psetex($key, $milliseconds, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->psetex(...\func_get_args()); + } + + public function publish($channel, $message): \Relay\Relay|false|int + { + return $this->lazyObjectReal->publish(...\func_get_args()); + } + + public function setnx($key, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->setnx(...\func_get_args()); + } + + public function mget($keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->mget(...\func_get_args()); + } + + public function move($key, $db): \Relay\Relay|false|int + { + return $this->lazyObjectReal->move(...\func_get_args()); + } + + public function mset($kvals): \Relay\Relay|bool + { + return $this->lazyObjectReal->mset(...\func_get_args()); + } + + public function msetnx($kvals): \Relay\Relay|bool + { + return $this->lazyObjectReal->msetnx(...\func_get_args()); + } + + public function rename($key, $newkey): \Relay\Relay|bool + { + return $this->lazyObjectReal->rename(...\func_get_args()); + } + + public function renamenx($key, $newkey): \Relay\Relay|bool + { + return $this->lazyObjectReal->renamenx(...\func_get_args()); + } + + public function del(...$keys): \Relay\Relay|bool|int + { + return $this->lazyObjectReal->del(...\func_get_args()); + } + + public function unlink(...$keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->unlink(...\func_get_args()); + } + + public function expire($key, $seconds, $mode = null): \Relay\Relay|bool + { + return $this->lazyObjectReal->expire(...\func_get_args()); + } + + public function pexpire($key, $milliseconds): \Relay\Relay|bool + { + return $this->lazyObjectReal->pexpire(...\func_get_args()); + } + + public function expireat($key, $timestamp): \Relay\Relay|bool + { + return $this->lazyObjectReal->expireat(...\func_get_args()); + } + + public function expiretime($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->expiretime(...\func_get_args()); + } + + public function pexpireat($key, $timestamp_ms): \Relay\Relay|bool + { + return $this->lazyObjectReal->pexpireat(...\func_get_args()); + } + + public function pexpiretime($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->pexpiretime(...\func_get_args()); + } + + public function persist($key): \Relay\Relay|bool + { + return $this->lazyObjectReal->persist(...\func_get_args()); + } + + public function type($key): \Relay\Relay|bool|int|string + { + return $this->lazyObjectReal->type(...\func_get_args()); + } + + public function lmove($srckey, $dstkey, $srcpos, $dstpos): \Relay\Relay|false|null|string + { + return $this->lazyObjectReal->lmove(...\func_get_args()); + } + + public function blmove($srckey, $dstkey, $srcpos, $dstpos, $timeout): \Relay\Relay|false|null|string + { + return $this->lazyObjectReal->blmove(...\func_get_args()); + } + + public function lrange($key, $start, $stop): \Relay\Relay|array|false + { + return $this->lazyObjectReal->lrange(...\func_get_args()); + } + + public function lpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lpush(...\func_get_args()); + } + + public function rpush($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->rpush(...\func_get_args()); + } + + public function lpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lpushx(...\func_get_args()); + } + + public function rpushx($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->rpushx(...\func_get_args()); + } + + public function lset($key, $index, $mem): \Relay\Relay|bool + { + return $this->lazyObjectReal->lset(...\func_get_args()); + } + + public function lpop($key, $count = 1): mixed + { + return $this->lazyObjectReal->lpop(...\func_get_args()); + } + + public function lpos($key, $value, $options = null): \Relay\Relay|array|false|int|null + { + return $this->lazyObjectReal->lpos(...\func_get_args()); + } + + public function rpop($key, $count = 1): mixed + { + return $this->lazyObjectReal->rpop(...\func_get_args()); + } + + public function rpoplpush($source, $dest): mixed + { + return $this->lazyObjectReal->rpoplpush(...\func_get_args()); + } + + public function brpoplpush($source, $dest, $timeout): mixed + { + return $this->lazyObjectReal->brpoplpush(...\func_get_args()); + } + + public function blpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->blpop(...\func_get_args()); + } + + public function blmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->blmpop(...\func_get_args()); + } + + public function bzmpop($timeout, $keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->bzmpop(...\func_get_args()); + } + + public function lmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->lmpop(...\func_get_args()); + } + + public function zmpop($keys, $from, $count = 1): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->zmpop(...\func_get_args()); + } + + public function brpop($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->brpop(...\func_get_args()); + } + + public function bzpopmax($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->bzpopmax(...\func_get_args()); + } + + public function bzpopmin($key, $timeout_or_key, ...$extra_args): \Relay\Relay|array|false|null + { + return $this->lazyObjectReal->bzpopmin(...\func_get_args()); + } + + public function object($op, $key): mixed + { + return $this->lazyObjectReal->object(...\func_get_args()); + } + + public function geopos($key, ...$members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->geopos(...\func_get_args()); + } + + public function lrem($key, $mem, $count = 0): \Relay\Relay|false|int + { + return $this->lazyObjectReal->lrem(...\func_get_args()); + } + + public function lindex($key, $index): mixed + { + return $this->lazyObjectReal->lindex(...\func_get_args()); + } + + public function linsert($key, $op, $pivot, $element): \Relay\Relay|false|int + { + return $this->lazyObjectReal->linsert(...\func_get_args()); + } + + public function ltrim($key, $start, $end): \Relay\Relay|bool + { + return $this->lazyObjectReal->ltrim(...\func_get_args()); + } + + public function hget($hash, $member): mixed + { + return $this->lazyObjectReal->hget(...\func_get_args()); + } + + public function hstrlen($hash, $member): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hstrlen(...\func_get_args()); + } + + public function hgetall($hash): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hgetall(...\func_get_args()); + } + + public function hkeys($hash): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hkeys(...\func_get_args()); + } + + public function hvals($hash): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hvals(...\func_get_args()); + } + + public function hmget($hash, $members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->hmget(...\func_get_args()); + } + + public function hrandfield($hash, $options = null): \Relay\Relay|array|false|string + { + return $this->lazyObjectReal->hrandfield(...\func_get_args()); + } + + public function hmset($hash, $members): \Relay\Relay|bool + { + return $this->lazyObjectReal->hmset(...\func_get_args()); + } + + public function hexists($hash, $member): \Relay\Relay|bool + { + return $this->lazyObjectReal->hexists(...\func_get_args()); + } + + public function hsetnx($hash, $member, $value): \Relay\Relay|bool + { + return $this->lazyObjectReal->hsetnx(...\func_get_args()); + } + + public function hset($key, $mem, $val, ...$kvals): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hset(...\func_get_args()); + } + + public function hdel($key, $mem, ...$mems): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hdel(...\func_get_args()); + } + + public function hincrby($key, $mem, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hincrby(...\func_get_args()); + } + + public function hincrbyfloat($key, $mem, $value): \Relay\Relay|bool|float + { + return $this->lazyObjectReal->hincrbyfloat(...\func_get_args()); + } + + public function incr($key, $by = 1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->incr(...\func_get_args()); + } + + public function decr($key, $by = 1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->decr(...\func_get_args()); + } + + public function incrby($key, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->incrby(...\func_get_args()); + } + + public function decrby($key, $value): \Relay\Relay|false|int + { + return $this->lazyObjectReal->decrby(...\func_get_args()); + } + + public function incrbyfloat($key, $value): \Relay\Relay|false|float + { + return $this->lazyObjectReal->incrbyfloat(...\func_get_args()); + } + + public function sdiff($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sdiff(...\func_get_args()); + } + + public function sdiffstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sdiffstore(...\func_get_args()); + } + + public function sinter($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sinter(...\func_get_args()); + } + + public function sintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sintercard(...\func_get_args()); + } + + public function sinterstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sinterstore(...\func_get_args()); + } + + public function sunion($key, ...$other_keys): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sunion(...\func_get_args()); + } + + public function sunionstore($key, ...$other_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sunionstore(...\func_get_args()); + } + + public function touch($key_or_array, ...$more_keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->touch(...\func_get_args()); + } + + public function pipeline(): \Relay\Relay|bool + { + return $this->lazyObjectReal->pipeline(...\func_get_args()); + } + + public function multi($mode = 0): \Relay\Relay|bool + { + return $this->lazyObjectReal->multi(...\func_get_args()); + } + + public function exec(): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->exec(...\func_get_args()); + } + + public function wait($replicas, $timeout): \Relay\Relay|false|int + { + return $this->lazyObjectReal->wait(...\func_get_args()); + } + + public function watch($key, ...$other_keys): \Relay\Relay|bool + { + return $this->lazyObjectReal->watch(...\func_get_args()); + } + + public function unwatch(): \Relay\Relay|bool + { + return $this->lazyObjectReal->unwatch(...\func_get_args()); + } + + public function discard(): bool + { + return $this->lazyObjectReal->discard(...\func_get_args()); + } + + public function getMode($masked = false): int + { + return $this->lazyObjectReal->getMode(...\func_get_args()); + } + + public function clearBytes(): void + { + $this->lazyObjectReal->clearBytes(...\func_get_args()); + } + + public function scan(&$iterator, $match = null, $count = 0, $type = null): array|false + { + return $this->lazyObjectReal->scan(...\func_get_args()); + } + + public function hscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->lazyObjectReal->hscan(...\func_get_args()); + } + + public function sscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->lazyObjectReal->sscan(...\func_get_args()); + } + + public function zscan($key, &$iterator, $match = null, $count = 0): array|false + { + return $this->lazyObjectReal->zscan(...\func_get_args()); + } + + public function keys($pattern): \Relay\Relay|array|false + { + return $this->lazyObjectReal->keys(...\func_get_args()); + } + + public function slowlog($operation, ...$extra_args): \Relay\Relay|array|bool|int + { + return $this->lazyObjectReal->slowlog(...\func_get_args()); + } + + public function smembers($set): \Relay\Relay|array|false + { + return $this->lazyObjectReal->smembers(...\func_get_args()); + } + + public function sismember($set, $member): \Relay\Relay|bool + { + return $this->lazyObjectReal->sismember(...\func_get_args()); + } + + public function smismember($set, ...$members): \Relay\Relay|array|false + { + return $this->lazyObjectReal->smismember(...\func_get_args()); + } + + public function srem($set, $member, ...$members): \Relay\Relay|false|int + { + return $this->lazyObjectReal->srem(...\func_get_args()); + } + + public function sadd($set, $member, ...$members): \Relay\Relay|false|int + { + return $this->lazyObjectReal->sadd(...\func_get_args()); + } + + public function sort($key, $options = []): \Relay\Relay|array|false|int + { + return $this->lazyObjectReal->sort(...\func_get_args()); + } + + public function sort_ro($key, $options = []): \Relay\Relay|array|false + { + return $this->lazyObjectReal->sort_ro(...\func_get_args()); + } + + public function smove($srcset, $dstset, $member): \Relay\Relay|bool + { + return $this->lazyObjectReal->smove(...\func_get_args()); + } + + public function spop($set, $count = 1): mixed + { + return $this->lazyObjectReal->spop(...\func_get_args()); + } + + public function srandmember($set, $count = 1): mixed + { + return $this->lazyObjectReal->srandmember(...\func_get_args()); + } + + public function scard($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->scard(...\func_get_args()); + } + + public function script($command, ...$args): mixed + { + return $this->lazyObjectReal->script(...\func_get_args()); + } + + public function strlen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->strlen(...\func_get_args()); + } + + public function hlen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->hlen(...\func_get_args()); + } + + public function llen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->llen(...\func_get_args()); + } + + public function xack($key, $group, $ids): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xack(...\func_get_args()); + } + + public function xadd($key, $id, $values, $maxlen = 0, $approx = false, $nomkstream = false): \Relay\Relay|false|string + { + return $this->lazyObjectReal->xadd(...\func_get_args()); + } + + public function xclaim($key, $group, $consumer, $min_idle, $ids, $options): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->xclaim(...\func_get_args()); + } + + public function xautoclaim($key, $group, $consumer, $min_idle, $start, $count = -1, $justid = false): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->xautoclaim(...\func_get_args()); + } + + public function xlen($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xlen(...\func_get_args()); + } + + public function xgroup($operation, $key = null, $group = null, $id_or_consumer = null, $mkstream = false, $entries_read = -2): mixed + { + return $this->lazyObjectReal->xgroup(...\func_get_args()); + } + + public function xdel($key, $ids): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xdel(...\func_get_args()); + } + + public function xinfo($operation, $arg1 = null, $arg2 = null, $count = -1): mixed + { + return $this->lazyObjectReal->xinfo(...\func_get_args()); + } + + public function xpending($key, $group, $start = null, $end = null, $count = -1, $consumer = null, $idle = 0): \Relay\Relay|array|false + { + return $this->lazyObjectReal->xpending(...\func_get_args()); + } + + public function xrange($key, $start, $end, $count = -1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->xrange(...\func_get_args()); + } + + public function xrevrange($key, $end, $start, $count = -1): \Relay\Relay|array|bool + { + return $this->lazyObjectReal->xrevrange(...\func_get_args()); + } + + public function xread($streams, $count = -1, $block = -1): \Relay\Relay|array|bool|null + { + return $this->lazyObjectReal->xread(...\func_get_args()); + } + + public function xreadgroup($group, $consumer, $streams, $count = 1, $block = 1): \Relay\Relay|array|bool|null + { + return $this->lazyObjectReal->xreadgroup(...\func_get_args()); + } + + public function xtrim($key, $threshold, $approx = false, $minid = false, $limit = -1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->xtrim(...\func_get_args()); + } + + public function zadd($key, ...$args): mixed + { + return $this->lazyObjectReal->zadd(...\func_get_args()); + } + + public function zrandmember($key, $options = null): mixed + { + return $this->lazyObjectReal->zrandmember(...\func_get_args()); + } + + public function zrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrange(...\func_get_args()); + } + + public function zrevrange($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrevrange(...\func_get_args()); + } + + public function zrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrangebyscore(...\func_get_args()); + } + + public function zrevrangebyscore($key, $start, $end, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrevrangebyscore(...\func_get_args()); + } + + public function zrangestore($dst, $src, $start, $end, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrangestore(...\func_get_args()); + } + + public function zrangebylex($key, $min, $max, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrangebylex(...\func_get_args()); + } + + public function zrevrangebylex($key, $max, $min, $offset = -1, $count = -1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zrevrangebylex(...\func_get_args()); + } + + public function zrank($key, $rank): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrank(...\func_get_args()); + } + + public function zrevrank($key, $rank): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrevrank(...\func_get_args()); + } + + public function zrem($key, ...$args): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zrem(...\func_get_args()); + } + + public function zremrangebylex($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zremrangebylex(...\func_get_args()); + } + + public function zremrangebyrank($key, $start, $end): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zremrangebyrank(...\func_get_args()); + } + + public function zremrangebyscore($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zremrangebyscore(...\func_get_args()); + } + + public function zcard($key): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zcard(...\func_get_args()); + } + + public function zcount($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zcount(...\func_get_args()); + } + + public function zdiff($keys, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zdiff(...\func_get_args()); + } + + public function zdiffstore($dst, $keys): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zdiffstore(...\func_get_args()); + } + + public function zincrby($key, $score, $mem): \Relay\Relay|false|float + { + return $this->lazyObjectReal->zincrby(...\func_get_args()); + } + + public function zlexcount($key, $min, $max): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zlexcount(...\func_get_args()); + } + + public function zmscore($key, ...$mems): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zmscore(...\func_get_args()); + } + + public function zscore($key, $member): \Relay\Relay|false|float + { + return $this->lazyObjectReal->zscore(...\func_get_args()); + } + + public function zinter($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zinter(...\func_get_args()); + } + + public function zintercard($keys, $limit = -1): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zintercard(...\func_get_args()); + } + + public function zinterstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zinterstore(...\func_get_args()); + } + + public function zunion($keys, $weights = null, $options = null): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zunion(...\func_get_args()); + } + + public function zunionstore($dst, $keys, $weights = null, $options = null): \Relay\Relay|false|int + { + return $this->lazyObjectReal->zunionstore(...\func_get_args()); + } + + public function zpopmin($key, $count = 1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zpopmin(...\func_get_args()); + } + + public function zpopmax($key, $count = 1): \Relay\Relay|array|false + { + return $this->lazyObjectReal->zpopmax(...\func_get_args()); + } + + public function _getKeys() + { + return $this->lazyObjectReal->_getKeys(...\func_get_args()); + } +} diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 208f466d749b8..8e70070fc8723 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `ParameterBag::getEnum()` * Create migration for session table when pdo handler is used + * Add support for Relay PHP extension for Redis 6.2 --- diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php index c4f1e0216e6cf..b696eee4b7d1f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use Predis\Response\ErrorInterface; +use Relay\Relay; /** * Redis based session storage handler based on the Redis class @@ -39,7 +40,7 @@ class RedisSessionHandler extends AbstractSessionHandler * @throws \InvalidArgumentException When unsupported client or options are passed */ public function __construct( - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, array $options = [], ) { if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php index 33aaa7df5f60a..dbbe7dc880e23 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; use Doctrine\DBAL\DriverManager; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; /** @@ -32,6 +33,7 @@ public static function createHandler(object|string $connection, array $options = switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php index e5937b7df6494..e0c030310709d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php @@ -12,10 +12,12 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; use PHPUnit\Framework\TestCase; +use Relay\Relay; use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler; /** * @requires extension redis + * * @group time-sensitive */ abstract class AbstractRedisSessionHandlerTestCase extends TestCase @@ -32,7 +34,7 @@ abstract class AbstractRedisSessionHandlerTestCase extends TestCase */ protected $redisClient; - abstract protected function createRedisClient(string $host): \Redis|\RedisArray|\RedisCluster|\Predis\Client; + abstract protected function createRedisClient(string $host): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\Client; protected function setUp(): void { diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php new file mode 100644 index 0000000000000..76553f96d3375 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RelaySessionHandlerTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Session\Storage\Handler; + +use Relay\Relay; +use Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler\AbstractRedisSessionHandlerTestCase; + +/** + * @requires extension relay + * + * @group integration + */ +class RelaySessionHandlerTest extends AbstractRedisSessionHandlerTestCase +{ + protected function createRedisClient(string $host): Relay + { + return new Relay(...explode(':', $host)); + } +} diff --git a/src/Symfony/Component/Lock/CHANGELOG.md b/src/Symfony/Component/Lock/CHANGELOG.md index cc5528ffe188a..d5e1103f9e00e 100644 --- a/src/Symfony/Component/Lock/CHANGELOG.md +++ b/src/Symfony/Component/Lock/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Create migration for lock table when DoctrineDbalStore is used + * Add support for Relay PHP extension for Redis 6.0 --- diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index 3b3267a5acb6c..4b29e0f1996ab 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Store; use Predis\Response\ServerException; +use Relay\Relay; use Symfony\Component\Lock\Exception\InvalidTtlException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\LockStorageException; @@ -35,7 +36,7 @@ class RedisStore implements SharedLockStoreInterface * @param float $initialTtl The expiration delay of locks in seconds */ public function __construct( - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, private float $initialTtl = 300.0, ) { if ($initialTtl <= 0) { @@ -226,7 +227,7 @@ public function exists(Key $key): bool private function evaluate(string $script, string $resource, array $args): mixed { - if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { $this->redis->clearLastError(); $result = $this->redis->eval($script, array_merge([$resource], $args), 1); if (null !== $err = $this->redis->getLastError()) { diff --git a/src/Symfony/Component/Lock/Store/StoreFactory.php b/src/Symfony/Component/Lock/Store/StoreFactory.php index f93dcd086c363..7e962ed55da18 100644 --- a/src/Symfony/Component/Lock/Store/StoreFactory.php +++ b/src/Symfony/Component/Lock/Store/StoreFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Store; use Doctrine\DBAL\Connection; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\PersistingStoreInterface; @@ -27,6 +28,7 @@ public static function createStore(#[\SensitiveParameter] object|string $connect { switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: diff --git a/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php index 08e5280ad13a9..19563bd24ea8f 100644 --- a/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/AbstractRedisStoreTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Lock\Tests\Store; +use Relay\Relay; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; @@ -29,7 +30,7 @@ protected function getClockDelay() return 250000; } - abstract protected function getRedisConnection(): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface; + abstract protected function getRedisConnection(): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface; public function getStore(): PersistingStoreInterface { @@ -85,7 +86,7 @@ public function exists(Key $key) private function evaluate(string $script, string $resource, array $args) { - if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { return $this->redis->eval($script, array_merge([$resource], $args), 1); } @@ -97,7 +98,7 @@ private function evaluate(string $script, string $resource, array $args) return $this->redis->eval(...array_merge([$script, 1, $resource], $args)); } - throw new InvalidArgumentException(sprintf('"%s()" expects being initialized with a Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($this->redis))); + throw new InvalidArgumentException(sprintf('"%s()" expects being initialized with a Redis, Relay, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($this->redis))); } private function getUniqueToken(Key $key): string diff --git a/src/Symfony/Component/Lock/Tests/Store/RelayStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/RelayStoreTest.php new file mode 100644 index 0000000000000..1c6657a5d7159 --- /dev/null +++ b/src/Symfony/Component/Lock/Tests/Store/RelayStoreTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Store; + +use PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; +use Symfony\Component\Lock\Tests\Store\AbstractRedisStoreTest; +use Symfony\Component\Lock\Tests\Store\SharedLockStoreTestTrait; + +/** + * @requires extension relay + * + * @group integration + */ +class RelayStoreTest extends AbstractRedisStoreTest +{ + use SharedLockStoreTestTrait; + + public static function setUpBeforeClass(): void + { + try { + new Relay(...explode(':', getenv('REDIS_HOST'))); + } catch (\Relay\Exception $e) { + throw new SkippedTestSuiteError($e->getMessage()); + } + } + + protected function getRedisConnection(): Relay + { + return new Relay(...explode(':', getenv('REDIS_HOST'))); + } +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md index 410e6eeaee302..640ed7f9a8515 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for Relay PHP extension for Redis + 6.1 --- diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php index 5ac0dbb5fc668..4117b14785b3c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RedisExtIntegrationTest.php @@ -12,12 +12,14 @@ namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport; use PHPUnit\Framework\TestCase; +use Relay\Relay; use Symfony\Component\Messenger\Bridge\Redis\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Bridge\Redis\Transport\Connection; use Symfony\Component\Messenger\Exception\TransportException; /** * @requires extension redis + * * @group time-sensitive * @group integration */ @@ -258,7 +260,7 @@ public function testLazyCluster() public function testLazy() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/messenger-lazy?lazy=1', [], $redis); $connection->add('1', []); @@ -275,7 +277,7 @@ public function testLazy() public function testDbIndex() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); Connection::fromDsn('redis://localhost/queue?dbindex=2', [], $redis); @@ -296,7 +298,7 @@ public function testFromDsnWithMultipleHosts() public function testJsonError() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/json-error', [], $redis); try { $connection->add("\xB1\x31", []); @@ -308,7 +310,7 @@ public function testJsonError() public function testGetNonBlocking() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/messenger-getnonblocking', ['sentinel_master' => null], $redis); @@ -321,7 +323,7 @@ public function testGetNonBlocking() public function testGetAfterReject() { - $redis = new \Redis(); + $redis = $this->createRedisClient(); $connection = Connection::fromDsn('redis://localhost/messenger-rejectthenget', ['sentinel_master' => null], $redis); $connection->add('1', []); @@ -380,4 +382,9 @@ private function skipIfRedisClusterUnavailable() self::markTestSkipped($e->getMessage()); } } + + protected function createRedisClient(): \Redis|Relay + { + return new \Redis(); + } } diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RelayExtIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RelayExtIntegrationTest.php new file mode 100644 index 0000000000000..bd33434a064c8 --- /dev/null +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/RelayExtIntegrationTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Bridge\Redis\Tests\Transport; + +use Relay\Relay; + +/** + * @requires extension relay + * + * @group time-sensitive + * @group integration + */ +class RelayExtIntegrationTest extends RedisExtIntegrationTest +{ + protected function createRedisClient(): \Redis|Relay + { + return new Relay(); + } +} diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index 481dc5d64bba8..837480de209f4 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Messenger\Bridge\Redis\Transport; +use Relay\Relay; +use Relay\Sentinel; use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\Exception\LogicException; use Symfony\Component\Messenger\Exception\TransportException; @@ -43,7 +45,7 @@ class Connection 'claim_interval' => 60000, // Interval by which pending/abandoned messages should be checked 'lazy' => false, 'auth' => null, - 'serializer' => \Redis::SERIALIZER_PHP, + 'serializer' => 1, // see \Redis::SERIALIZER_PHP, 'sentinel_master' => null, // String, master to look for (optional, default is NULL meaning Sentinel support is disabled) 'timeout' => 0.0, // Float, value in seconds (optional, default is 0 meaning unlimited) 'read_timeout' => 0.0, // Float, value in seconds (optional, default is 0 meaning unlimited) @@ -52,7 +54,7 @@ class Connection 'ssl' => null, // see https://php.net/context.ssl ]; - private \Redis|\RedisCluster|\Closure $redis; + private \Redis|Relay|\RedisCluster|\Closure $redis; private string $stream; private string $queue; private string $group; @@ -66,7 +68,7 @@ class Connection private bool $deleteAfterReject; private bool $couldHavePendingMessages = true; - public function __construct(array $options, \Redis|\RedisCluster $redis = null) + public function __construct(array $options, \Redis|Relay|\RedisCluster $redis = null) { if (version_compare(phpversion('redis'), '4.3.0', '<')) { throw new LogicException('The redis transport requires php-redis 4.3.0 or higher.'); @@ -78,8 +80,8 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) $auth = $options['auth']; $sentinelMaster = $options['sentinel_master']; - if (null !== $sentinelMaster && !class_exists(\RedisSentinel::class)) { - throw new InvalidArgumentException('Redis Sentinel support requires the "redis" extension v5.2 or higher.'); + if (null !== $sentinelMaster && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) { + throw new InvalidArgumentException('Redis Sentinel support requires ext-redis>=5.2, or ext-relay.'); } if (null !== $sentinelMaster && ($redis instanceof \RedisCluster || \is_array($host))) { @@ -91,7 +93,8 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) $this->redis = static fn () => self::initializeRedisCluster($redis, $hosts, $auth, $options); } else { if (null !== $sentinelMaster) { - $sentinelClient = new \RedisSentinel($host, $port, $options['timeout'], $options['persistent_id'], $options['retry_interval'], $options['read_timeout']); + $sentinelClass = \extension_loaded('redis') ? \RedisSentinel::class : Sentinel::class; + $sentinelClient = new $sentinelClass($host, $port, $options['timeout'], $options['persistent_id'], $options['retry_interval'], $options['read_timeout']); if (!$address = $sentinelClient->getMasterAddrByName($sentinelMaster)) { throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $sentinelMaster, $host, $port)); @@ -100,7 +103,7 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) [$host, $port] = $address; } - $this->redis = static fn () => self::initializeRedis($redis ?? new \Redis(), $host, $port, $auth, $options); + $this->redis = static fn () => self::initializeRedis($redis ?? (\extension_loaded('redis') ? new \Redis() : new Relay()), $host, $port, $auth, $options); } if (!$options['lazy']) { @@ -128,12 +131,12 @@ public function __construct(array $options, \Redis|\RedisCluster $redis = null) /** * @param string|string[]|null $auth */ - private static function initializeRedis(\Redis $redis, string $host, int $port, string|array|null $auth, array $params): \Redis + private static function initializeRedis(\Redis|Relay $redis, string $host, int $port, string|array|null $auth, array $params): \Redis|Relay { $connect = isset($params['persistent_id']) ? 'pconnect' : 'connect'; - $redis->{$connect}($host, $port, $params['timeout'], $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []); + $redis->{$connect}($host, $port, $params['timeout'], $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...(\defined('Redis::SCAN_PREFIX') || \extension_loaded('relay')) ? [['stream' => $params['ssl'] ?? null]] : []); - $redis->setOption(\Redis::OPT_SERIALIZER, $params['serializer']); + $redis->setOption($redis instanceof \Redis ? \Redis::OPT_SERIALIZER : Relay::OPT_SERIALIZER, $params['serializer']); if (null !== $auth && !$redis->auth($auth)) { throw new InvalidArgumentException('Redis connection failed: '.$redis->getLastError()); @@ -157,7 +160,7 @@ private static function initializeRedisCluster(?\RedisCluster $redis, array $hos return $redis; } - public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], \Redis|\RedisCluster $redis = null): self + public static function fromDsn(#[\SensitiveParameter] string $dsn, array $options = [], \Redis|Relay|\RedisCluster $redis = null): self { if (!str_contains($dsn, ',')) { $parsedUrl = self::parseDsn($dsn, $options); @@ -265,7 +268,7 @@ private function claimOldPendingMessages() // This could soon be optimized with https://github.com/antirez/redis/issues/5212 or // https://github.com/antirez/redis/issues/6256 $pendingMessages = $this->getRedis()->xpending($this->stream, $this->group, '-', '+', 1); - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -294,7 +297,7 @@ private function claimOldPendingMessages() ); $this->couldHavePendingMessages = true; - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } } @@ -352,7 +355,7 @@ public function get(): ?array [$this->stream => $messageId], 1 ); - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -390,7 +393,7 @@ public function ack(string $id): void if ($this->deleteAfterAck) { $acknowledged = $redis->xdel($this->stream, [$id]); } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -411,7 +414,7 @@ public function reject(string $id): void if ($this->deleteAfterReject) { $deleted = $redis->xdel($this->stream, [$id]) && $deleted; } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -474,7 +477,7 @@ public function add(string $body, array $headers, int $delayInMs = 0): string $id = $added; } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { if ($error = $redis->getLastError() ?: null) { $redis->clearLastError(); } @@ -497,7 +500,7 @@ public function setup(): void try { $redis->xgroup('CREATE', $this->stream, $this->group, 0, true); - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -600,7 +603,7 @@ private function rawCommand(string $command, ...$arguments): mixed } else { $result = $redis->rawCommand($command, $this->queue, ...$arguments); } - } catch (\RedisException $e) { + } catch (\RedisException|\Relay\Exception $e) { throw new TransportException($e->getMessage(), 0, $e); } @@ -614,7 +617,7 @@ private function rawCommand(string $command, ...$arguments): mixed return $result; } - private function getRedis(): \Redis|\RedisCluster + private function getRedis(): \Redis|Relay|\RedisCluster { if ($this->redis instanceof \Closure) { $this->redis = ($this->redis)(); diff --git a/src/Symfony/Component/Semaphore/CHANGELOG.md b/src/Symfony/Component/Semaphore/CHANGELOG.md index f53fc9dc2a2cf..c92b74aba72ae 100644 --- a/src/Symfony/Component/Semaphore/CHANGELOG.md +++ b/src/Symfony/Component/Semaphore/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add support for Relay PHP extension for Redis + 5.3 --- diff --git a/src/Symfony/Component/Semaphore/Store/RedisStore.php b/src/Symfony/Component/Semaphore/Store/RedisStore.php index 0b3652c5d9c11..f1c6fcc30edd9 100644 --- a/src/Symfony/Component/Semaphore/Store/RedisStore.php +++ b/src/Symfony/Component/Semaphore/Store/RedisStore.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Semaphore\Store; +use Relay\Relay; use Symfony\Component\Semaphore\Exception\InvalidArgumentException; use Symfony\Component\Semaphore\Exception\SemaphoreAcquiringException; use Symfony\Component\Semaphore\Exception\SemaphoreExpiredException; @@ -26,7 +27,7 @@ class RedisStore implements PersistingStoreInterface { public function __construct( - private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, + private \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface $redis, ) { } @@ -157,7 +158,7 @@ public function exists(Key $key): bool private function evaluate(string $script, string $resource, array $args): mixed { - if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster) { + if ($this->redis instanceof \Redis || $this->redis instanceof Relay || $this->redis instanceof \RedisCluster) { return $this->redis->eval($script, array_merge([$resource], $args), 1); } diff --git a/src/Symfony/Component/Semaphore/Store/StoreFactory.php b/src/Symfony/Component/Semaphore/Store/StoreFactory.php index eb77431c39030..639298bab2453 100644 --- a/src/Symfony/Component/Semaphore/Store/StoreFactory.php +++ b/src/Symfony/Component/Semaphore/Store/StoreFactory.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Semaphore\Store; +use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Semaphore\Exception\InvalidArgumentException; use Symfony\Component\Semaphore\PersistingStoreInterface; @@ -26,6 +27,7 @@ public static function createStore(#[\SensitiveParameter] object|string $connect { switch (true) { case $connection instanceof \Redis: + case $connection instanceof Relay: case $connection instanceof \RedisArray: case $connection instanceof \RedisCluster: case $connection instanceof \Predis\ClientInterface: @@ -33,7 +35,6 @@ public static function createStore(#[\SensitiveParameter] object|string $connect case !\is_string($connection): throw new InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection::class)); - case str_starts_with($connection, 'redis://'): case str_starts_with($connection, 'rediss://'): if (!class_exists(AbstractAdapter::class)) { diff --git a/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php b/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php index 4952f880f6a32..a7de9357a2373 100644 --- a/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php +++ b/src/Symfony/Component/Semaphore/Tests/Store/AbstractRedisStoreTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Semaphore\Tests\Store; +use Relay\Relay; use Symfony\Component\Semaphore\PersistingStoreInterface; use Symfony\Component\Semaphore\Store\RedisStore; @@ -19,7 +20,7 @@ */ abstract class AbstractRedisStoreTest extends AbstractStoreTest { - abstract protected function getRedisConnection(): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface; + abstract protected function getRedisConnection(): \Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInterface; public function getStore(): PersistingStoreInterface { diff --git a/src/Symfony/Component/Semaphore/Tests/Store/RelayStoreTest.php b/src/Symfony/Component/Semaphore/Tests/Store/RelayStoreTest.php new file mode 100644 index 0000000000000..694e0b4b7b983 --- /dev/null +++ b/src/Symfony/Component/Semaphore/Tests/Store/RelayStoreTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Semaphore\Tests\Store; + +use PHPUnit\Framework\SkippedTestSuiteError; +use Relay\Relay; + +/** + * @requires extension relay + */ +class RelayStoreTest extends AbstractRedisStoreTest +{ + protected function setUp(): void + { + $this->getRedisConnection()->flushDB(); + } + + public static function setUpBeforeClass(): void + { + try { + new Relay(...explode(':', getenv('REDIS_HOST'))); + } catch (\Relay\Exception $e) { + throw new SkippedTestSuiteError($e->getMessage()); + } + } + + protected function getRedisConnection(): Relay + { + return new Relay(...explode(':', getenv('REDIS_HOST'))); + } +} diff --git a/src/Symfony/Component/VarDumper/CHANGELOG.md b/src/Symfony/Component/VarDumper/CHANGELOG.md index c5abeee346a06..3b29f419dc32d 100644 --- a/src/Symfony/Component/VarDumper/CHANGELOG.md +++ b/src/Symfony/Component/VarDumper/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add caster for `WeakMap` * Add support of named arguments to `dd()` and `dump()` to display the argument name + * Add support for `Relay\Relay` 6.2 --- diff --git a/src/Symfony/Component/VarDumper/Caster/RedisCaster.php b/src/Symfony/Component/VarDumper/Caster/RedisCaster.php index eac25a12a8b56..f88c72a40cf86 100644 --- a/src/Symfony/Component/VarDumper/Caster/RedisCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/RedisCaster.php @@ -11,6 +11,7 @@ namespace Symfony\Component\VarDumper\Caster; +use Relay\Relay; use Symfony\Component\VarDumper\Cloner\Stub; /** @@ -23,15 +24,15 @@ class RedisCaster { private const SERIALIZERS = [ - \Redis::SERIALIZER_NONE => 'NONE', - \Redis::SERIALIZER_PHP => 'PHP', + 0 => 'NONE', // Redis::SERIALIZER_NONE + 1 => 'PHP', // Redis::SERIALIZER_PHP 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY ]; private const MODES = [ - \Redis::ATOMIC => 'ATOMIC', - \Redis::MULTI => 'MULTI', - \Redis::PIPELINE => 'PIPELINE', + 0 => 'ATOMIC', // Redis::ATOMIC + 1 => 'MULTI', // Redis::MULTI + 2 => 'PIPELINE', // Redis::PIPELINE ]; private const COMPRESSION_MODES = [ @@ -46,7 +47,7 @@ class RedisCaster \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', ]; - public static function castRedis(\Redis $c, array $a, Stub $stub, bool $isNested) + public static function castRedis(\Redis|Relay $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -102,9 +103,9 @@ public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, return $a; } - private static function getRedisOptions(\Redis|\RedisArray|\RedisCluster $redis, array $options = []): EnumStub + private static function getRedisOptions(\Redis|Relay|\RedisArray|\RedisCluster $redis, array $options = []): EnumStub { - $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); + $serializer = $redis->getOption(\defined('Redis::OPT_SERIALIZER') ? \Redis::OPT_SERIALIZER : 1); if (\is_array($serializer)) { foreach ($serializer as &$v) { if (isset(self::SERIALIZERS[$v])) { @@ -136,11 +137,11 @@ private static function getRedisOptions(\Redis|\RedisArray|\RedisCluster $redis, } $options += [ - 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, - 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : Relay::OPT_TCP_KEEPALIVE, + 'READ_TIMEOUT' => $redis->getOption(\defined('Redis::OPT_READ_TIMEOUT') ? \Redis::OPT_READ_TIMEOUT : Relay::OPT_READ_TIMEOUT), 'COMPRESSION' => $compression, 'SERIALIZER' => $serializer, - 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), + 'PREFIX' => $redis->getOption(\defined('Redis::OPT_PREFIX') ? \Redis::OPT_PREFIX : Relay::OPT_PREFIX), 'SCAN' => $retry, ]; diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index bcd3013716e9e..599c59ecf6e54 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -130,6 +130,7 @@ abstract class AbstractCloner implements ClonerInterface 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'Relay\Relay' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php index 058b95d0d0ab6..3d56584a5640e 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/RedisCasterTest.php @@ -16,13 +16,15 @@ /** * @author Nicolas Grekas - * @requires extension redis * @group integration */ class RedisCasterTest extends TestCase { use VarDumperTestTrait; + /** + * @requires extension redis + */ public function testNotConnected() { $redis = new \Redis(); @@ -36,10 +38,18 @@ public function testNotConnected() $this->assertDumpMatchesFormat($xCast, $redis); } - public function testConnected() + /** + * @testWith ["Redis"] + * ["Relay\\Relay"] + */ + public function testConnected(string $class) { + if (!class_exists($class)) { + self::markTestSkipped(sprintf('"%s" class required', $class)); + } + $redisHost = explode(':', getenv('REDIS_HOST')) + [1 => 6379]; - $redis = new \Redis(); + $redis = new $class; try { $redis->connect(...$redisHost); } catch (\Exception $e) { @@ -47,7 +57,7 @@ public function testConnected() } $xCast = <<