1313
1414use Predis \Connection \Aggregate \ClusterInterface ;
1515use Predis \Connection \Aggregate \RedisCluster ;
16- use Predis \Connection \Factory ;
1716use Predis \Response \Status ;
1817use Symfony \Component \Cache \Exception \CacheException ;
19
D30D
code>18use Symfony \Component \Cache \Exception \InvalidArgumentException ;
@@ -37,7 +36,10 @@ trait RedisTrait
3736 'retry_interval ' => 0 ,
3837 'compression ' => true ,
3938 'tcp_keepalive ' => 0 ,
40- 'lazy ' => false ,
39+ 'lazy ' => null ,
40+ 'redis_cluster ' => false ,
41+ 'dbindex ' => 0 ,
42+ 'failover ' => 'none ' ,
4143 );
4244 private $ redis ;
4345 private $ marshaller ;
@@ -53,7 +55,7 @@ private function init($redisClient, $namespace, $defaultLifetime, ?MarshallerInt
5355 throw new InvalidArgumentException (sprintf ('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed. ' , $ match [0 ]));
5456 }
5557 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 )));
5759 }
5860 $ this ->redis = $ redisClient ;
5961 $ this ->marshaller = $ marshaller ?? new DefaultMarshaller ();
@@ -74,57 +76,87 @@ private function init($redisClient, $namespace, $defaultLifetime, ?MarshallerInt
7476 *
7577 * @throws InvalidArgumentException when the DSN is invalid
7678 *
77- * @return \Redis|\Predis\Client According to the "class" option
79+ * @return \Redis|\RedisCluster|\ Predis\Client According to the "class" option
7880 */
7981 public static function createConnection ($ dsn , array $ options = array ())
8082 {
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 ));
8385 }
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 ];
8794 }
8895
89- return 'file:// ' ;
96+ return 'file: ' .( $ m [ 1 ] ?? '' ) ;
9097 }, $ 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 )) {
95100 throw new InvalidArgumentException (sprintf ('Invalid Redis DSN: %s ' , $ dsn ));
96101 }
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+
111105 if (isset ($ params ['query ' ])) {
112106 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 , ': ' )) {
D30D
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+ }
114139 }
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 ' ];
118151 }
119- $ class = null === $ params ['class ' ] ? (\extension_loaded ('redis ' ) ? \Redis::class : \Predis \Client::class) : $ params ['class ' ];
120152
121153 if (is_a ($ class , \Redis::class, true )) {
122154 $ connect = $ params ['persistent ' ] || $ params ['persistent_id ' ] ? 'pconnect ' : 'connect ' ;
123155 $ redis = new $ class ();
124156
125- $ initializer = function ($ redis ) use ($ connect , $ params , $ dsn , $ auth ) {
157+ $ initializer = function ($ redis ) use ($ connect , $ params , $ dsn , $ auth, $ hosts ) {
126158 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 ' ]);
128160 } catch (\RedisException $ e ) {
129161 throw new InvalidArgumentException (sprintf ('Redis connection failed (%s): %s ' , $ e ->getMessage (), $ dsn ));
130162 }
@@ -160,15 +192,82 @@ public static function createConnection($dsn, array $options = array())
160192 } else {
161193 $ initializer ($ redis );
162194 }
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 ();
163242 } 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 ));
168267 } 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 ));
170269 } else {
171- throw new InvalidArgumentException (sprintf ('Class "%s" does not exist ' , $ class ));
270+ throw new InvalidArgumentException (sprintf ('Class "%s" does not exist. ' , $ class ));
172271 }
173272
174273 return $ redis ;
@@ -183,7 +282,6 @@ protected function doFetch(array $ids)
183282 return array ();
184283 }
185284
186- $ i = -1 ;
187285 $ result = array ();
188286
189287 if ($ this ->redis instanceof \Predis \Client) {
@@ -244,6 +342,7 @@ protected function doClear($namespace)
244342 $ h ->connect ($ host [0 ], $ host [1 ]);
245343 }
246344 }
345+
247346 foreach ($ hosts as $ host ) {
248347 if (!isset ($ namespace [0 ])) {
249348 $ cleared = $ host ->flushDb () && $ cleared ;
0 commit comments