21
21
use Predis \Response \ErrorInterface ;
22
22
use Predis \Response \Status ;
23
23
use Relay \Relay ;
24
+ use Relay \Cluster as RelayCluster ;
24
25
use Relay \Sentinel ;
25
26
use Symfony \Component \Cache \Exception \CacheException ;
26
27
use Symfony \Component \Cache \Exception \InvalidArgumentException ;
@@ -41,19 +42,21 @@ trait RedisTrait
41
42
'persistent_id ' => null ,
42
43
'timeout ' => 30 ,
43
44
'read_timeout ' => 0 ,
45
+ 'command_timeout ' => 0 ,
44
46
'retry_interval ' => 0 ,
45
47
'tcp_keepalive ' => 0 ,
46
48
'lazy ' => null ,
47
49
'redis_cluster ' => false ,
50
+ 'relay_cluster ' => false ,
48
51
'redis_sentinel ' => null ,
49
52
'dbindex ' => 0 ,
50
53
'failover ' => 'none ' ,
51
54
'ssl ' => null , // see https://php.net/context.ssl
52
55
];
53
- private \Redis |Relay |\RedisArray |\RedisCluster |\Predis \ClientInterface $ redis ;
56
+ private \Redis |Relay |RelayCluster | \RedisArray |\RedisCluster |\Predis \ClientInterface $ redis ;
54
57
private MarshallerInterface $ marshaller ;
55
58
56
- private function init (\Redis |Relay |\RedisArray |\RedisCluster |\Predis \ClientInterface $ redis , string $ namespace , int $ defaultLifetime , ?MarshallerInterface $ marshaller ): void
59
+ private function init (\Redis |Relay |RelayCluster | \RedisArray |\RedisCluster |\Predis \ClientInterface $ redis , string $ namespace , int $ defaultLifetime , ?MarshallerInterface $ marshaller ): void
57
60
{
58
61
parent ::__construct ($ namespace , $ defaultLifetime );
59
62
@@ -85,7 +88,7 @@ private function init(\Redis|Relay|\RedisArray|\RedisCluster|\Predis\ClientInter
85
88
*
86
89
* @throws InvalidArgumentException when the DSN is invalid
87
90
*/
88
- public static function createConnection (#[\SensitiveParameter] string $ dsn , array $ options = []): \Redis |\RedisArray |\RedisCluster |\Predis \ClientInterface |Relay
91
+ public static function createConnection (#[\SensitiveParameter] string $ dsn , array $ options = []): \Redis |\RedisArray |\RedisCluster |\Predis \ClientInterface |Relay | RelayCluster
89
92
{
90
93
if (str_starts_with ($ dsn , 'redis: ' )) {
91
94
$ scheme = 'redis ' ;
@@ -188,9 +191,20 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
188
191
$ params ['lazy ' ] = filter_var ($ params ['lazy ' ], \FILTER_VALIDATE_BOOLEAN );
189
192
}
190
193
$ params ['redis_cluster ' ] = filter_var ($ params ['redis_cluster ' ], \FILTER_VALIDATE_BOOLEAN );
191
-
192
- if ($ params ['redis_cluster ' ] && isset ($ params ['redis_sentinel ' ])) {
193
- throw new InvalidArgumentException ('Cannot use both "redis_cluster" and "redis_sentinel" at the same time. ' );
194
+ $ params ['relay_cluster ' ] = filter_var ($ params ['relay_cluster ' ], \FILTER_VALIDATE_BOOLEAN );
195
+
196
+ $ conflictingOptions = array_filter ([
197
+ 'redis_cluster ' => $ params ['redis_cluster ' ],
198
+ 'relay_cluster ' => $ params ['relay_cluster ' ],
199
+ 'redis_sentinel ' => $ params ['redis_sentinel ' ],
200
+ ], fn ($ value ) => $ value === true );
201
+
202
+ if (count ($ conflictingOptions ) > 1 ) {
203
+ $ keys = implode ('", " ' , array_keys ($ conflictingOptions ));
204
+ throw new InvalidArgumentException (sprintf (
205
+ 'Cannot use %s at the same time. Please configure only one of "redis_cluster", "relay_cluster", or "redis_sentinel". ' ,
206
+ $ keys
207
+ ));
194
208
}
195
209
196
210
$ class = $ params ['class ' ] ?? match (true ) {
@@ -200,9 +214,9 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
200
214
\extension_loaded ('relay ' ) => Relay::class,
201
215
default => \Predis \Client::class,
202
216
},
203
- 1 < \count ($ hosts ) && \extension_loaded ('redis ' ) => \RedisArray::class,
204
- \extension_loaded ('redis ' ) => \Redis::class,
205
- \extension_loaded ('relay ' ) => Relay::class,
217
+ $ params [ ' relay_cluster ' ] === false && 1 < \count ($ hosts ) && \extension_loaded ('redis ' ) => \RedisArray::class,
218
+ $ params [ ' relay_cluster ' ] === false && \extension_loaded ('redis ' ) => \Redis::class,
219
+ \extension_loaded ('relay ' ) => $ params [ ' relay_cluster ' ] === true ? RelayCluster::class : Relay::class,
206
220
default => \Predis \Client::class,
207
221
};
208
222
@@ -348,6 +362,46 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
348
362
if (0 < $ params ['tcp_keepalive ' ] && (!$ isRedisExt || \defined ('Redis::OPT_TCP_KEEPALIVE ' ))) {
349
363
$ redis ->setOption ($ isRedisExt ? \Redis::OPT_TCP_KEEPALIVE : Relay::OPT_TCP_KEEPALIVE , $ params ['tcp_keepalive ' ]);
350
364
}
365
+ } elseif (is_a ($ class , RelayCluster::class, true )) {
366
+ if (version_compare (phpversion ('relay ' ), '0.10.0 ' , '< ' )) {
367
+ throw new InvalidArgumentException ('Using RelayCluster is supported from ext-relay 0.10.0 or higher. ' );
368
+ }
369
+
370
+ $ initializer = static function () use ($ class , $ params , $ hosts ) {
371
+ foreach ($ hosts as $ i => $ host ) {
372
+ $ hosts [$ i ] = match ($ host ['scheme ' ]) {
373
+ 'tcp ' => $ host ['host ' ].': ' .$ host ['port ' ],
374
+ 'tls ' => 'tls:// ' .$ host ['host ' ].': ' .$ host ['port ' ],
375
+ default => $ host ['path ' ],
376
+ };
377
+ }
378
+
379
+ try {
380
+ $ relayCluster = new $ class (
381
+ name: null ,
382
+ seeds: $ hosts ,
383
+ connect_timeout: $ params ['timeout ' ],
384
+ command_timeout: $ params ['command_timeout ' ],
385
+ persistent: (bool ) $ params ['persistent ' ],
386
+ auth: $ params ['auth ' ] ?? null ,
387
+ context: []
388
+ );
389
+ } catch (\Relay \Exception $ e ) {
390
+ throw new InvalidArgumentException ('Relay cluster connection failed: ' .$ e ->getMessage ());
391
+ }
392
+
393
+ if (0 < $ params ['tcp_keepalive ' ]) {
394
+ $ relayCluster ->setOption (Relay::OPT_TCP_KEEPALIVE , $ params ['tcp_keepalive ' ]);
395
+ }
396
+
397
+ if (0 < $ params ['read_timeout ' ]) {
398
+ $ relayCluster ->setOption (Relay::OPT_READ_TIMEOUT , $ params ['read_timeout ' ]);
399
+ }
400
+
401
+ return $ relayCluster ;
402
+ };
403
+
404
+ $ redis = $ params ['lazy ' ] ? RelayClusterProxy::createLazyProxy ($ initializer ) : $ initializer ();
351
405
} elseif (is_a ($ class , \RedisCluster::class, true )) {
352
406
$ initializer = static function () use ($ isRedisExt , $ class , $ params , $ hosts ) {
353
407
foreach ($ hosts as $ i => $ host ) {
@@ -478,6 +532,40 @@ protected function doClear(string $namespace): bool
478
532
}
479
533
480
534
$ cleared = true ;
535
+
536
+ if ($ this ->redis instanceof RelayCluster) {
537
+ $ prefix = Relay::SCAN_PREFIX & $ this ->redis ->getOption (Relay::OPT_SCAN ) ? '' : $ this ->redis ->getOption (Relay::OPT_PREFIX );
538
+ $ prefixLen = \strlen ($ prefix );
539
+ $ pattern = $ prefix .$ namespace .'* ' ;
540
+ foreach ($ this ->redis ->_masters () as $ ipAndPort ) {
541
+ $ address = implode (': ' , $ ipAndPort );
542
+ $ cursor = null ;
543
+ do {
544
+ // mixed &$iterator
545
+ // array|string $key_or_address
546
+ // mixed $match = null
547
+ // int $count = 0
548
+ // string|null $type = null
549
+ $ keys = $ this ->redis ->scan ($ cursor , $ address , $ pattern , 1000 );
550
+ if (isset ($ keys [1 ]) && \is_array ($ keys [1 ])) {
551
+ $ cursor = $ keys [0 ];
552
+ $ keys = $ keys [1 ];
553
+ }
554
+
555
+ if ($ keys ) {
556
+ if ($ prefixLen ) {
557
+ foreach ($ keys as $ i => $ key ) {
558
+ $ keys [$ i ] = substr ($ key , $ prefixLen );
559
+ }
560
+ }
561
+ $ this ->doDelete ($ keys );
562
+ }
563
+ } while ($ cursor );
564
+ }
565
+
566
+ return $ cleared ;
567
+ }
568
+
481
569
$ hosts = $ this ->getHosts ();
482
570
$ host = reset ($ hosts );
483
571
if ($ host instanceof \Predis \Client) {
@@ -605,8 +693,9 @@ private function pipeline(\Closure $generator, ?object $redis = null): \Generato
605
693
$ ids = [];
606
694
$ redis ??= $ this ->redis ;
607
695
608
- if ($ redis instanceof \RedisCluster || ($ redis instanceof \Predis \ClientInterface && ($ redis ->getConnection () instanceof RedisCluster || $ redis ->getConnection () instanceof Predis2RedisCluster))) {
696
+ if ($ redis instanceof \RedisCluster || $ redis instanceof \ Relay \Cluster || ($ redis instanceof \Predis \ClientInterface && ($ redis ->getConnection () instanceof RedisCluster || $ redis ->getConnection () instanceof Predis2RedisCluster))) {
609
697
// phpredis & predis don't support pipelining with RedisCluster
698
+ // \Relay\Cluster does not support multi with pipeline mode
610
699
// see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
611
700
// see https://github.com/nrk/predis/issues/267#issuecomment-123781423
612
701
$ results = [];