13
13
14
14
use Predis \Connection \Aggregate \ClusterInterface ;
15
15
use Predis \Connection \Aggregate \RedisCluster ;
16
- use Predis \Connection \Factory ;
17
16
use Predis \Response \Status ;
18
17
use Symfony \Component \Cache \Exception \CacheException ;
19
18
use Symfony \Component \Cache \Exception \InvalidArgumentException ;
@@ -37,7 +36,10 @@ trait RedisTrait
37
36
'retry_interval ' => 0 ,
38
37
'compression ' => true ,
39
38
'tcp_keepalive ' => 0 ,
40
- 'lazy ' => false ,
39
+ 'lazy ' => null ,
40
+ 'redis_cluster ' => false ,
41
+ 'dbindex ' => 0 ,
42
+ 'failover ' => 'none ' ,
41
43
);
42
44
private $ redis ;
43
45
private $ marshaller ;
@@ -53,7 +55,7 @@ private function init($redisClient, $namespace, $defaultLifetime, ?MarshallerInt
53
55
throw new InvalidArgumentException (sprintf ('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed. ' , $ match [0 ]));
54
56
}
55
57
if (!$ redisClient instanceof \Redis && !$ redisClient instanceof \RedisArray && !$ redisClient instanceof \RedisCluster && !$ redisClient instanceof \Predis \Client && !$ redisClient instanceof RedisProxy && !$ redisClient instanceof RedisClusterProxy) {
56
- throw new InvalidArgumentException (sprintf ('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given ' , __METHOD__ , \is_object ($ redisClient ) ? \get_class ($ redisClient ) : \gettype ($ redisClient )));
58
+ throw new InvalidArgumentException (sprintf ('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given. ' , __METHOD__ , \is_object ($ redisClient ) ? \get_class ($ redisClient ) : \gettype ($ redisClient )));
57
59
}
58
60
$ this ->redis = $ redisClient ;
59
61
$ this ->marshaller = $ marshaller ?? new DefaultMarshaller ();
@@ -74,57 +76,87 @@ private function init($redisClient, $namespace, $defaultLifetime, ?MarshallerInt
74
76
*
75
77
* @throws InvalidArgumentException when the DSN is invalid
76
78
*
77
- * @return \Redis|\Predis\Client According to the "class" option
79
+ * @return \Redis|\RedisCluster|\ Predis\Client According to the "class" option
78
80
*/
79
81
public static function createConnection ($ dsn , array $ options = array ())
80
82
{
81
- if (0 !== strpos ($ dsn , 'redis:// ' )) {
82
- throw new InvalidArgumentException (sprintf ('Invalid Redis DSN: %s does not start with "redis://" ' , $ dsn ));
83
+ if (0 !== strpos ($ dsn , 'redis: ' )) {
84
+ throw new InvalidArgumentException (sprintf ('Invalid Redis DSN: %s does not start with "redis:". ' , $ dsn ));
83
85
}
84
- $ params = preg_replace_callback ('#^redis://(?:(?:[^:@]*+:)?([^@]*+)@)?# ' , function ($ m ) use (&$ auth ) {
85
- if (isset ($ m [1 ])) {
86
- $ auth = $ m [1 ];
86
+
87
+ if (!\extension_loaded ('redis ' ) && !class_exists (\Predis \Client::class)) {
88
+ throw new CacheException (sprintf ('Cannot find the "redis" extension nor the "predis/predis" package: %s ' , $ dsn ));
89
+ }
90
+
91
+ $ params = preg_replace_callback ('#^redis:(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?# ' , function ($ m ) use (&$ auth ) {
92
+ if (isset ($ m [2 ])) {
93
+ $ auth = $ m [2 ];
87
94
}
88
95
89
- return 'file:// ' ;
96
+ return 'file: ' .( $ m [ 1 ] ?? '' ) ;
90
97
}, $ dsn );
91
- if (false === $ params = parse_url ($ params )) {
92
- throw new InvalidArgumentException (sprintf ('Invalid Redis DSN: %s ' , $ dsn ));
93
- }
94
- if (!isset ($ params ['host ' ]) && !isset ($ params ['path ' ])) {
98
+
99
+ if (false === $ params = parse_url ($ dsn )) {
95
100
throw new InvalidArgumentException (sprintf ('Invalid Redis DSN: %s ' , $ dsn ));
96
101
}
97
- if (isset ($ params ['path ' ]) && preg_match ('#/(\d+)$# ' , $ params ['path ' ], $ m )) {
98
- $ params ['dbindex ' ] = $ m [1 ];
99
- $ params ['path ' ] = substr ($ params ['path ' ], 0 , -\strlen ($ m [0 ]));
100
- }
101
- if (isset ($ params ['host ' ])) {
102
- $ scheme = 'tcp ' ;
103
- } else {
104
- $ scheme = 'unix ' ;
105
- }
106
- $ params += array (
107
- 'host ' => isset ($ params ['host ' ]) ? $ params ['host ' ] : $ params ['path ' ],
108
- 'port ' => isset ($ params ['host ' ]) ? 6379 : null ,
109
- 'dbindex ' => 0 ,
110
- );
102
+
103
+ $ query = $ hosts = array ();
104
+
111
105
if (isset ($ params ['query ' ])) {
112
106
parse_str ($ params ['query ' ], $ query );
113
- $ params += $ query ;
107
+
108
+ if (isset ($ query ['host ' ])) {
109
+ if (!\is_array ($ hosts = $ query ['host ' ])) {
110
+ throw new InvalidArgumentException (sprintf ('Invalid Redis DSN: %s ' , $ dsn ));
111
+ }
112
+ foreach ($ hosts as $ host => $ parameters ) {
113
+ if (\is_string ($ parameters )) {
114
+ parse_str ($ parameters , $ parameters );
115
+ }
116
+ if (false === $ i = strrpos ($ host , ': ' )) {
117
+ $ hosts [$ host ] = array ('scheme ' => 'tcp ' , 'host ' => $ host , 'port ' => 6379 ) + $ parameters ;
118
+ } elseif ($ port = (int ) substr ($ host , 1 + $ i )) {
119
+ $ hosts [$ host ] = array ('scheme ' => 'tcp ' , 'host ' => substr ($ host , 0 , $ i ), 'port ' => $ port ) + $ parameters ;
120
+ } else {
121
+ $ hosts [$ host ] = array ('scheme ' => 'unix ' , 'path ' => substr ($ host , 0 , $ i )) + $ parameters ;
122
+ }
123
+ }
124
+ $ hosts = array_values ($ hosts );
125
+ }
126
+ }
127
+
128
+ if (isset ($ params ['host ' ]) || isset ($ params ['path ' ])) {
129
+ if (!isset ($ params ['dbindex ' ]) && isset ($ params ['path ' ]) && preg_match ('#/(\d+)$# ' , $ params ['path ' ], $ m )) {
130
+ $ params ['dbindex ' ] = $ m [1 ];
131
+ $ params ['path ' ] = substr ($ params ['path ' ], 0 , -\strlen ($ m [0 ]));
132
+ }
133
+
134
+ if (isset ($ params ['host ' ])) {
135
+ array_unshift ($ hosts , array ('scheme ' => 'tcp ' , 'host ' => $ params ['host ' ], 'port ' => $ params ['port ' ] ?? 6379 ));
136
+ } else {
137
+ array_unshift ($ hosts , array ('scheme ' => 'unix ' , 'path ' => $ params ['path ' ]));
138
+ }
114
139
}
115
- $ params += $ options + self ::$ defaultConnectionOptions ;
116
- if (null === $ params ['class ' ] && !\extension_loaded ('redis ' ) && !class_exists (\Predis \Client::class)) {
117
- throw new CacheException (sprintf ('Cannot find the "redis" extension, and "predis/predis" is not installed: %s ' , $ dsn ));
140
+
141
+ if (!$ hosts ) {
142
+ throw new InvalidArgumentException (sprintf ('Invalid Redis DSN: %s ' , $ dsn ));
143
+ }
144
+
145
+ $ params += $ query + $ options + self ::$ defaultConnectionOptions ;
146
+
147
+ if (null === $ params ['class ' ] && \extension_loaded ('redis ' )) {
148
+ $ class = $ params ['redis_cluster ' ] ? \RedisCluster::class : (1 < \count ($ hosts ) ? \RedisArray::class : \Redis::class);
149
+ } else {
150
+ $ class = null === $ params ['class ' ] ? \Predis \Client::class : $ params ['class ' ];
118
151
}
119
- $ class = null === $ params ['class ' ] ? (\extension_loaded ('redis ' ) ? \Redis::class : \Predis \Client::class) : $ params ['class ' ];
120
152
121
153
if (is_a ($ class , \Redis::class, true )) {
122
154
$ connect = $ params ['persistent ' ] || $ params ['persistent_id ' ] ? 'pconnect ' : 'connect ' ;
123
155
$ redis = new $ class ();
124
156
125
- $ initializer = function ($ redis ) use ($ connect , $ params , $ dsn , $ auth ) {
157
+ $ initializer = function ($ redis ) use ($ connect , $ params , $ dsn , $ auth, $ hosts ) {
126
158
try {
127
- @$ redis ->{$ connect }($ params [ 'host ' ], $ params [ 'port ' ], $ params ['timeout ' ], $ params ['persistent_id ' ], $ params ['retry_interval ' ]);
159
+ @$ redis ->{$ connect }($ hosts [ 0 ][ 'host ' ], $ hosts [ 0 ][ 'port ' ], $ params ['timeout ' ], ( string ) $ params ['persistent_id ' ], $ params ['retry_interval ' ]);
128
160
} catch (\RedisException $ e ) {
129
161
throw new InvalidArgumentException (sprintf ('Redis connection failed (%s): %s ' , $ e ->getMessage (), $ dsn ));
130
162
}
@@ -160,15 +192,82 @@ public static function createConnection($dsn, array $options = array())
160
192
} else {
161
193
$ initializer ($ redis );
162
194
}
195
+ } elseif (is_a ($ class , \RedisArray::class, true )) {
196
+ foreach ($ hosts as $ i => $ host ) {
197
+ $ hosts [$ i ] = 'tcp ' === $ host ['scheme ' ] ? $ host ['host ' ].': ' .$ host ['port ' ] : $ host ['path ' ];
198
+ }
199
+ $ params ['lazy_connect ' ] = $ params ['lazy ' ] ?? true ;
200
+ $ params ['connect_timeout ' ] = $ params ['timeout ' ];
201
+
202
+ try {
203
+ $ redis = new $ class ($ hosts , $ params );
204
+ } catch (\RedisClusterException $ e ) {
205
+ throw new InvalidArgumentException (sprintf ('Redis connection failed (%s): %s ' , $ e ->getMessage (), $ dsn ));
206
+ }
207
+
208
+ if (0 < $ params ['tcp_keepalive ' ] && \defined ('Redis::OPT_TCP_KEEPALIVE ' )) {
209
+ $ redis ->setOption (\Redis::OPT_TCP_KEEPALIVE , $ params ['tcp_keepalive ' ]);
210
+ }
211
+ if ($ params ['compression ' ] && \defined ('Redis::COMPRESSION_LZF ' )) {
212
+ $ redis ->setOption (\Redis::OPT_COMPRESSION , \Redis::COMPRESSION_LZF );
213
+ }
214
+ } elseif (is_a ($ class , \RedisCluster::class, true )) {
215
+ $ initializer = function () use ($ class , $ params , $ dsn , $ hosts ) {
216
+ foreach ($ hosts as $ i => $ host ) {
217
+ $ hosts [$ i ] = 'tcp ' === $ host ['scheme ' ] ? $ host ['host ' ].': ' .$ host ['port ' ] : $ host ['path ' ];
218
+ }
219
+
220
+ try {
221
+ $ redis = new $ class (null , $ hosts , $ params ['timeout ' ], $ params ['read_timeout ' ], (bool ) $ params ['persistent ' ]);
222
+ } catch (\RedisClusterException $ e ) {
223
+ throw new InvalidArgumentException (sprintf ('Redis connection failed (%s): %s ' , $ e ->getMessage (), $ dsn ));
224
+ }
225
+
226
+ if (0 < $ params ['tcp_keepalive ' ] && \defined ('Redis::OPT_TCP_KEEPALIVE ' )) {
227
+ $ redis ->setOption (\Redis::OPT_TCP_KEEPALIVE , $ params ['tcp_keepalive ' ]);
228
+ }
229
+ if ($ params ['compression ' ] && \defined ('Redis::COMPRESSION_LZF ' )) {
230
+ $ redis ->setOption (\Redis::OPT_COMPRESSION , \Redis::COMPRESSION_LZF );
231
+ }
232
+ switch ($ params ['failover ' ]) {
233
+ case 'error ' : $ redis ->setOption (\RedisCluster::OPT_SLAVE_FAILOVER , \RedisCluster::FAILOVER_ERROR ); break ;
234
+ case 'distribute ' : $ redis ->setOption (\RedisCluster::OPT_SLAVE_FAILOVER , \RedisCluster::FAILOVER_DISTRIBUTE ); break ;
235
+ case 'slaves ' : $ redis ->setOption (\RedisCluster::OPT_SLAVE_FAILOVER , \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES ); break ;
236
+ }
237
+
238
+ return $ redis ;
239
+ };
240
+
241
+ $ redis = $ params ['lazy ' ] ? new RedisClusterProxy ($ initializer ) : $ initializer ();
163
242
} elseif (is_a ($ class , \Predis \Client::class, true )) {
164
- $ params ['scheme ' ] = $ scheme ;
165
- $ params ['database ' ] = $ params ['dbindex ' ] ?: null ;
166
- $ params ['password ' ] = $ auth ;
167
- $ redis = new $ class ((new Factory ())->create ($ params ));
243
+ if ($ params ['redis_cluster ' ]) {
244
+ $ params ['cluster ' ] = 'redis ' ;
245
+ }
246
+ $ params += array ('parameters ' => array ());
247
+ $ params ['parameters ' ] += array (
248
+ 'persistent ' => $ params ['persistent ' ],
249
+ 'timeout ' => $ params ['timeout ' ],
250
+ 'read_write_timeout ' => $ params ['read_timeout ' ],
251
+ 'tcp_nodelay ' => true ,
252
+ );
253
+ if ($ params ['dbindex ' ]) {
254
+ $ params ['parameters ' ]['database ' ] = $ params ['dbindex ' ];
255
+ }
256
+ if (null !== $ auth ) {
257
+ $ params ['parameters ' ]['password ' ] = $ auth ;
258
+ }
259
+ if (1 === \count ($ hosts ) && !$ params ['redis_cluster ' ]) {
260
+ $ hosts = $ hosts [0 ];
261
+ } elseif (\in_array ($ params ['failover ' ], array ('slaves ' , 'distribute ' ), true ) && !isset ($ params ['replication ' ])) {
262
+ $ params ['replication ' ] = true ;
263
+ $ hosts [0 ] += array ('alias ' => 'master ' );
264
+ }
265
+
266
+ $ redis = new $ class ($ hosts , array_diff_key ($ params , self ::$ defaultConnectionOptions ));
168
267
} elseif (class_exists ($ class , false )) {
169
- throw new InvalidArgumentException (sprintf ('"%s" is not a subclass of "Redis" or " Predis\Client" ' , $ class ));
268
+ throw new InvalidArgumentException (sprintf ('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor " Predis\Client". ' , $ class ));
170
269
} else {
171
- throw new InvalidArgumentException (sprintf ('Class "%s" does not exist ' , $ class ));
270
+ throw new InvalidArgumentException (sprintf ('Class "%s" does not exist. ' , $ class ));
172
271
}
173
272
174
273
return $ redis ;
@@ -183,7 +282,6 @@ protected function doFetch(array $ids)
183
282
return array ();
184
283
}
185
284
186
- $ i = -1 ;
187
285
$ result = array ();
188
286
189
287
if ($ this ->redis instanceof \Predis \Client) {
@@ -244,6 +342,7 @@ protected function doClear($namespace)
244
342
$ h ->connect ($ host [0 ], $ host [1 ]);
245
343
}
246
344
}
345
+
247
346
foreach ($ hosts as $ host ) {
248
347
if (!isset ($ namespace [0 ])) {
249
348
$ cleared = $ host ->flushDb () && $ cleared ;
0 commit comments