Skip to content

Commit 1bf86b6

Browse files
committed
Add phpredis serialization and compression config support
1 parent 5321e34 commit 1bf86b6

File tree

6 files changed

+367
-51
lines changed

6 files changed

+367
-51
lines changed

.github/workflows/tests.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ jobs:
4949
uses: shivammathur/setup-php@v2
5050
with:
5151
php-version: ${{ matrix.php }}
52-
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis, memcached
52+
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis-phpredis/[email protected], igbinary, msgpack, lzf, zstd, lz4, memcached
5353
tools: composer:v2
5454
coverage: none
55+
env:
56+
REDIS_CONFIGURE_OPTS: --enable-redis --enable-redis-igbinary --enable-redis-msgpack --enable-redis-lzf --with-liblzf --enable-redis-zstd --with-libzstd --enable-redis-lz4 --with-liblz4
57+
REDIS_LIBS: liblz4-dev, liblzf-dev, libzstd-dev
5558

5659
- name: Set Minimum PHP 8.0 Versions
5760
uses: nick-invision/retry@v1

src/Illuminate/Cache/PhpRedisLock.php

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
namespace Illuminate\Cache;
44

55
use Illuminate\Redis\Connections\PhpRedisConnection;
6-
use Redis;
7-
use UnexpectedValueException;
86

97
class PhpRedisLock extends RedisLock
108
{
9+
/**
10+
* The phpredis factory implementation.
11+
*
12+
* @var \Illuminate\Redis\Connections\PhpredisConnection
13+
*/
14+
protected $redis;
15+
1116
/**
1217
* Create a new phpredis lock instance.
1318
*
@@ -31,7 +36,7 @@ public function release()
3136
LuaScripts::releaseLock(),
3237
1,
3338
$this->name,
34-
$this->serializedAndCompressedOwner()
39+
...$this->redis->pack([$this->owner])
3540
);
3641
}
3742

@@ -41,72 +46,64 @@ public function release()
4146
* @return string
4247
*
4348
* @throws \UnexpectedValueException
49+
*
50+
* @deprecated Will be removed in a later laravel version. Use PhpRedisConnection::pack.
51+
* @see \Illuminate\Redis\Connections\PhpRedisConnection::pack
4452
*/
4553
protected function serializedAndCompressedOwner(): string
4654
{
47-
$client = $this->redis->client();
48-
49-
$owner = $client->_serialize($this->owner);
50-
51-
// https://github.com/phpredis/phpredis/issues/1938
52-
if ($this->compressed()) {
53-
if ($this->lzfCompressed()) {
54-
$owner = \lzf_compress($owner);
55-
} elseif ($this->zstdCompressed()) {
56-
$owner = \zstd_compress($owner, $client->getOption(Redis::OPT_COMPRESSION_LEVEL));
57-
} elseif ($this->lz4Compressed()) {
58-
$owner = \lz4_compress($owner, $client->getOption(Redis::OPT_COMPRESSION_LEVEL));
59-
} else {
60-
throw new UnexpectedValueException(sprintf(
61-
'Unknown phpredis compression in use [%d]. Unable to release lock.',
62-
$client->getOption(Redis::OPT_COMPRESSION)
63-
));
64-
}
65-
}
66-
67-
return $owner;
55+
return $this->redis->pack([$this->owner])[0];
6856
}
6957

7058
/**
7159
* Determine if compression is enabled.
7260
*
7361
* @return bool
62+
*
63+
* @deprecated Will be removed in a later laravel version. Use PhpRedisConnection::compressed.
64+
* @see \Illuminate\Redis\Connections\PhpRedisConnection::compressed
7465
*/
7566
protected function compressed(): bool
7667
{
77-
return $this->redis->client()->getOption(Redis::OPT_COMPRESSION) !== Redis::COMPRESSION_NONE;
68+
return $this->redis->compressed();
7869
}
7970

8071
/**
8172
* Determine if LZF compression is enabled.
8273
*
8374
* @return bool
75+
*
76+
* @deprecated Will be removed in a later laravel version. Use PhpRedisConnection::lzfCompressed.
77+
* @see \Illuminate\Redis\Connections\PhpRedisConnection::lzfCompressed
8478
*/
8579
protected function lzfCompressed(): bool
8680
{
87-
return defined('Redis::COMPRESSION_LZF') &&
88-
$this->redis->client()->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZF;
81+
return $this->redis->lzfCompressed();
8982
}
9083

9184
/**
9285
* Determine if ZSTD compression is enabled.
9386
*
9487
* @return bool
88+
*
89+
* @deprecated Will be removed in a later laravel version. Use PhpRedisConnection::zstdCompressed.
90+
* @see \Illuminate\Redis\Connections\PhpRedisConnection::zstdCompressed
9591
*/
9692
protected function zstdCompressed(): bool
9793
{
98-
return defined('Redis::COMPRESSION_ZSTD') &&
99-
$this->redis->client()->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_ZSTD;
94+
return $this->redis->zstdCompressed();
10095
}
10196

10297
/**
10398
* Determine if LZ4 compression is enabled.
10499
*
105100
* @return bool
101+
*
102+
* @deprecated Will be removed in a later laravel version. Use PhpRedisConnection::lz4Compressed.
103+
* @see \Illuminate\Redis\Connections\PhpRedisConnection::lz4Compressed
106104
*/
107105
protected function lz4Compressed(): bool
108106
{
109-
return defined('Redis::COMPRESSION_LZ4') &&
110-
$this->redis->client()->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZ4;
107+
return $this->redis->lz4Compressed();
111108
}
112109
}

src/Illuminate/Redis/Connections/PhpRedisConnection.php

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Redis;
1010
use RedisCluster;
1111
use RedisException;
12+
use RuntimeException;
13+
use UnexpectedValueException;
1214

1315
/**
1416
* @mixin \Redis
@@ -29,6 +31,27 @@ class PhpRedisConnection extends Connection implements ConnectionContract
2931
*/
3032
protected $config;
3133

34+
/**
35+
* In memory cache of version check whether phpredis supports packing and unpacking.
36+
*
37+
* @var bool|null
38+
*/
39+
private $supportsPack = null;
40+
41+
/**
42+
* In memory cache of version check whether phpredis supports lzf compression.
43+
*
44+
* @var bool|null
45+
*/
46+
private $supportsLzf = null;
47+
48+
/**
49+
* In memory cache of version check whether phpredis supports zstd compression.
50+
*
51+
* @var bool|null
52+
*/
53+
private $supportsZstd = null;
54+
3255
/**
3356
* Create a new PhpRedis connection.
3457
*
@@ -573,4 +596,138 @@ public function __call($method, $parameters)
573596
{
574597
return parent::__call(strtolower($method), $parameters);
575598
}
599+
600+
/**
601+
* Prepares values to be used with e.g. the `eval` command, because the
602+
* phpredis extension does not do it for us. This includes serialization and
603+
* compression if any of those settings are configured.
604+
*
605+
* @param array<int|string,string> $values
606+
* @return array<int|string,string>
607+
*/
608+
public function pack(array $values): array
609+
{
610+
if (empty($values)) {
611+
return $values;
612+
}
613+
614+
if ($this->supportsPack()) {
615+
return array_map([$this->client, '_pack'], $values);
616+
}
617+
618+
if ($this->compressed()) {
619+
if ($this->supportsLzf() && $this->lzfCompressed()) {
620+
if (! function_exists('lzf_compress')) {
621+
throw new RuntimeException("Missing 'lzf' extension to call 'lzf_compress'.");
622+
}
623+
624+
$processor = function ($value) {
625+
return \lzf_compress($this->client->_serialize($value));
626+
};
627+
} elseif ($this->supportsZstd() && $this->zstdCompressed()) {
628+
if (! function_exists('zstd_compress')) {
629+
throw new RuntimeException("Missing 'zstd' extension to call 'zstd_compress'.");
630+
}
631+
632+
$compressionLevel = $this->client->getOption(Redis::OPT_COMPRESSION_LEVEL);
633+
$processor = function ($value) use ($compressionLevel) {
634+
return \zstd_compress(
635+
$this->client->_serialize($value),
636+
$compressionLevel === 0 ? Redis::COMPRESSION_ZSTD_DEFAULT : $compressionLevel
637+
);
638+
};
639+
} else {
640+
throw new UnexpectedValueException(sprintf(
641+
'Not supported phpredis compression in use [%d].',
642+
$this->client->getOption(Redis::OPT_COMPRESSION)
643+
));
644+
}
645+
} else {
646+
$processor = function ($value) {
647+
return $this->client->_serialize($value);
648+
};
649+
}
650+
651+
return array_map($processor, $values);
652+
}
653+
654+
/**
655+
* Determine if compression is enabled.
656+
*
657+
* @return bool
658+
*/
659+
public function compressed(): bool
660+
{
661+
return defined('Redis::OPT_COMPRESSION') &&
662+
$this->client->getOption(Redis::OPT_COMPRESSION) !== Redis::COMPRESSION_NONE;
663+
}
664+
665+
/**
666+
* Determine if LZF compression is enabled.
667+
*
668+
* @return bool
669+
*/
670+
public function lzfCompressed(): bool
671+
{
672+
return defined('Redis::COMPRESSION_LZF') &&
673+
$this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZF;
674+
}
675+
676+
/**
677+
* Determine if ZSTD compression is enabled.
678+
*
679+
* @return bool
680+
*/
681+
public function zstdCompressed(): bool
682+
{
683+
return defined('Redis::COMPRESSION_ZSTD') &&
684+
$this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_ZSTD;
685+
}
686+
687+
/**
688+
* Determine if LZ4 compression is enabled.
689+
*
690+
* @return bool
691+
*/
692+
public function lz4Compressed(): bool
693+
{
694+
return defined('Redis::COMPRESSION_LZ4') &&
695+
$this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZ4;
696+
}
697+
698+
private function supportsPack(): bool
699+
{
700+
if ($this->supportsPack === null) {
701+
$phpredisVersion = phpversion('redis');
702+
703+
$this->supportsPack =
704+
($phpredisVersion !== false && version_compare($phpredisVersion, '5.3.5', '>='));
705+
}
706+
707+
return $this->supportsPack;
708+
}
709+
710+
private function supportsLzf(): bool
711+
{
712+
if ($this->supportsLzf === null) {
713+
$phpredisVersion = phpversion('redis');
714+
715+
$this->supportsLzf =
716+
($phpredisVersion !== false && version_compare($phpredisVersion, '4.3.0', '>='));
717+
}
718+
719+
return $this->supportsLzf;
720+
}
721+
722+
private function supportsZstd(): bool
723+
{
724+
if ($this->supportsZstd === null) {
725+
$phpredisVersion = phpversion('redis');
726+
727+
$this->supportsZstd =
728+
($phpredisVersion !== false && version_compare($phpredisVersion, '5.1.0', '>='));
729+
}
730+
731+
return $this->supportsZstd;
732+
}
576733
}

src/Illuminate/Redis/Connectors/PhpRedisConnector.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ protected function createClient(array $config)
106106
if (! empty($config['name'])) {
107107
$client->client('SETNAME', $config['name']);
108108
}
109+
110+
if (array_key_exists('serializer', $config)) {
111+
$client->setOption(Redis::OPT_SERIALIZER, $config['serializer']);
112+
}
113+
114+
if (array_key_exists('compression', $config)) {
115+
$client->setOption(Redis::OPT_COMPRESSION, $config['compression']);
116+
}
117+
118+
if (array_key_exists('compression_level', $config)) {
119+
$client->setOption(Redis::OPT_COMPRESSION_LEVEL, $config['compression_level']);
120+
}
109121
});
110122
}
111123

@@ -184,6 +196,18 @@ protected function createRedisClusterInstance(array $servers, array $options)
184196
if (! empty($options['name'])) {
185197
$client->client('SETNAME', $options['name']);
186198
}
199+
200+
if (array_key_exists('serializer', $options)) {
201+
$client->setOption(RedisCluster::OPT_SERIALIZER, $options['serializer']);
202+
}
203+
204+
if (array_key_exists('compression', $options)) {
205+
$client->setOption(RedisCluster::OPT_COMPRESSION, $options['compression']);
206+
}
207+
208+
if (array_key_exists('compression_level', $options)) {
209+
$client->setOption(RedisCluster::OPT_COMPRESSION_LEVEL, $options['compression_level']);
210+
}
187211
});
188212
}
189213

tests/Integration/Cache/PhpRedisCacheLockTest.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,6 @@ public function testRedisLockCanBeAcquiredAndReleasedWithLz4Compression()
224224
$this->markTestSkipped('Redis extension is not configured to support the lz4 compression.');
225225
}
226226

227-
$this->markTestIncomplete(
228-
'phpredis extension does not compress consistently with the php '.
229-
'extension lz4. See: https://github.com/phpredis/phpredis/issues/1939'
230-
);
231-
232227
$this->app['config']->set('database.redis.client', 'phpredis');
233228
$this->app['config']->set('cache.stores.redis.connection', 'default');
234229
$this->app['config']->set('cache.stores.redis.lock_connection', 'default');

0 commit comments

Comments
 (0)