11
11
12
12
namespace Symfony \Component \Cache \Adapter ;
13
13
14
+ use Predis \Connection \Factory ;
15
+ use Predis \Connection \Aggregate \PredisCluster ;
16
+ use Predis \Connection \Aggregate \RedisCluster ;
14
17
use Symfony \Component \Cache \Exception \InvalidArgumentException ;
15
18
16
19
/**
19
22
class RedisAdapter extends AbstractAdapter
20
23
{
21
24
private static $ defaultConnectionOptions = array (
22
- 'class ' => \Redis::class ,
25
+ 'class ' => null ,
23
26
'persistent ' => 0 ,
24
27
'timeout ' => 0 ,
25
28
'read_timeout ' => 0 ,
26
29
'retry_interval ' => 0 ,
27
30
);
28
31
private $ redis ;
29
32
30
- public function __construct (\ Redis $ redisClient , $ namespace = '' , $ defaultLifetime = 0 )
33
+ public function __construct ($ redisClient , $ namespace = '' , $ defaultLifetime = 0 )
31
34
{
32
35
parent ::__construct ($ namespace , $ defaultLifetime );
33
36
34
37
if (preg_match ('#[^-+_.A-Za-z0-9]# ' , $ namespace , $ match )) {
35
38
throw new InvalidArgumentException (sprintf ('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed. ' , $ match [0 ]));
36
39
}
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
+ }
37
47
$ this ->redis = $ redisClient ;
38
48
}
39
49
@@ -52,7 +62,7 @@ public function __construct(\Redis $redisClient, $namespace = '', $defaultLifeti
52
62
*
53
63
* @throws InvalidArgumentException When the DSN is invalid.
54
64
*
55
- * @return \Redis
65
+ * @return \Redis|\Predis\Client According to the "class" option.
56
66
*/
57
67
public static function createConnection ($ dsn , array $ options = array ())
58
68
{
@@ -86,7 +96,7 @@ public static function createConnection($dsn, array $options = array())
86
96
$ params += $ query ;
87
97
}
88
98
$ params += $ options + self ::$ defaultConnectionOptions ;
89
- $ class = $ params ['class ' ];
99
+ $ class = null === $ params [ ' class ' ] ? ( extension_loaded ( ' redis ' ) ? \Redis::class : \ Predis \Client::class) : $ params ['class ' ];
90
100
91
101
if (is_a ($ class , \Redis::class, true )) {
92
102
$ connect = empty ($ params ['persistent ' ]) ? 'connect ' : 'pconnect ' ;
@@ -105,8 +115,13 @@ public static function createConnection($dsn, array $options = array())
105
115
$ e = preg_replace ('/^ERR / ' , '' , $ redis ->getLastError ());
106
116
throw new InvalidArgumentException (sprintf ('Redis connection failed (%s): %s ' , $ e , $ dsn ));
107
117
}
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 ));
108
123
} 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 ));
110
125
} else {
111
126
throw new InvalidArgumentException (sprintf ('Class "%s" does not exist ' , $ class ));
112
127
}
@@ -139,24 +154,48 @@ protected function doFetch(array $ids)
139
154
*/
140
155
protected function doHave ($ id )
141
156
{
142
- return $ this ->redis ->exists ($ id );
157
+ return ( bool ) $ this ->redis ->exists ($ id );
143
158
}
144
159
145
160
/**
146
161
* {@inheritdoc}
147
162
*/
148
163
protected function doClear ($ namespace )
149
164
{
165
+ // When using a native Redis cluster, clearing the cache always returns false.
166
+
150
167
// As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
151
168
// can hang your server when it is executed against large databases (millions of items).
152
169
// Whenever you hit this scale, it is advised to deploy one Redis database per cache pool
153
170
// instead of using namespaces, so that FLUSHDB is used instead.
154
171
$ 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 );
155
174
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
+ }
160
199
}
161
200
162
201
return true ;
@@ -194,12 +233,36 @@ protected function doSave(array $values, $lifetime)
194
233
return $ failed ;
195
234
}
196
235
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
+ $ this ->redis ->multi (\Redis::PIPELINE );
260
+ foreach ($ serialized as $ id => $ value ) {
261
+ $ this ->redis ->setEx ($ id , $ lifetime , $ value );
262
+ }
263
+ if (!$ this ->redis ->exec ()) {
264
+ return false ;
265
+ }
203
266
}
204
267
} elseif (!$ this ->redis ->mSet ($ serialized )) {
205
268
return false ;
0 commit comments