diff --git a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php index 38782f3d4c..d52c08d642 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php @@ -15,6 +15,7 @@ use Predis\Connection\Aggregate\ClusterInterface; use Predis\Connection\Aggregate\PredisCluster; use Predis\Response\Status; use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Marshaller\DeflateMarshaller; use Symfony\Component\Cache\Marshaller\MarshallerInterface; use Symfony\Component\Cache\Marshaller\TagAwareMarshaller; use Symfony\Component\Cache\Traits\RedisTrait; @@ -68,6 +69,16 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_class($redisClient->getConnection()))); } + if (\defined('Redis::OPT_COMPRESSION') && ($redisClient instanceof \Redis || $redisClient instanceof \RedisArray || $redisClient instanceof \RedisCluster)) { + $compression = $redisClient->getOption(\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.', \get_class($this), DeflateMarshaller::class)); + } + } + } + $this->init($redisClient, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller)); } diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index ffe2bbeaa0..ed8e9d1cb0 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -8,6 +8,8 @@ CHANGELOG * added argument `$prefix` to `AdapterInterface::clear()` * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter` + * added `DeflateMarshaller` to compress serialized values + * removed support for phpredis 4 `compression` * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead 4.3.0 diff --git a/src/Symfony/Component/Cache/Marshaller/DeflateMarshaller.php b/src/Symfony/Component/Cache/Marshaller/DeflateMarshaller.php new file mode 100644 index 0000000000..5544806116 --- /dev/null +++ b/src/Symfony/Component/Cache/Marshaller/DeflateMarshaller.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Marshaller; + +use Symfony\Component\Cache\Exception\CacheException; + +/** + * Compresses values using gzdeflate(). + * + * @author Nicolas Grekas + */ +class DeflateMarshaller implements MarshallerInterface +{ + private $marshaller; + + public function __construct(MarshallerInterface $marshaller) + { + if (!\function_exists('gzdeflate')) { + throw new CacheException('The "zlib" PHP extension is not loaded.'); + } + + $this->marshaller = $marshaller; + } + + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + return array_map('gzdeflate', $this->marshaller->marshall($values, $failed)); + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value) + { + if (false !== $inflatedValue = @gzinflate($value)) { + $value = $inflatedValue; + } + + return $this->marshaller->unmarshall($value); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Marshaller/DeflateMarshallerTest.php b/src/Symfony/Component/Cache/Tests/Marshaller/DeflateMarshallerTest.php new file mode 100644 index 0000000000..5caf5ebb24 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Marshaller/DeflateMarshallerTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Marshaller; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\DeflateMarshaller; + +/** + * @requires extension zlib + */ +class DeflateMarshallerTest extends TestCase +{ + public function testMarshall() + { + $defaultMarshaller = new DefaultMarshaller(); + $deflateMarshaller = new DeflateMarshaller($defaultMarshaller); + + $values = ['abc' => [str_repeat('def', 100)]]; + + $failed = []; + $defaultResult = $defaultMarshaller->marshall($values, $failed); + + $deflateResult = $deflateMarshaller->marshall($values, $failed); + $deflateResult['abc'] = gzinflate($deflateResult['abc']); + + $this->assertSame($defaultResult, $deflateResult); + } + + public function testUnmarshall() + { + $defaultMarshaller = new DefaultMarshaller(); + $deflateMarshaller = new DeflateMarshaller($defaultMarshaller); + + $values = ['abc' => [str_repeat('def', 100)]]; + + $defaultResult = $defaultMarshaller->marshall($values, $failed); + $deflateResult = $deflateMarshaller->marshall($values, $failed); + + $this->assertSame($values['abc'], $deflateMarshaller->unmarshall($deflateResult['abc'])); + $this->assertSame($values['abc'], $deflateMarshaller->unmarshall($defaultResult['abc'])); + } +} diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 1399f6b75e..5eab66890b 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -34,7 +34,6 @@ trait RedisTrait 'timeout' => 30, 'read_timeout' => 0, 'retry_interval' => 0, - 'compression' => true, 'tcp_keepalive' => 0, 'lazy' => null, 'redis_cluster' => false, @@ -197,9 +196,6 @@ trait RedisTrait if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } - if ($params['compression'] && \defined('Redis::COMPRESSION_LZF')) { - $redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF); - } return true; }; @@ -225,9 +221,6 @@ trait RedisTrait if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } - if ($params['compression'] && \defined('Redis::COMPRESSION_LZF')) { - $redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF); - } } elseif (is_a($class, \RedisCluster::class, true)) { $initializer = function () use ($class, $params, $dsn, $hosts) { foreach ($hosts as $i => $host) { @@ -243,9 +236,6 @@ trait RedisTrait if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) { $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']); } - if ($params['compression'] && \defined('Redis::COMPRESSION_LZF')) { - $redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF); - } switch ($params['failover']) { case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break; case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break;