From 67bf6a423b3cb60dc87587e15bcfb68a6f889105 Mon Sep 17 00:00:00 2001 From: Egor Korobov Date: Fri, 5 Jun 2026 10:20:03 +0300 Subject: [PATCH 1/3] feat(phpredis): add compression and compression_level options Adds support for Redis::OPT_COMPRESSION (7) and Redis::OPT_COMPRESSION_LEVEL (9) for phpredis, cluster, and array clients. Supported compression types: none, lzf, zstd, lz4. --- .../Configuration/Configuration.php | 2 + src/Factory/PhpredisClientFactory.php | 41 +++++++++++++++ .../SncRedisExtensionEnvTest.php | 14 +++++ .../SncRedisExtensionTest.php | 28 ++++++++++ tests/Factory/PhpredisClientFactoryTest.php | 51 +++++++++++++++++++ 5 files changed, 136 insertions(+) diff --git a/src/DependencyInjection/Configuration/Configuration.php b/src/DependencyInjection/Configuration/Configuration.php index 1ff7a08b..96df4cac 100644 --- a/src/DependencyInjection/Configuration/Configuration.php +++ b/src/DependencyInjection/Configuration/Configuration.php @@ -155,6 +155,8 @@ private function addClientsSection(ArrayNodeDefinition $rootNode): void ->booleanNode('iterable_multibulk')->defaultFalse()->end() ->booleanNode('throw_errors')->defaultTrue()->end() ->scalarNode('serialization')->defaultValue('default')->end() + ->scalarNode('compression')->defaultNull()->end() + ->integerNode('compression_level')->defaultNull()->end() ->scalarNode('cluster')->defaultNull()->end() ->booleanNode('array')->defaultFalse()->end() ->scalarNode('prefix')->defaultNull()->end() diff --git a/src/Factory/PhpredisClientFactory.php b/src/Factory/PhpredisClientFactory.php index 92871898..97612a5f 100644 --- a/src/Factory/PhpredisClientFactory.php +++ b/src/Factory/PhpredisClientFactory.php @@ -235,6 +235,14 @@ private function createArrayClient(array $dsns, string $class, string $alias, ar $client->setOption(Redis::OPT_SERIALIZER, $this->loadSerializationType($options['serialization'])); } + if (isset($options['compression'])) { + $client->setOption(Redis::OPT_COMPRESSION, $this->loadCompressionType($options['compression'])); + } + + if (isset($options['compression_level'])) { + $client->setOption(Redis::OPT_COMPRESSION_LEVEL, $options['compression_level']); + } + return $loggingEnabled ? $this->createLoggingProxy($client, $alias) : $client; } @@ -286,6 +294,14 @@ private function createClusterClient(array $dsns, string $class, string $alias, $client->setOption(Redis::OPT_SERIALIZER, $this->loadSerializationType($options['serialization'])); } + if (isset($options['compression'])) { + $client->setOption(Redis::OPT_COMPRESSION, $this->loadCompressionType($options['compression'])); + } + + if (isset($options['compression_level'])) { + $client->setOption(Redis::OPT_COMPRESSION_LEVEL, $options['compression_level']); + } + if (isset($options['slave_failover'])) { $client->setOption(RedisCluster::OPT_SLAVE_FAILOVER, $this->loadSlaveFailoverType($options['slave_failover'])); } @@ -365,6 +381,14 @@ private function createClient(RedisDsn $dsn, string $class, string $alias, array $client->setOption($class::OPT_SERIALIZER, $this->loadSerializationType($options['serialization'])); } + if (isset($options['compression'])) { + $client->setOption($class::OPT_COMPRESSION, $this->loadCompressionType($options['compression'])); + } + + if (isset($options['compression_level'])) { + $client->setOption($class::OPT_COMPRESSION_LEVEL, $options['compression_level']); + } + return $client; } @@ -387,6 +411,23 @@ private function loadSerializationType(string $type): int throw new InvalidConfigurationException(sprintf('%s in not a valid serializer. Valid serializers: %s', $type, implode(', ', array_keys($types)))); } + /** @throws InvalidConfigurationException */ + private function loadCompressionType(string $type): int + { + $types = [ + 'none' => 0, // Redis::COMPRESSION_NONE + 'lzf' => 1, // Redis::COMPRESSION_LZF + 'zstd' => 2, // Redis::COMPRESSION_ZSTD + 'lz4' => 3, // Redis::COMPRESSION_LZ4 + ]; + + if (array_key_exists($type, $types)) { + return $types[$type]; + } + + throw new InvalidConfigurationException(sprintf('%s is not a valid compression type. Valid types: %s', $type, implode(', ', array_keys($types)))); + } + private function loadSlaveFailoverType(string $type): int { $types = [ diff --git a/tests/DependencyInjection/SncRedisExtensionEnvTest.php b/tests/DependencyInjection/SncRedisExtensionEnvTest.php index 8fcb3148..44590d83 100644 --- a/tests/DependencyInjection/SncRedisExtensionEnvTest.php +++ b/tests/DependencyInjection/SncRedisExtensionEnvTest.php @@ -63,6 +63,8 @@ public function testPredisDefaultParameterConfig(): void 'read_write_timeout' => null, 'iterable_multibulk' => false, 'serialization' => 'default', + 'compression' => null, + 'compression_level' => null, 'prefix' => null, 'service' => null, 'async_connect' => false, @@ -104,6 +106,8 @@ public function testPhpredisDefaultParameterConfig(string $config, string $class 'iterable_multibulk' => false, 'throw_errors' => true, 'serialization' => 'default', + 'compression' => null, + 'compression_level' => null, 'cluster' => null, 'prefix' => null, 'service' => null, @@ -146,6 +150,8 @@ public function testPhpredisFullConfig(): void 'read_write_timeout' => null, 'iterable_multibulk' => false, 'throw_errors' => true, + 'compression' => null, + 'compression_level' => null, 'cluster' => null, 'service' => null, ], @@ -185,6 +191,8 @@ public function testPhpredisWithAclConfig(): void 'scan' => Redis::SCAN_NORETRY, 'read_write_timeout' => null, 'serialization' => 'php', + 'compression' => null, + 'compression_level' => null, 'service' => null, 'throw_errors' => true, ], @@ -230,6 +238,8 @@ public function testPhpRedisClusterOption(): void 'iterable_multibulk' => false, 'throw_errors' => true, 'serialization' => 'default', + 'compression' => null, + 'compression_level' => null, 'prefix' => null, 'service' => null, ], @@ -260,6 +270,8 @@ public function testPhpRedisSentinelOption(): void 'iterable_multibulk' => false, 'throw_errors' => true, 'serialization' => 'default', + 'compression' => null, + 'compression_level' => null, 'cluster' => null, 'prefix' => null, ], @@ -290,6 +302,8 @@ public function testPhpRedisClusterOptionMultipleDsn(): void 'iterable_multibulk' => false, 'throw_errors' => true, 'serialization' => 'default', + 'compression' => null, + 'compression_level' => null, 'prefix' => null, 'service' => null, ], diff --git a/tests/DependencyInjection/SncRedisExtensionTest.php b/tests/DependencyInjection/SncRedisExtensionTest.php index 6bcfb253..7756bf27 100644 --- a/tests/DependencyInjection/SncRedisExtensionTest.php +++ b/tests/DependencyInjection/SncRedisExtensionTest.php @@ -279,6 +279,20 @@ public function testClientSerializationOption(): void $this->assertSame($options['serialization'], $masterParameters['serialization']); } + /** + * Test valid config of the compression option + */ + public function testClientCompressionOption(): void + { + $extension = new SncRedisExtension(); + $config = $this->parseYaml($this->getCompressionYamlConfig()); + $extension->load([$config], $container = $this->getContainer()); + + $defaultParameters = $container->getDefinition('snc_redis.default'); + $this->assertSame('lzf', $defaultParameters->getArgument(2)['compression']); + $this->assertSame(5, $defaultParameters->getArgument(2)['compression_level']); + } + /** * Test valid config of the single host sentinel replication option */ @@ -445,6 +459,20 @@ private function parseYaml(string $yaml): array return $parser->parse($yaml); } + private function getCompressionYamlConfig(): string + { + return <<<'EOF' +clients: + default: + type: phpredis + alias: default + dsn: redis://localhost + options: + compression: "lzf" + compression_level: 5 +EOF; + } + private function getSerializationYamlConfig(): string { return <<<'EOF' diff --git a/tests/Factory/PhpredisClientFactoryTest.php b/tests/Factory/PhpredisClientFactoryTest.php index b0e74137..29774de9 100644 --- a/tests/Factory/PhpredisClientFactoryTest.php +++ b/tests/Factory/PhpredisClientFactoryTest.php @@ -413,6 +413,57 @@ public static function serializationTypes(): array ]; } + /** @dataProvider compressionTypes */ + public function testLoadCompressionType(string $compressionType, int $compression): void + { + $factory = new PhpredisClientFactory(new RedisCallInterceptor($this->redisLogger)); + + $client = $factory->create( + Redis::class, + ['redis://localhost:6379'], + [ + 'compression' => $compressionType, + 'connection_timeout' => 5, + ], + 'default', + false, + ); + + self::assertSame($compression, $client->getOption(Redis::OPT_COMPRESSION)); + } + + public function testLoadCompressionTypeFail(): void + { + $factory = new PhpredisClientFactory(new RedisCallInterceptor($this->redisLogger)); + $this->expectException(InvalidConfigurationException::class); + + $factory->create( + Redis::class, + ['redis://localhost:6379'], + [ + 'compression' => 'unknown', + 'connection_timeout' => 5, + ], + 'default', + false, + ); + } + + /** @return list */ + public static function compressionTypes(): array + { + $r = new \ReflectionClass(Redis::class); + $types = [['none', Redis::COMPRESSION_NONE]]; + + foreach (['lzf' => 'COMPRESSION_LZF', 'zstd' => 'COMPRESSION_ZSTD', 'lz4' => 'COMPRESSION_LZ4'] as $name => $const) { + if ($r->hasConstant($const)) { + $types[] = [$name, $r->getConstant($const)]; + } + } + + return $types; + } + public function testMethodsWithVariadicParameters(): void { $this->logger->method('debug')->with(...$this->withConsecutive( From ed783302abd403ec2ff207347f3d224a0100e6dc Mon Sep 17 00:00:00 2001 From: Egor Korobov Date: Fri, 5 Jun 2026 11:10:02 +0300 Subject: [PATCH 2/3] fix: coding standards and psalm issues in compression implementation --- src/Factory/PhpredisClientFactory.php | 2 ++ tests/Factory/PhpredisClientFactoryTest.php | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Factory/PhpredisClientFactory.php b/src/Factory/PhpredisClientFactory.php index 97612a5f..7d1a9464 100644 --- a/src/Factory/PhpredisClientFactory.php +++ b/src/Factory/PhpredisClientFactory.php @@ -236,10 +236,12 @@ private function createArrayClient(array $dsns, string $class, string $alias, ar } if (isset($options['compression'])) { + /** @psalm-suppress InvalidArgument */ $client->setOption(Redis::OPT_COMPRESSION, $this->loadCompressionType($options['compression'])); } if (isset($options['compression_level'])) { + /** @psalm-suppress InvalidArgument */ $client->setOption(Redis::OPT_COMPRESSION_LEVEL, $options['compression_level']); } diff --git a/tests/Factory/PhpredisClientFactoryTest.php b/tests/Factory/PhpredisClientFactoryTest.php index 29774de9..74af4adb 100644 --- a/tests/Factory/PhpredisClientFactoryTest.php +++ b/tests/Factory/PhpredisClientFactoryTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Redis; +use ReflectionClass; use RedisArray; use RedisCluster; use Relay\Relay; @@ -452,13 +453,15 @@ public function testLoadCompressionTypeFail(): void /** @return list */ public static function compressionTypes(): array { - $r = new \ReflectionClass(Redis::class); + $r = new ReflectionClass(Redis::class); $types = [['none', Redis::COMPRESSION_NONE]]; foreach (['lzf' => 'COMPRESSION_LZF', 'zstd' => 'COMPRESSION_ZSTD', 'lz4' => 'COMPRESSION_LZ4'] as $name => $const) { - if ($r->hasConstant($const)) { - $types[] = [$name, $r->getConstant($const)]; + if (!$r->hasConstant($const)) { + continue; } + + $types[] = [$name, $r->getConstant($const)]; } return $types; From b6dfda814575f6c7ac567c00eef841fc181ac0d5 Mon Sep 17 00:00:00 2001 From: Egor Korobov Date: Fri, 5 Jun 2026 11:12:16 +0300 Subject: [PATCH 3/3] refactor: use constant() to resolve compression type instead of static map --- src/Factory/PhpredisClientFactory.php | 16 +++++++--------- tests/Factory/PhpredisClientFactoryTest.php | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Factory/PhpredisClientFactory.php b/src/Factory/PhpredisClientFactory.php index 7d1a9464..bed577a4 100644 --- a/src/Factory/PhpredisClientFactory.php +++ b/src/Factory/PhpredisClientFactory.php @@ -27,7 +27,9 @@ use function array_map; use function array_values; use function class_exists; +use function constant; use function count; +use function defined; use function get_class; use function implode; use function in_array; @@ -37,6 +39,7 @@ use function phpversion; use function spl_autoload_register; use function sprintf; +use function strtoupper; use function var_export; use function version_compare; @@ -416,18 +419,13 @@ private function loadSerializationType(string $type): int /** @throws InvalidConfigurationException */ private function loadCompressionType(string $type): int { - $types = [ - 'none' => 0, // Redis::COMPRESSION_NONE - 'lzf' => 1, // Redis::COMPRESSION_LZF - 'zstd' => 2, // Redis::COMPRESSION_ZSTD - 'lz4' => 3, // Redis::COMPRESSION_LZ4 - ]; + $const = 'Redis::COMPRESSION_' . strtoupper($type); - if (array_key_exists($type, $types)) { - return $types[$type]; + if (!defined($const)) { + throw new InvalidConfigurationException(sprintf('"%s" is not a valid compression type.', $type)); } - throw new InvalidConfigurationException(sprintf('%s is not a valid compression type. Valid types: %s', $type, implode(', ', array_keys($types)))); + return (int) constant($const); } private function loadSlaveFailoverType(string $type): int diff --git a/tests/Factory/PhpredisClientFactoryTest.php b/tests/Factory/PhpredisClientFactoryTest.php index 74af4adb..0da4814a 100644 --- a/tests/Factory/PhpredisClientFactoryTest.php +++ b/tests/Factory/PhpredisClientFactoryTest.php @@ -10,9 +10,9 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Redis; -use ReflectionClass; use RedisArray; use RedisCluster; +use ReflectionClass; use Relay\Relay; use SEEC\PhpUnit\Helper\ConsecutiveParams; use Snc\RedisBundle\Factory\PhpredisClientFactory;