1111
1212namespace Symfony \Component \Cache \Adapter ;
1313
14+ use Predis \Connection \Factory ;
15+ use Predis \Connection \Aggregate \PredisCluster ;
16+ use Predis \Connection \Aggregate \RedisCluster ;
1417use Symfony \Component \Cache \Exception \InvalidArgumentException ;
1518
1619/**
1922class RedisAdapter extends AbstractAdapter
2023{
2124 private static $ defaultConnectionOptions = array (
22- 'class ' => \Redis::class ,
25+ 'class ' => null ,
2326 'persistent ' => 0 ,
2427 'timeout ' => 0 ,
2528 'read_timeout ' => 0 ,
2629 'retry_interval ' => 0 ,
2730 );
2831 private $ redis ;
2932
30- public function __construct (\ Redis $ redisClient , $ namespace = '' , $ defaultLifetime = 0 )
33+ public function __construct ($ redisClient , $ namespace = '' , $ defaultLifetime = 0 )
3134 {
3235 parent ::__construct ($ namespace , $ defaultLifetime );
3336
3437 if (preg_match ('#[^-+_.A-Za-z0-9]# ' , $ namespace , $ match )) {
3538 throw new InvalidArgumentException (sprintf ('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed. ' , $ match [0 ]));
3639 }
40+ if ($ redisClient instanceof \Redis) {
41+ } elseif ($ redisClient instanceof \RedisArray) {
42+ } elseif ($ redisClient instanceof \RedisCluster) {
43+ } elseif ($ redisClient instanceof \Predis \Client) {
44+ } else {
45+ 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 )));
46+ }
3747 $ this ->redis = $ redisClient ;
3848 }
3949
@@ -52,7 +62,7 @@ public function __construct(\Redis $redisClient, $namespace = '', $defaultLifeti
5262 *
5363 * @throws InvalidArgumentException When the DSN is invalid.
5464 *
55- * @return \Redis
65+ * @return \Redis|\Predis\Client According to the "class" option.
5666 */
5767 public static function createConnection ($ dsn , array $ options = array ())
5868 {
@@ -86,7 +96,7 @@ public static function createConnection($dsn, array $options = array())
8696 $ params += $ query ;
8797 }
8898 $ params += $ options + self ::$ defaultConnectionOptions ;
89- $ class = $ params ['class ' ];
99+ $ class = null === $ params [ ' class ' ] ? ( extension_loaded ( ' redis ' ) ? \Redis::class : \ Predis \Client::class) : $ params ['class ' ];
90100
91101 if (is_a ($ class , \Redis::class, true )) {
92102 $ connect = empty ($ params ['persistent ' ]) ? 'connect ' : 'pconnect ' ;
@@ -105,8 +115,13 @@ public static function createConnection($dsn, array $options = array())
105115 $ e = preg_replace ('/^ERR / ' , '' , $ redis ->getLastError ());
106116 throw new InvalidArgumentException (sprintf ('Redis connection failed (%s): %s ' , $ e , $ dsn ));
107117 }
118+ } elseif (is_a ($ class , \Predis \Client::class, true )) {
119+ $ params ['scheme ' ] = isset ($ params ['host ' ]) ? 'tcp ' : 'unix ' ;
120+ $ params ['database ' ] = $ params ['dbindex ' ] ?: null ;
121+ $ params ['password ' ] = $ auth ;
122+ $ redis = new $ class ((new Factory ())->create ($ params ));
108123 } elseif (class_exists ($ class , false )) {
109- throw new InvalidArgumentException (sprintf ('"%s" is not a subclass of "Redis" ' , $ class ));
124+ throw new InvalidArgumentException (sprintf ('"%s" is not a subclass of "Redis" or "Predis\Client" ' , $ class ));
110125 } else {
111126 throw new InvalidArgumentException (sprintf ('Class "%s" does not exist ' , $ class ));
112127 }
@@ -139,24 +154,48 @@ protected function doFetch(array $ids)
139154 */
140155 protected function doHave ($ id )
141156 {
142- return $ this ->redis ->exists ($ id );
157+ return ( bool ) $ this ->redis ->exists ($ id );
143158 }
144159
145160 /**
146161 * {@inheritdoc}
147162 */
148163 protected function doClear ($ namespace )
149164 {
165+ // When using a native Redis cluster, clearing the cache always returns false.
166+
150167 // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
151168 // can hang your server when it is executed against large databases (millions of items).
152169 // Whenever you hit this scale, it is advised to deploy one Redis database per cache pool
153170 // instead of using namespaces, so that FLUSHDB is used instead.
154171 $ lua = "local keys=redis.call('KEYS',ARGV[1]..'*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end " ;
172+ $ hosts = array ($ this ->redis );
173+ $ evalArgs = array (array ($ namespace ), 0 );
155174
156- if (!isset ($ namespace [0 ])) {
157- $ this ->redis ->flushDb ();
158- } else {
159- $ this ->redis ->eval ($ lua , array ($ namespace ), 0 );
175+ if ($ this ->redis instanceof \Predis \Client) {
176+ $ evalArgs = array (0 , $ namespace );
177+
178+ $ connection = $ this ->redis ->getConnection ();
179+ if ($ connection instanceof PredisCluster) {
180+ foreach ($ connection as $ c ) {
181+ $ hosts [] = new \Predis \Client ($ c );
182+ }
183+ } elseif ($ connection instanceof RedisCluster) {
184+ return false ;
185+ }
186+ } elseif ($ this ->redis instanceof \RedisArray) {
187+ foreach ($ this ->redis ->_hosts () as $ host ) {
188+ $ hosts [] = $ this ->redis ->_instance ($ host );
189+ }
190+ } elseif ($ this ->redis instanceof \RedisCluster) {
191+ return false ;
192+ }
193+ foreach ($ hosts as $ host ) {
194+ if (!isset ($ namespace [0 ])) {
195+ $ host ->flushDb ();
196+ } else {
197+ $ host ->eval ($ lua , $ evalArgs [0 ], $ evalArgs [1 ]);
198+ }
160199 }
161200
162201 return true ;
@@ -194,12 +233,36 @@ protected function doSave(array $values, $lifetime)
194233 return $ failed ;
195234 }
196235 if ($ lifetime > 0 ) {
197- $ this ->redis ->multi (\Redis::PIPELINE );
198- foreach ($ serialized as $ id => $ value ) {
199- $ this ->redis ->setEx ($ id , $ lifetime , $ value );
200- }
201- if (!$ this ->redis ->exec ()) {
202- return false ;
236+ if ($ this ->redis instanceof \Predis \Client) {
237+ $ pipe = $ this ->redis ->pipeline ();
238+ foreach ($ serialized as $ id => $ value ) {
239+ $ pipe ->setEx ($ id , $ lifetime , $ value );
240+ }
241+ if (!$ pipe ->execute ()) {
242+ return false ;
243+ }
244+ } elseif ($ this ->redis instanceof \RedisArray) {
245+ $ redis = array ();
246+ foreach ($ serialized as $ id => $ value ) {
247+ if (!isset ($ redis [$ h = $ this ->redis ->_target ($ id )])) {
248+ $ redis [$ h ] = $ this ->redis ->_instance ($ h );
249+ $ redis [$ h ]->multi (\Redis::PIPELINE );
250+ }
251+ $ redis [$ h ]->setEx ($ id , $ lifetime , $ value );
252+ }
253+ foreach ($ redis as $ h ) {
254+ if (!$ h ->exec ()) {
255+ $ failed = false ;
256+ }
257+ }
258+ } else {
259+ $ pipe = $ this ->redis instanceof \Redis && $ this ->redis ->multi (\Redis::PIPELINE );
260+ foreach ($ serialized as $ id => $ value ) {
261+ $ this ->redis ->setEx ($ id , $ lifetime , $ value );
262+ }
263+ if ($ pipe && !$ this ->redis ->exec ()) {
264+ return false ;
265+ }
203266 }
204267 } elseif (!$ this ->redis ->mSet ($ serialized )) {
205268 return false ;
0 commit comments