Redis Object Cache - Version 1.0

Version Description

  • Initial release
Download this release

Release Info

Developer tillkruess
Plugin Icon 128x128 Redis Object Cache
Version 1.0
Comparing to
See all releases

Version 1.0

includes/admin-page.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <div class="wrap">
3
+
4
+ <h2><?php _e( 'Redis Object Cache', 'redis-cache' ); ?></h2>
5
+
6
+ <?php settings_errors(); ?>
7
+
8
+ <table class="form-table">
9
+
10
+ <tr valign="top">
11
+ <th scope="row"><?php _e( 'Object Cache Status', 'redis-cache' ); ?></th>
12
+ <td><code><?php echo $this->get_redis_status(); ?></code></td>
13
+ </tr>
14
+
15
+ <tr valign="top">
16
+ <th scope="row"><?php _e( 'Connection Parameters', 'redis-cache' ); ?></th>
17
+ <td>
18
+ <p>
19
+ <?php _e( 'Protocol:', 'redis-cache' ); ?> <code><?php echo strtoupper( esc_html( $this->get_redis_scheme() ) ); ?></code><br />
20
+ <?php _e( 'Host:', 'redis-cache' ); ?> <code><?php echo esc_html( $this->get_redis_host() ); ?></code><br />
21
+ <?php _e( 'Port:', 'redis-cache' ); ?> <code><?php echo esc_html( $this->get_redis_port() ); ?></code><br />
22
+ <?php _e( 'Database:', 'redis-cache' ); ?> <code><?php echo esc_html( $this->get_redis_database() ); ?></code><br />
23
+ <?php if ( ! is_null( $this->get_redis_password() ) ) : ?>
24
+ <?php _e( 'Password:', 'redis-cache' ); ?> <code><?php echo str_repeat( '*', strlen( $this->get_redis_password() ) ); ?></code>
25
+ <?php endif; ?>
26
+ </p>
27
+ </td>
28
+ </tr>
29
+
30
+ </table>
31
+
32
+ <p class="submit">
33
+ <?php if ( ! $this->object_cache_dropin_exists() ) : ?>
34
+ <a href="<?php echo wp_nonce_url( admin_url( add_query_arg( 'action', 'enable-cache', $this->admin_page ) ), 'enable-cache' ); ?>" class="button button-primary button-large"><?php _e( 'Enable Object Cache', 'redis-cache' ); ?></a>
35
+ <?php elseif ( $this->validate_object_cache_dropin() ) : ?>
36
+ <a href="<?php echo wp_nonce_url( admin_url( add_query_arg( 'action', 'disable-cache', $this->admin_page ) ), 'disable-cache' ); ?>" class="button button-secondary button-large"><?php _e( 'Disable Object Cache', 'redis-cache' ); ?></a>
37
+ <?php endif; ?>
38
+ </p>
39
+
40
+ </div>
includes/object-cache.php ADDED
@@ -0,0 +1,922 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Redis Object Cache
4
+ Plugin URI: http://wordpress.org/plugins/redis-cache/
5
+ Description: A Redis backend for the WordPress Object Cache based on the Predis client library for PHP.
6
+ Version: 1.0
7
+ Author: Till Krüss
8
+ Author URI: http://till.kruss.me/
9
+ License: GPLv3
10
+ License URI: http://www.gnu.org/licenses/gpl-3.0.html
11
+
12
+ Based on Eric Mann's and Erick Hitter's Redis Object Cache:
13
+ https://github.com/ericmann/Redis-Object-Cache
14
+
15
+ */
16
+
17
+ /**
18
+ * Adds a value to cache.
19
+ *
20
+ * If the specified key already exists, the value is not stored and the function
21
+ * returns false.
22
+ *
23
+ * @param string $key The key under which to store the value.
24
+ * @param mixed $value The value to store.
25
+ * @param string $group The group value appended to the $key.
26
+ * @param int $expiration The expiration time, defaults to 0.
27
+ *
28
+ * @global WP_Object_Cache $wp_object_cache
29
+ *
30
+ * @return bool Returns TRUE on success or FALSE on failure.
31
+ */
32
+ function wp_cache_add( $key, $value, $group = '', $expiration = 0 ) {
33
+ global $wp_object_cache;
34
+ return $wp_object_cache->add( $key, $value, $group, $expiration );
35
+ }
36
+
37
+ /**
38
+ * Closes the cache.
39
+ *
40
+ * This function has ceased to do anything since WordPress 2.5. The
41
+ * functionality was removed along with the rest of the persistent cache. This
42
+ * does not mean that plugins can't implement this function when they need to
43
+ * make sure that the cache is cleaned up after WordPress no longer needs it.
44
+ *
45
+ * @return bool Always returns True
46
+ */
47
+ function wp_cache_close() {
48
+ return true;
49
+ }
50
+
51
+ /**
52
+ * Decrement a numeric item's value.
53
+ *
54
+ * @param string $key The key under which to store the value.
55
+ * @param int $offset The amount by which to decrement the item's value.
56
+ * @param string $group The group value appended to the $key.
57
+ *
58
+ * @global WP_Object_Cache $wp_object_cache
59
+ *
60
+ * @return int|bool Returns item's new value on success or FALSE on failure.
61
+ */
62
+ function wp_cache_decr( $key, $offset = 1, $group = '' ) {
63
+ global $wp_object_cache;
64
+ return $wp_object_cache->decrement( $key, $offset, $group );
65
+ }
66
+
67
+ /**
68
+ * Remove the item from the cache.
69
+ *
70
+ * @param string $key The key under which to store the value.
71
+ * @param string $group The group value appended to the $key.
72
+ * @param int $time The amount of time the server will wait to delete the item in seconds.
73
+ *
74
+ * @global WP_Object_Cache $wp_object_cache
75
+ *
76
+ * @return bool Returns TRUE on success or FALSE on failure.
77
+ */
78
+ function wp_cache_delete( $key, $group = '', $time = 0 ) {
79
+ global $wp_object_cache;
80
+ return $wp_object_cache->delete( $key, $group, $time );
81
+ }
82
+
83
+ /**
84
+ * Invalidate all items in the cache.
85
+ *
86
+ * @param int $delay Number of seconds to wait before invalidating the items.
87
+ *
88
+ * @global WP_Object_Cache $wp_object_cache
89
+ *
90
+ * @return bool Returns TRUE on success or FALSE on failure.
91
+ */
92
+ function wp_cache_flush( $delay = 0 ) {
93
+ global $wp_object_cache;
94
+ return $wp_object_cache->flush( $delay );
95
+ }
96
+
97
+ /**
98
+ * Retrieve object from cache.
99
+ *
100
+ * Gets an object from cache based on $key and $group.
101
+ *
102
+ * @param string $key The key under which to store the value.
103
+ * @param string $group The group value appended to the $key.
104
+ *
105
+ * @global WP_Object_Cache $wp_object_cache
106
+ *
107
+ * @return bool|mixed Cached object value.
108
+ */
109
+ function wp_cache_get( $key, $group = '' ) {
110
+ global $wp_object_cache;
111
+ return $wp_object_cache->get( $key, $group );
112
+ }
113
+
114
+ /**
115
+ * Retrieve multiple values from cache.
116
+ *
117
+ * Gets multiple values from cache, including across multiple groups
118
+ *
119
+ * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) )
120
+ *
121
+ * Mirrors the Memcached Object Cache plugin's argument and return-value formats
122
+ *
123
+ * @param array $groups Array of groups and keys to retrieve
124
+ *
125
+ * @global WP_Object_Cache $wp_object_cache
126
+ *
127
+ * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys false
128
+ */
129
+ function wp_cache_get_multi( $groups ) {
130
+ global $wp_object_cache;
131
+ return $wp_object_cache->get_multi( $groups );
132
+ }
133
+
134
+ /**
135
+ * Increment a numeric item's value.
136
+ *
137
+ * @param string $key The key under which to store the value.
138
+ * @param int $offset The amount by which to increment the item's value.
139
+ * @param string $group The group value appended to the $key.
140
+ *
141
+ * @global WP_Object_Cache $wp_object_cache
142
+ *
143
+ * @return int|bool Returns item's new value on success or FALSE on failure.
144
+ */
145
+ function wp_cache_incr( $key, $offset = 1, $group = '' ) {
146
+ global $wp_object_cache;
147
+ return $wp_object_cache->increment( $key, $offset, $group );
148
+ }
149
+
150
+ /**
151
+ * Sets up Object Cache Global and assigns it.
152
+ *
153
+ * @global WP_Object_Cache $wp_object_cache WordPress Object Cache
154
+ *
155
+ * @return void
156
+ */
157
+ function wp_cache_init() {
158
+ global $wp_object_cache;
159
+ $wp_object_cache = new WP_Object_Cache();
160
+ }
161
+
162
+ /**
163
+ * Replaces a value in cache.
164
+ *
165
+ * This method is similar to "add"; however, is does not successfully set a value if
166
+ * the object's key is not already set in cache.
167
+ *
168
+ * @param string $key The key under which to store the value.
169
+ * @param mixed $value The value to store.
170
+ * @param string $group The group value appended to the $key.
171
+ * @param int $expiration The expiration time, defaults to 0.
172
+ *
173
+ * @global WP_Object_Cache $wp_object_cache
174
+ *
175
+ * @return bool Returns TRUE on success or FALSE on failure.
176
+ */
177
+ function wp_cache_replace( $key, $value, $group = '', $expiration = 0 ) {
178
+ global $wp_object_cache;
179
+ return $wp_object_cache->replace( $key, $value, $group, $expiration );
180
+ }
181
+
182
+ /**
183
+ * Sets a value in cache.
184
+ *
185
+ * The value is set whether or not this key already exists in Redis.
186
+ *
187
+ * @param string $key The key under which to store the value.
188
+ * @param mixed $value The value to store.
189
+ * @param string $group The group value appended to the $key.
190
+ * @param int $expiration The expiration time, defaults to 0.
191
+ *
192
+ * @global WP_Object_Cache $wp_object_cache
193
+ *
194
+ * @return bool Returns TRUE on success or FALSE on failure.
195
+ */
196
+ function wp_cache_set( $key, $value, $group = '', $expiration = 0 ) {
197
+ global $wp_object_cache;
198
+ return $wp_object_cache->set( $key, $value, $group, $expiration );
199
+ }
200
+
201
+ /**
202
+ * Switch the interal blog id.
203
+ *
204
+ * This changes the blog id used to create keys in blog specific groups.
205
+ *
206
+ * @param int $_blog_id Blog ID
207
+ *
208
+ * @global WP_Object_Cache $wp_object_cache
209
+ *
210
+ * @return bool
211
+ */
212
+ function wp_cache_switch_to_blog( $_blog_id ) {
213
+ global $wp_object_cache;
214
+ return $wp_object_cache->switch_to_blog( $_blog_id );
215
+ }
216
+
217
+ /**
218
+ * Adds a group or set of groups to the list of Redis groups.
219
+ *
220
+ * @param string|array $groups A group or an array of groups to add.
221
+ *
222
+ * @global WP_Object_Cache $wp_object_cache
223
+ *
224
+ * @return void
225
+ */
226
+ function wp_cache_add_global_groups( $groups ) {
227
+ global $wp_object_cache;
228
+ $wp_object_cache->add_global_groups( $groups );
229
+ }
230
+
231
+ /**
232
+ * Adds a group or set of groups to the list of non-Redis groups.
233
+ *
234
+ * @param string|array $groups A group or an array of groups to add.
235
+ *
236
+ * @global WP_Object_Cache $wp_object_cache
237
+ *
238
+ * @return void
239
+ */
240
+ function wp_cache_add_non_persistent_groups( $groups ) {
241
+ global $wp_object_cache;
242
+ $wp_object_cache->add_non_persistent_groups( $groups );
243
+ }
244
+
245
+ class WP_Object_Cache {
246
+
247
+ /**
248
+ * Holds the Redis client.
249
+ *
250
+ * @var Predis\Client
251
+ */
252
+ private $redis;
253
+
254
+ /**
255
+ * Track if Redis is available
256
+ *
257
+ * @var bool
258
+ */
259
+ private $redis_connected = false;
260
+
261
+ /**
262
+ * Holds the non-Redis objects.
263
+ *
264
+ * @var array
265
+ */
266
+ private $cache = array();
267
+
268
+ /**
269
+ * List of global groups.
270
+ *
271
+ * @var array
272
+ */
273
+ public $global_groups = array( 'users', 'userlogins', 'usermeta', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss' );
274
+
275
+ /**
276
+ * List of groups not saved to Redis.
277
+ *
278
+ * @var array
279
+ */
280
+ public $no_redis_groups = array( 'comment', 'counts' );
281
+
282
+ /**
283
+ * Prefix used for global groups.
284
+ *
285
+ * @var string
286
+ */
287
+ public $global_prefix = '';
288
+
289
+ /**
290
+ * Prefix used for non-global groups.
291
+ *
292
+ * @var string
293
+ */
294
+ public $blog_prefix = '';
295
+
296
+ /**
297
+ * Track how many requests were found in cache
298
+ *
299
+ * @var int
300
+ */
301
+ public $cache_hits = 0;
302
+
303
+ /**
304
+ * Track how may requests were not cached
305
+ *
306
+ * @var int
307
+ */
308
+ public $cache_misses = 0;
309
+
310
+ /**
311
+ * Instantiate the Redis class.
312
+ *
313
+ * Instantiates the Redis class.
314
+ *
315
+ * @param null $persistent_id To create an instance that persists between requests, use persistent_id to specify a unique ID for the instance.
316
+ */
317
+ public function __construct() {
318
+ global $blog_id, $table_prefix;
319
+
320
+ // General Redis settings
321
+ $redis = array(
322
+ 'scheme' => 'tcp',
323
+ 'host' => '127.0.0.1',
324
+ 'port' => 6379
325
+ );
326
+
327
+ if ( defined( 'WP_REDIS_SCHEME' ) ) {
328
+ $redis[ 'scheme' ] = WP_REDIS_SCHEME;
329
+ }
330
+
331
+ if ( defined( 'WP_REDIS_HOST' ) ) {
332
+ $redis[ 'host' ] = WP_REDIS_HOST;
333
+ }
334
+
335
+ if ( defined( 'WP_REDIS_PORT' ) ) {
336
+ $redis[ 'port' ] = WP_REDIS_PORT;
337
+ }
338
+
339
+ if ( defined( 'WP_REDIS_PASSWORD' ) ) {
340
+ $redis[ 'password' ] = WP_REDIS_PASSWORD;
341
+ }
342
+
343
+ if ( defined( 'WP_REDIS_DATABASE' ) ) {
344
+ $redis[ 'database' ] = WP_REDIS_DATABASE;
345
+ }
346
+
347
+ try {
348
+
349
+ if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) {
350
+ throw new Exception;
351
+ }
352
+
353
+ if ( ! realpath( dirname( __FILE__ ) . '/plugins/redis-cache/includes/predis.php' ) ) {
354
+ throw new Exception;
355
+ }
356
+
357
+ require_once dirname( __FILE__ ) . '/plugins/redis-cache/includes/predis.php';
358
+
359
+ Predis\Autoloader::register();
360
+
361
+ $this->redis = new Predis\Client( $redis );
362
+ $this->redis_connected = true;
363
+
364
+ } catch ( Exception $exception ) {
365
+
366
+ // When Redis is unavailable, fall back to the internal back by forcing all groups to be "no redis" groups
367
+ $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $this->global_groups ) );
368
+
369
+ $this->redis_connected = false;
370
+
371
+ }
372
+
373
+ /**
374
+ * This approach is borrowed from Sivel and Boren. Use the salt for easy cache invalidation and for
375
+ * multi single WP installs on the same server.
376
+ */
377
+ if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
378
+ define( 'WP_CACHE_KEY_SALT', '' );
379
+ }
380
+
381
+ // Assign global and blog prefixes for use with keys
382
+ if ( function_exists( 'is_multisite' ) ) {
383
+ $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
384
+ $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix ) . ':';
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Is Redis available?
390
+ *
391
+ * @return bool
392
+ */
393
+ public function redis_status() {
394
+ return $this->redis_connected;
395
+ }
396
+
397
+ /**
398
+ * Adds a value to cache.
399
+ *
400
+ * If the specified key already exists, the value is not stored and the function
401
+ * returns false.
402
+ *
403
+ * @param string $key The key under which to store the value.
404
+ * @param mixed $value The value to store.
405
+ * @param string $group The group value appended to the $key.
406
+ * @param int $expiration The expiration time, defaults to 0.
407
+ * @return bool Returns TRUE on success or FALSE on failure.
408
+ */
409
+ public function add( $key, $value, $group = 'default', $expiration = 0 ) {
410
+ return $this->add_or_replace( true, $key, $value, $group, $expiration );
411
+ }
412
+
413
+ /**
414
+ * Replace a value in the cache.
415
+ *
416
+ * If the specified key doesn't exist, the value is not stored and the function
417
+ * returns false.
418
+ *
419
+ * @param string $key The key under which to store the value.
420
+ * @param mixed $value The value to store.
421
+ * @param string $group The group value appended to the $key.
422
+ * @param int $expiration The expiration time, defaults to 0.
423
+ * @return bool Returns TRUE on success or FALSE on failure.
424
+ */
425
+ public function replace( $key, $value, $group = 'default', $expiration = 0 ) {
426
+ return $this->add_or_replace( false, $key, $value, $group, $expiration );
427
+ }
428
+
429
+ /**
430
+ * Add or replace a value in the cache.
431
+ *
432
+ * Add does not set the value if the key exists; replace does not replace if the value doesn't exist.
433
+ *
434
+ * @param bool $add True if should only add if value doesn't exist, false to only add when value already exists
435
+ * @param string $key The key under which to store the value.
436
+ * @param mixed $value The value to store.
437
+ * @param string $group The group value appended to the $key.
438
+ * @param int $expiration The expiration time, defaults to 0.
439
+ * @return bool Returns TRUE on success or FALSE on failure.
440
+ */
441
+ protected function add_or_replace( $add, $key, $value, $group = 'default', $expiration = 0 ) {
442
+ $derived_key = $this->build_key( $key, $group );
443
+
444
+ // If group is a non-Redis group, save to internal cache, not Redis
445
+ if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
446
+
447
+ // Check if conditions are right to continue
448
+ if (
449
+ ( $add && isset( $this->cache[ $derived_key ] ) ) ||
450
+ ( ! $add && ! isset( $this->cache[ $derived_key ] ) )
451
+ ) {
452
+ return false;
453
+ }
454
+
455
+ $this->add_to_internal_cache( $derived_key, $value );
456
+
457
+ return true;
458
+ }
459
+
460
+ // Check if conditions are right to continue
461
+ if (
462
+ ( $add && $this->redis->exists( $derived_key ) ) ||
463
+ ( ! $add && ! $this->redis->exists( $derived_key ) )
464
+ ) {
465
+ return false;
466
+ }
467
+
468
+ // Save to Redis
469
+ $expiration = abs( intval( $expiration ) );
470
+ if ( $expiration ) {
471
+ $result = $this->parse_predis_response( $this->redis->setex( $derived_key, $expiration, maybe_serialize( $value ) ) );
472
+ } else {
473
+ $result = $this->parse_predis_response( $this->redis->set( $derived_key, maybe_serialize( $value ) ) );
474
+ }
475
+
476
+ return $result;
477
+ }
478
+
479
+ /**
480
+ * Remove the item from the cache.
481
+ *
482
+ * @param string $key The key under which to store the value.
483
+ * @param string $group The group value appended to the $key.
484
+ * @return bool Returns TRUE on success or FALSE on failure.
485
+ */
486
+ public function delete( $key, $group = 'default' ) {
487
+ $derived_key = $this->build_key( $key, $group );
488
+
489
+ // Remove from no_redis_groups array
490
+ if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
491
+ if ( isset( $this->cache[ $derived_key ] ) ) {
492
+ unset( $this->cache[ $derived_key ] );
493
+
494
+ return true;
495
+ } else {
496
+ return false;
497
+ }
498
+ }
499
+
500
+ $result = $this->parse_predis_response( $this->redis->del( $derived_key ) );
501
+
502
+ unset( $this->cache[ $derived_key ] );
503
+
504
+ return $result;
505
+ }
506
+
507
+ /**
508
+ * Invalidate all items in the cache.
509
+ *
510
+ * @param int $delay Number of seconds to wait before invalidating the items.
511
+ * @return bool Returns TRUE on success or FALSE on failure.
512
+ */
513
+ public function flush( $delay = 0 ) {
514
+ $delay = abs( intval( $delay ) );
515
+ if ( $delay ) {
516
+ sleep( $delay );
517
+ }
518
+
519
+ $this->cache = array();
520
+
521
+ if ( $this->redis_status() ) {
522
+ $result = $this->parse_predis_response( $this->redis->flushdb() );
523
+ }
524
+
525
+ return $result;
526
+ }
527
+
528
+ /**
529
+ * Retrieve object from cache.
530
+ *
531
+ * Gets an object from cache based on $key and $group.
532
+ *
533
+ * @param string $key The key under which to store the value.
534
+ * @param string $group The group value appended to the $key.
535
+ * @return bool|mixed Cached object value.
536
+ */
537
+ public function get( $key, $group = 'default' ) {
538
+ $derived_key = $this->build_key( $key, $group );
539
+
540
+ if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
541
+ if ( isset( $this->cache[ $derived_key ] ) ) {
542
+ $this->cache_hits++;
543
+ return is_object( $this->cache[ $derived_key ] ) ? clone $this->cache[ $derived_key ] : $this->cache[ $derived_key ];
544
+ } else {
545
+ $this->cache_misses++;
546
+ return false;
547
+ }
548
+ }
549
+
550
+ if ( $this->redis->exists( $derived_key ) ) {
551
+ $this->cache_hits++;
552
+ $value = maybe_unserialize( $this->redis->get( $derived_key ) );
553
+ } else {
554
+ $this->cache_misses;
555
+ return false;
556
+ }
557
+
558
+ $this->add_to_internal_cache( $derived_key, $value );
559
+
560
+ return is_object( $value ) ? clone $value : $value;
561
+ }
562
+
563
+ /**
564
+ * Retrieve multiple values from cache.
565
+ *
566
+ * Gets multiple values from cache, including across multiple groups
567
+ *
568
+ * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) )
569
+ *
570
+ * Mirrors the Memcached Object Cache plugin's argument and return-value formats
571
+ *
572
+ * @param array $groups Array of groups and keys to retrieve
573
+ * @uses this::filter_redis_get_multi()
574
+ * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys null.
575
+ */
576
+ public function get_multi( $groups ) {
577
+ if ( empty( $groups ) || ! is_array( $groups ) ) {
578
+ return false;
579
+ }
580
+
581
+ // Retrieve requested caches and reformat results to mimic Memcached Object Cache's output
582
+ $cache = array();
583
+
584
+ foreach ( $groups as $group => $keys ) {
585
+ if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
586
+ foreach ( $keys as $key ) {
587
+ $cache[ $this->build_key( $key, $group ) ] = $this->get( $key, $group );
588
+ }
589
+ } else {
590
+ // Reformat arguments as expected by Redis
591
+ $derived_keys = array();
592
+ foreach ( $keys as $key ) {
593
+ $derived_keys[] = $this->build_key( $key, $group );
594
+ }
595
+
596
+ // Retrieve from cache in a single request
597
+ $group_cache = $this->redis->mget( $derived_keys );
598
+
599
+ // Build an array of values looked up, keyed by the derived cache key
600
+ $group_cache = array_combine( $derived_keys, $group_cache );
601
+
602
+ // Restores cached data to its original data type
603
+ $group_cache = array_map( array( $this, 'maybe_unserialize' ), $group_cache );
604
+
605
+ // Redis returns null for values not found in cache, but expected return value is false in this instance
606
+ $group_cache = array_map( array( $this, 'filter_redis_get_multi' ), $group_cache );
607
+
608
+ $cache = array_merge( $cache, $group_cache );
609
+ }
610
+ }
611
+
612
+ // Add to the internal cache the found values from Redis
613
+ foreach ( $cache as $key => $value ) {
614
+ if ( $value ) {
615
+ $this->cache_hits++;
616
+ $this->add_to_internal_cache( $key, $value );
617
+ } else {
618
+ $this->cache_misses++;
619
+ }
620
+ }
621
+
622
+ return $cache;
623
+ }
624
+
625
+ /**
626
+ * Sets a value in cache.
627
+ *
628
+ * The value is set whether or not this key already exists in Redis.
629
+ *
630
+ * @param string $key The key under which to store the value.
631
+ * @param mixed $value The value to store.
632
+ * @param string $group The group value appended to the $key.
633
+ * @param int $expiration The expiration time, defaults to 0.
634
+ * @return bool Returns TRUE on success or FALSE on failure.
635
+ */
636
+ public function set( $key, $value, $group = 'default', $expiration = 0 ) {
637
+ $derived_key = $this->build_key( $key, $group );
638
+
639
+ // If group is a non-Redis group, save to internal cache, not Redis
640
+ if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
641
+ $this->add_to_internal_cache( $derived_key, $value );
642
+
643
+ return true;
644
+ }
645
+
646
+ // Save to Redis
647
+ $expiration = abs( intval( $expiration ) );
648
+ if ( $expiration ) {
649
+ $result = $this->parse_predis_response( $this->redis->setex( $derived_key, $expiration, maybe_serialize( $value ) ) );
650
+ } else {
651
+ $result = $this->parse_predis_response( $this->redis->set( $derived_key, maybe_serialize( $value ) ) );
652
+ }
653
+
654
+ return $result;
655
+ }
656
+
657
+ /**
658
+ * Increment a Redis counter by the amount specified
659
+ *
660
+ * @param string $key
661
+ * @param int $offset
662
+ * @param string $group
663
+ * @return bool
664
+ */
665
+ public function increment( $key, $offset = 1, $group = 'default' ) {
666
+ $derived_key = $this->build_key( $key, $group );
667
+ $offset = (int) $offset;
668
+
669
+ // If group is a non-Redis group, save to internal cache, not Redis
670
+ if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
671
+ $value = $this->get_from_internal_cache( $derived_key, $group );
672
+ $value += $offset;
673
+ $this->add_to_internal_cache( $derived_key, $value );
674
+
675
+ return true;
676
+ }
677
+
678
+ // Save to Redis
679
+ $result = $this->parse_predis_response( $this->redis->incrBy( $derived_key, $offset ) );
680
+
681
+ $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) );
682
+
683
+ return $result;
684
+ }
685
+
686
+ /**
687
+ * Decrement a Redis counter by the amount specified
688
+ *
689
+ * @param string $key
690
+ * @param int $offset
691
+ * @param string $group
692
+ * @return bool
693
+ */
694
+ public function decrement( $key, $offset = 1, $group = 'default' ) {
695
+ $derived_key = $this->build_key( $key, $group );
696
+ $offset = (int) $offset;
697
+
698
+ // If group is a non-Redis group, save to internal cache, not Redis
699
+ if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
700
+ $value = $this->get_from_internal_cache( $derived_key, $group );
701
+ $value -= $offset;
702
+ $this->add_to_internal_cache( $derived_key, $value );
703
+
704
+ return true;
705
+ }
706
+
707
+ // Save to Redis
708
+ $result = $this->parse_predis_response( $this->redis->decrBy( $derived_key, $offset ) );
709
+
710
+ $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) );
711
+
712
+ return $result;
713
+ }
714
+
715
+ /**
716
+ * Render data about current cache requests
717
+ *
718
+ * @return string
719
+ */
720
+ public function stats() { ?>
721
+
722
+ <p>
723
+ <strong><?php $this->_i18n( '_e', 'Cache Status:' ); ?></strong> <?php echo $this->redis_status() ? $this->_i18n( '__', 'Connected' ) : $this->_i18n( '__', 'Not connected' ); ?><br />
724
+ <strong><?php $this->_i18n( '_e', 'Cache Hits:' ); ?></strong> <?php echo $this->_i18n( 'number_format_i18n', $this->cache_hits, false ); ?><br />
725
+ <strong><?php $this->_i18n( '_e', 'Cache Misses:' ); ?></strong> <?php echo $this->_i18n( 'number_format_i18n', $this->cache_misses, false ); ?>
726
+ </p>
727
+
728
+ <p><strong><?php $this->_i18n( '_e', 'Caches Retrieved:' ); ?></strong></p>
729
+
730
+ <ul>
731
+ <li><em><?php $this->_i18n( '_e', 'prefix:group:key - size in kilobytes' ); ?></em></li>
732
+ <?php foreach ( $this->cache as $group => $cache ) : ?>
733
+ <li><?php printf( $this->_i18n( '__', '%s - %s %s' ), $this->_esc_html( $group, false ), $this->_i18n( 'number_format_i18n', strlen( serialize( $cache ) ) / 1024, false, 2 ), $this->_i18n( '__', 'kb' ) ); ?></li>
734
+ <?php endforeach; ?>
735
+ </ul><?php
736
+
737
+ }
738
+
739
+ /**
740
+ * Builds a key for the cached object using the blog_id, key, and group values.
741
+ *
742
+ * @author Ryan Boren This function is inspired by the original WP Memcached Object cache.
743
+ * @link http://wordpress.org/extend/plugins/memcached/
744
+ *
745
+ * @param string $key The key under which to store the value.
746
+ * @param string $group The group value appended to the $key.
747
+ *
748
+ * @return string
749
+ */
750
+ public function build_key( $key, $group = 'default' ) {
751
+ if ( empty( $group ) ) {
752
+ $group = 'default';
753
+ }
754
+
755
+ if ( false !== array_search( $group, $this->global_groups ) ) {
756
+ $prefix = $this->global_prefix;
757
+ } else {
758
+ $prefix = $this->blog_prefix;
759
+ }
760
+
761
+ return preg_replace( '/\s+/', '', WP_CACHE_KEY_SALT . "$prefix$group:$key" );
762
+ }
763
+
764
+ /**
765
+ * Convert data types when using Redis MGET
766
+ *
767
+ * When requesting multiple keys, those not found in cache are assigned the value null upon return.
768
+ * Expected value in this case is false, so we convert
769
+ *
770
+ * @param string $value Value to possibly convert
771
+ * @return string Converted value
772
+ */
773
+ protected function filter_redis_get_multi( $value ) {
774
+ if ( is_null( $value ) ) {
775
+ $value = false;
776
+ }
777
+
778
+ return $value;
779
+ }
780
+
781
+ /**
782
+ * Convert the response fro Predis into something meaningful
783
+ *
784
+ * @param mixed $response
785
+ * @return mixed
786
+ */
787
+ protected function parse_predis_response( $response ) {
788
+ if ( is_bool( $response ) ) {
789
+ return $response;
790
+ }
791
+
792
+ if ( is_numeric( $response ) ) {
793
+ return (bool) $response;
794
+ }
795
+
796
+ if ( is_object( $response ) && method_exists( $response, 'getPayload' ) ) {
797
+ return 'OK' === $response->getPayload();
798
+ }
799
+
800
+ return false;
801
+ }
802
+
803
+ /**
804
+ * Simple wrapper for saving object to the internal cache.
805
+ *
806
+ * @param string $derived_key Key to save value under.
807
+ * @param mixed $value Object value.
808
+ */
809
+ public function add_to_internal_cache( $derived_key, $value ) {
810
+ $this->cache[ $derived_key ] = $value;
811
+ }
812
+
813
+ /**
814
+ * Get a value specifically from the internal, run-time cache, not Redis.
815
+ *
816
+ * @param int|string $key Key value.
817
+ * @param int|string $group Group that the value belongs to.
818
+ *
819
+ * @return bool|mixed Value on success; false on failure.
820
+ */
821
+ public function get_from_internal_cache( $key, $group ) {
822
+ $derived_key = $this->build_key( $key, $group );
823
+
824
+ if ( isset( $this->cache[ $derived_key ] ) ) {
825
+ return $this->cache[ $derived_key ];
826
+ }
827
+
828
+ return false;
829
+ }
830
+
831
+ /**
832
+ * In multisite, switch blog prefix when switching blogs
833
+ *
834
+ * @param int $_blog_id
835
+ * @return bool
836
+ */
837
+ public function switch_to_blog( $_blog_id ) {
838
+ if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) {
839
+ return false;
840
+ }
841
+
842
+ $this->blog_prefix = $_blog_id . ':';
843
+ return true;
844
+ }
845
+
846
+ /**
847
+ * Sets the list of global groups.
848
+ *
849
+ * @param array $groups List of groups that are global.
850
+ */
851
+ public function add_global_groups( $groups ) {
852
+ $groups = (array) $groups;
853
+
854
+ if ( $this->redis_status() ) {
855
+ $this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
856
+ } else {
857
+ $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $groups ) );
858
+ }
859
+ }
860
+
861
+ /**
862
+ * Sets the list of groups not to be cached by Redis.
863
+ *
864
+ * @param array $groups List of groups that are to be ignored.
865
+ */
866
+ public function add_non_persistent_groups( $groups ) {
867
+ $groups = (array) $groups;
868
+
869
+ $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $groups ) );
870
+ }
871
+
872
+ /**
873
+ * Run a value through an i18n WP function if it exists. Otherwise, just rpass through.
874
+ *
875
+ * Since this class may run befor the i18n methods are loaded in WP, we'll make sure they
876
+ * exist before using them. Most require a text domain, some don't, so the second param allows
877
+ * specifiying which type is being called.
878
+ *
879
+ * @param string $method The WP method to pass the string through if it exists.
880
+ * @param string $string The string to internationalize.
881
+ * @param bool $domain Whether or not to pass the text domain to the method as well.
882
+ * @param mixed $params Any extra param or array of params to send to the method.
883
+ * @return string The maybe internationalaized string.
884
+ */
885
+ protected function _i18n( $method, $string, $domain = true, $params = array() ) {
886
+ // Pass through if the method doesn't exist.
887
+ if ( ! function_exists( $method ) ) {
888
+ return $string;
889
+ }
890
+ // Allow non-array single extra values
891
+ if ( ! is_array( $params ) ) {
892
+ $params = array( $params );
893
+ }
894
+ // Add domain param if needed.
895
+ if ( (bool) $domain ) {
896
+ array_unshift( $params, 'redis-cache' );
897
+ }
898
+ // Add the string
899
+ array_unshift( $params, $string );
900
+
901
+ return call_user_func_array( $method, $params );
902
+ }
903
+
904
+ /**
905
+ * Try to escape any HTML from output, if not available, strip tags.
906
+ *
907
+ * This helper ensures invalid HTML output is escaped with esc_html if possible. If not,
908
+ * it will use the native strip_tags instead to simply remove them. This is needed since
909
+ * in some circumstances this may be loaded before esc_html is available.
910
+ *
911
+ * @param string $string The string to escape or strip.
912
+ * @return string The safe string for output.
913
+ */
914
+ public function _esc_html( $string ) {
915
+ if ( function_exists( 'esc_html' ) ) {
916
+ return esc_html( $string );
917
+ } else {
918
+ return strip_tags( $string );
919
+ }
920
+ }
921
+
922
+ }
includes/predis.php ADDED
@@ -0,0 +1,15143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Predis package.
5
+ *
6
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Predis\Command;
13
+
14
+ use InvalidArgumentException;
15
+
16
+ /**
17
+ * Defines an abstraction representing a Redis command.
18
+ *
19
+ * @author Daniele Alessandri <suppakilla@gmail.com>
20
+ */
21
+ interface CommandInterface
22
+ {
23
+ /**
24
+ * Returns the ID of the Redis command. By convention, command identifiers
25
+ * must always be uppercase.
26
+ *
27
+ * @return string
28
+ */
29
+ public function getId();
30
+
31
+ /**
32
+ * Assign the specified slot to the command for clustering distribution.
33
+ *
34
+ * @param int $slot Slot ID.
35
+ */
36
+ public function setSlot($slot);
37
+
38
+ /**
39
+ * Returns the assigned slot of the command for clustering distribution.
40
+ *
41
+ * @return int|null
42
+ */
43
+ public function getSlot();
44
+
45
+ /**
46
+ * Sets the arguments for the command.
47
+ *
48
+ * @param array $arguments List of arguments.
49
+ */
50
+ public function setArguments(array $arguments);
51
+
52
+ /**
53
+ * Sets the raw arguments for the command without processing them.
54
+ *
55
+ * @param array $arguments List of arguments.
56
+ */
57
+ public function setRawArguments(array $arguments);
58
+
59
+ /**
60
+ * Gets the arguments of the command.
61
+ *
62
+ * @return array
63
+ */
64
+ public function getArguments();
65
+
66
+ /**
67
+ * Gets the argument of the command at the specified index.
68
+ *
69
+ * @param int $index Index of the desired argument.
70
+ *
71
+ * @return mixed|null
72
+ */
73
+ public function getArgument($index);
74
+
75
+ /**
76
+ * Parses a raw response and returns a PHP object.
77
+ *
78
+ * @param string $data Binary string containing the whole response.
79
+ *
80
+ * @return mixed
81
+ */
82
+ public function parseResponse($data);
83
+ }
84
+
85
+ /**
86
+ * Base class for Redis commands.
87
+ *
88
+ * @author Daniele Alessandri <suppakilla@gmail.com>
89
+ */
90
+ abstract class Command implements CommandInterface
91
+ {
92
+ private $slot;
93
+ private $arguments = array();
94
+
95
+ /**
96
+ * Returns a filtered array of the arguments.
97
+ *
98
+ * @param array $arguments List of arguments.
99
+ *
100
+ * @return array
101
+ */
102
+ protected function filterArguments(array $arguments)
103
+ {
104
+ return $arguments;
105
+ }
106
+
107
+ /**
108
+ * {@inheritdoc}
109
+ */
110
+ public function setArguments(array $arguments)
111
+ {
112
+ $this->arguments = $this->filterArguments($arguments);
113
+ unset($this->slot);
114
+ }
115
+
116
+ /**
117
+ * {@inheritdoc}
118
+ */
119
+ public function setRawArguments(array $arguments)
120
+ {
121
+ $this->arguments = $arguments;
122
+ unset($this->slot);
123
+ }
124
+
125
+ /**
126
+ * {@inheritdoc}
127
+ */
128
+ public function getArguments()
129
+ {
130
+ return $this->arguments;
131
+ }
132
+
133
+ /**
134
+ * {@inheritdoc}
135
+ */
136
+ public function getArgument($index)
137
+ {
138
+ if (isset($this->arguments[$index])) {
139
+ return $this->arguments[$index];
140
+ }
141
+ }
142
+
143
+ /**
144
+ * {@inheritdoc}
145
+ */
146
+ public function setSlot($slot)
147
+ {
148
+ $this->slot = $slot;
149
+ }
150
+
151
+ /**
152
+ * {@inheritdoc}
153
+ */
154
+ public function getSlot()
155
+ {
156
+ if (isset($this->slot)) {
157
+ return $this->slot;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * {@inheritdoc}
163
+ */
164
+ public function parseResponse($data)
165
+ {
166
+ return $data;
167
+ }
168
+
169
+ /**
170
+ * Normalizes the arguments array passed to a Redis command.
171
+ *
172
+ * @param array $arguments Arguments for a command.
173
+ *
174
+ * @return array
175
+ */
176
+ public static function normalizeArguments(array $arguments)
177
+ {
178
+ if (count($arguments) === 1 && is_array($arguments[0])) {
179
+ return $arguments[0];
180
+ }
181
+
182
+ return $arguments;
183
+ }
184
+
185
+ /**
186
+ * Normalizes the arguments array passed to a variadic Redis command.
187
+ *
188
+ * @param array $arguments Arguments for a command.
189
+ *
190
+ * @return array
191
+ */
192
+ public static function normalizeVariadic(array $arguments)
193
+ {
194
+ if (count($arguments) === 2 && is_array($arguments[1])) {
195
+ return array_merge(array($arguments[0]), $arguments[1]);
196
+ }
197
+
198
+ return $arguments;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * @link http://redis.io/commands/zrange
204
+ * @author Daniele Alessandri <suppakilla@gmail.com>
205
+ */
206
+ class ZSetRange extends Command
207
+ {
208
+ /**
209
+ * {@inheritdoc}
210
+ */
211
+ public function getId()
212
+ {
213
+ return 'ZRANGE';
214
+ }
215
+
216
+ /**
217
+ * {@inheritdoc}
218
+ */
219
+ protected function filterArguments(array $arguments)
220
+ {
221
+ if (count($arguments) === 4) {
222
+ $lastType = gettype($arguments[3]);
223
+
224
+ if ($lastType === 'string' && strtoupper($arguments[3]) === 'WITHSCORES') {
225
+ // Used for compatibility with older versions
226
+ $arguments[3] = array('WITHSCORES' => true);
227
+ $lastType = 'array';
228
+ }
229
+
230
+ if ($lastType === 'array') {
231
+ $options = $this->prepareOptions(array_pop($arguments));
232
+
233
+ return array_merge($arguments, $options);
234
+ }
235
+ }
236
+
237
+ return $arguments;
238
+ }
239
+
240
+ /**
241
+ * Returns a list of options and modifiers compatible with Redis.
242
+ *
243
+ * @param array $options List of options.
244
+ *
245
+ * @return array
246
+ */
247
+ protected function prepareOptions($options)
248
+ {
249
+ $opts = array_change_key_case($options, CASE_UPPER);
250
+ $finalizedOpts = array();
251
+
252
+ if (!empty($opts['WITHSCORES'])) {
253
+ $finalizedOpts[] = 'WITHSCORES';
254
+ }
255
+
256
+ return $finalizedOpts;
257
+ }
258
+
259
+ /**
260
+ * Checks for the presence of the WITHSCORES modifier.
261
+ *
262
+ * @return bool
263
+ */
264
+ protected function withScores()
265
+ {
266
+ $arguments = $this->getArguments();
267
+
268
+ if (count($arguments) < 4) {
269
+ return false;
270
+ }
271
+
272
+ return strtoupper($arguments[3]) === 'WITHSCORES';
273
+ }
274
+
275
+ /**
276
+ * {@inheritdoc}
277
+ */
278
+ public function parseResponse($data)
279
+ {
280
+ if ($this->withScores()) {
281
+ $result = array();
282
+
283
+ for ($i = 0; $i < count($data); $i++) {
284
+ $result[$data[$i]] = $data[++$i];
285
+ }
286
+
287
+ return $result;
288
+ }
289
+
290
+ return $data;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * @link http://redis.io/commands/sinter
296
+ * @author Daniele Alessandri <suppakilla@gmail.com>
297
+ */
298
+ class SetIntersection extends Command
299
+ {
300
+ /**
301
+ * {@inheritdoc}
302
+ */
303
+ public function getId()
304
+ {
305
+ return 'SINTER';
306
+ }
307
+
308
+ /**
309
+ * {@inheritdoc}
310
+ */
311
+ protected function filterArguments(array $arguments)
312
+ {
313
+ return self::normalizeArguments($arguments);
314
+ }
315
+ }
316
+
317
+ /**
318
+ * @link http://redis.io/commands/eval
319
+ * @author Daniele Alessandri <suppakilla@gmail.com>
320
+ */
321
+ class ServerEval extends Command
322
+ {
323
+ /**
324
+ * {@inheritdoc}
325
+ */
326
+ public function getId()
327
+ {
328
+ return 'EVAL';
329
+ }
330
+
331
+ /**
332
+ * Calculates the SHA1 hash of the body of the script.
333
+ *
334
+ * @return string SHA1 hash.
335
+ */
336
+ public function getScriptHash()
337
+ {
338
+ return sha1($this->getArgument(0));
339
+ }
340
+ }
341
+
342
+ /**
343
+ * @link http://redis.io/commands/sinterstore
344
+ * @author Daniele Alessandri <suppakilla@gmail.com>
345
+ */
346
+ class SetIntersectionStore extends Command
347
+ {
348
+ /**
349
+ * {@inheritdoc}
350
+ */
351
+ public function getId()
352
+ {
353
+ return 'SINTERSTORE';
354
+ }
355
+
356
+ /**
357
+ * {@inheritdoc}
358
+ */
359
+ protected function filterArguments(array $arguments)
360
+ {
361
+ if (count($arguments) === 2 && is_array($arguments[1])) {
362
+ return array_merge(array($arguments[0]), $arguments[1]);
363
+ }
364
+
365
+ return $arguments;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * @link http://redis.io/commands/rpush
371
+ * @author Daniele Alessandri <suppakilla@gmail.com>
372
+ */
373
+ class ListPushTail extends Command
374
+ {
375
+ /**
376
+ * {@inheritdoc}
377
+ */
378
+ public function getId()
379
+ {
380
+ return 'RPUSH';
381
+ }
382
+
383
+ /**
384
+ * {@inheritdoc}
385
+ */
386
+ protected function filterArguments(array $arguments)
387
+ {
388
+ return self::normalizeVariadic($arguments);
389
+ }
390
+ }
391
+
392
+ /**
393
+ * @link http://redis.io/commands/ttl
394
+ * @author Daniele Alessandri <suppakilla@gmail.com>
395
+ */
396
+ class KeyTimeToLive extends Command
397
+ {
398
+ /**
399
+ * {@inheritdoc}
400
+ */
401
+ public function getId()
402
+ {
403
+ return 'TTL';
404
+ }
405
+ }
406
+
407
+ /**
408
+ * @link http://redis.io/commands/expireat
409
+ * @author Daniele Alessandri <suppakilla@gmail.com>
410
+ */
411
+ class KeyExpireAt extends Command
412
+ {
413
+ /**
414
+ * {@inheritdoc}
415
+ */
416
+ public function getId()
417
+ {
418
+ return 'EXPIREAT';
419
+ }
420
+
421
+ /**
422
+ * {@inheritdoc}
423
+ */
424
+ public function parseResponse($data)
425
+ {
426
+ return (bool) $data;
427
+ }
428
+ }
429
+
430
+ /**
431
+ * @link http://redis.io/commands/rename
432
+ * @author Daniele Alessandri <suppakilla@gmail.com>
433
+ */
434
+ class KeyRename extends Command
435
+ {
436
+ /**
437
+ * {@inheritdoc}
438
+ */
439
+ public function getId()
440
+ {
441
+ return 'RENAME';
442
+ }
443
+ }
444
+
445
+ /**
446
+ * @link http://redis.io/commands/subscribe
447
+ * @author Daniele Alessandri <suppakilla@gmail.com>
448
+ */
449
+ class PubSubSubscribe extends Command
450
+ {
451
+ /**
452
+ * {@inheritdoc}
453
+ */
454
+ public function getId()
455
+ {
456
+ return 'SUBSCRIBE';
457
+ }
458
+
459
+ /**
460
+ * {@inheritdoc}
461
+ */
462
+ protected function filterArguments(array $arguments)
463
+ {
464
+ return self::normalizeArguments($arguments);
465
+ }
466
+ }
467
+
468
+ /**
469
+ * @link http://redis.io/commands/blpop
470
+ * @author Daniele Alessandri <suppakilla@gmail.com>
471
+ */
472
+ class ListPopFirstBlocking extends Command
473
+ {
474
+ /**
475
+ * {@inheritdoc}
476
+ */
477
+ public function getId()
478
+ {
479
+ return 'BLPOP';
480
+ }
481
+
482
+ /**
483
+ * {@inheritdoc}
484
+ */
485
+ protected function filterArguments(array $arguments)
486
+ {
487
+ if (count($arguments) === 2 && is_array($arguments[0])) {
488
+ list($arguments, $timeout) = $arguments;
489
+ array_push($arguments, $timeout);
490
+ }
491
+
492
+ return $arguments;
493
+ }
494
+ }
495
+
496
+ /**
497
+ * @link http://redis.io/commands/info
498
+ * @author Daniele Alessandri <suppakilla@gmail.com>
499
+ */
500
+ class ServerInfo extends Command
501
+ {
502
+ /**
503
+ * {@inheritdoc}
504
+ */
505
+ public function getId()
506
+ {
507
+ return 'INFO';
508
+ }
509
+
510
+ /**
511
+ * {@inheritdoc}
512
+ */
513
+ public function parseResponse($data)
514
+ {
515
+ $info = array();
516
+ $infoLines = preg_split('/\r?\n/', $data);
517
+
518
+ foreach ($infoLines as $row) {
519
+ if (strpos($row, ':') === false) {
520
+ continue;
521
+ }
522
+
523
+ list($k, $v) = $this->parseRow($row);
524
+ $info[$k] = $v;
525
+ }
526
+
527
+ return $info;
528
+ }
529
+
530
+ /**
531
+ * Parses a single row of the response and returns the key-value pair.
532
+ *
533
+ * @param string $row Single row of the response.
534
+ *
535
+ * @return array
536
+ */
537
+ protected function parseRow($row)
538
+ {
539
+ list($k, $v) = explode(':', $row, 2);
540
+
541
+ if (preg_match('/^db\d+$/', $k)) {
542
+ $v = $this->parseDatabaseStats($v);
543
+ }
544
+
545
+ return array($k, $v);
546
+ }
547
+
548
+ /**
549
+ * Extracts the statistics of each logical DB from the string buffer.
550
+ *
551
+ * @param string $str Response buffer.
552
+ *
553
+ * @return array
554
+ */
555
+ protected function parseDatabaseStats($str)
556
+ {
557
+ $db = array();
558
+
559
+ foreach (explode(',', $str) as $dbvar) {
560
+ list($dbvk, $dbvv) = explode('=', $dbvar);
561
+ $db[trim($dbvk)] = $dbvv;
562
+ }
563
+
564
+ return $db;
565
+ }
566
+
567
+ /**
568
+ * Parses the response and extracts the allocation statistics.
569
+ *
570
+ * @param string $str Response buffer.
571
+ *
572
+ * @return array
573
+ */
574
+ protected function parseAllocationStats($str)
575
+ {
576
+ $stats = array();
577
+
578
+ foreach (explode(',', $str) as $kv) {
579
+ @list($size, $objects, $extra) = explode('=', $kv);
580
+
581
+ // hack to prevent incorrect values when parsing the >=256 key
582
+ if (isset($extra)) {
583
+ $size = ">=$objects";
584
+ $objects = $extra;
585
+ }
586
+
587
+ $stats[$size] = $objects;
588
+ }
589
+
590
+ return $stats;
591
+ }
592
+ }
593
+
594
+ /**
595
+ * @link http://redis.io/commands/zrangebyscore
596
+ * @author Daniele Alessandri <suppakilla@gmail.com>
597
+ */
598
+ class ZSetRangeByScore extends ZSetRange
599
+ {
600
+ /**
601
+ * {@inheritdoc}
602
+ */
603
+ public function getId()
604
+ {
605
+ return 'ZRANGEBYSCORE';
606
+ }
607
+
608
+ /**
609
+ * {@inheritdoc}
610
+ */
611
+ protected function prepareOptions($options)
612
+ {
613
+ $opts = array_change_key_case($options, CASE_UPPER);
614
+ $finalizedOpts = array();
615
+
616
+ if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
617
+ $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
618
+
619
+ $finalizedOpts[] = 'LIMIT';
620
+ $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
621
+ $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
622
+ }
623
+
624
+ return array_merge($finalizedOpts, parent::prepareOptions($options));
625
+ }
626
+
627
+ /**
628
+ * {@inheritdoc}
629
+ */
630
+ protected function withScores()
631
+ {
632
+ $arguments = $this->getArguments();
633
+
634
+ for ($i = 3; $i < count($arguments); $i++) {
635
+ switch (strtoupper($arguments[$i])) {
636
+ case 'WITHSCORES':
637
+ return true;
638
+
639
+ case 'LIMIT':
640
+ $i += 2;
641
+ break;
642
+ }
643
+ }
644
+
645
+ return false;
646
+ }
647
+ }
648
+
649
+ /**
650
+ * @link http://redis.io/commands/zunionstore
651
+ * @author Daniele Alessandri <suppakilla@gmail.com>
652
+ */
653
+ class ZSetUnionStore extends Command
654
+ {
655
+ /**
656
+ * {@inheritdoc}
657
+ */
658
+ public function getId()
659
+ {
660
+ return 'ZUNIONSTORE';
661
+ }
662
+
663
+ /**
664
+ * {@inheritdoc}
665
+ */
666
+ protected function filterArguments(array $arguments)
667
+ {
668
+ $options = array();
669
+ $argc = count($arguments);
670
+
671
+ if ($argc > 2 && is_array($arguments[$argc - 1])) {
672
+ $options = $this->prepareOptions(array_pop($arguments));
673
+ }
674
+
675
+ if (is_array($arguments[1])) {
676
+ $arguments = array_merge(
677
+ array($arguments[0], count($arguments[1])),
678
+ $arguments[1]
679
+ );
680
+ }
681
+
682
+ return array_merge($arguments, $options);
683
+ }
684
+
685
+ /**
686
+ * Returns a list of options and modifiers compatible with Redis.
687
+ *
688
+ * @param array $options List of options.
689
+ *
690
+ * @return array
691
+ */
692
+ private function prepareOptions($options)
693
+ {
694
+ $opts = array_change_key_case($options, CASE_UPPER);
695
+ $finalizedOpts = array();
696
+
697
+ if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) {
698
+ $finalizedOpts[] = 'WEIGHTS';
699
+
700
+ foreach ($opts['WEIGHTS'] as $weight) {
701
+ $finalizedOpts[] = $weight;
702
+ }
703
+ }
704
+
705
+ if (isset($opts['AGGREGATE'])) {
706
+ $finalizedOpts[] = 'AGGREGATE';
707
+ $finalizedOpts[] = $opts['AGGREGATE'];
708
+ }
709
+
710
+ return $finalizedOpts;
711
+ }
712
+ }
713
+
714
+ /**
715
+ * @link http://redis.io/commands/mset
716
+ * @author Daniele Alessandri <suppakilla@gmail.com>
717
+ */
718
+ class StringSetMultiple extends Command
719
+ {
720
+ /**
721
+ * {@inheritdoc}
722
+ */
723
+ public function getId()
724
+ {
725
+ return 'MSET';
726
+ }
727
+
728
+ /**
729
+ * {@inheritdoc}
730
+ */
731
+ protected function filterArguments(array $arguments)
732
+ {
733
+ if (count($arguments) === 1 && is_array($arguments[0])) {
734
+ $flattenedKVs = array();
735
+ $args = $arguments[0];
736
+
737
+ foreach ($args as $k => $v) {
738
+ $flattenedKVs[] = $k;
739
+ $flattenedKVs[] = $v;
740
+ }
741
+
742
+ return $flattenedKVs;
743
+ }
744
+
745
+ return $arguments;
746
+ }
747
+ }
748
+
749
+ /**
750
+ * @link http://redis.io/commands/setex
751
+ * @author Daniele Alessandri <suppakilla@gmail.com>
752
+ */
753
+ class StringSetExpire extends Command
754
+ {
755
+ /**
756
+ * {@inheritdoc}
757
+ */
758
+ public function getId()
759
+ {
760
+ return 'SETEX';
761
+ }
762
+ }
763
+
764
+ /**
765
+ * @link http://redis.io/commands/expire
766
+ * @author Daniele Alessandri <suppakilla@gmail.com>
767
+ */
768
+ class KeyExpire extends Command
769
+ {
770
+ /**
771
+ * {@inheritdoc}
772
+ */
773
+ public function getId()
774
+ {
775
+ return 'EXPIRE';
776
+ }
777
+
778
+ /**
779
+ * {@inheritdoc}
780
+ */
781
+ public function parseResponse($data)
782
+ {
783
+ return (bool) $data;
784
+ }
785
+ }
786
+
787
+ /**
788
+ * @link http://redis.io/commands/unsubscribe
789
+ * @author Daniele Alessandri <suppakilla@gmail.com>
790
+ */
791
+ class PubSubUnsubscribe extends Command
792
+ {
793
+ /**
794
+ * {@inheritdoc}
795
+ */
796
+ public function getId()
797
+ {
798
+ return 'UNSUBSCRIBE';
799
+ }
800
+
801
+ /**
802
+ * {@inheritdoc}
803
+ */
804
+ protected function filterArguments(array $arguments)
805
+ {
806
+ return self::normalizeArguments($arguments);
807
+ }
808
+ }
809
+
810
+ /**
811
+ * @link http://redis.io/commands/evalsha
812
+ * @author Daniele Alessandri <suppakilla@gmail.com>
813
+ */
814
+ class ServerEvalSHA extends ServerEval
815
+ {
816
+ /**
817
+ * {@inheritdoc}
818
+ */
819
+ public function getId()
820
+ {
821
+ return 'EVALSHA';
822
+ }
823
+
824
+ /**
825
+ * Returns the SHA1 hash of the body of the script.
826
+ *
827
+ * @return string SHA1 hash.
828
+ */
829
+ public function getScriptHash()
830
+ {
831
+ return $this->getArgument(0);
832
+ }
833
+ }
834
+
835
+ /**
836
+ * @link http://redis.io/commands/decr
837
+ * @author Daniele Alessandri <suppakilla@gmail.com>
838
+ */
839
+ class StringDecrement extends Command
840
+ {
841
+ /**
842
+ * {@inheritdoc}
843
+ */
844
+ public function getId()
845
+ {
846
+ return 'DECR';
847
+ }
848
+ }
849
+
850
+ /**
851
+ * @link http://redis.io/commands/decrby
852
+ * @author Daniele Alessandri <suppakilla@gmail.com>
853
+ */
854
+ class StringDecrementBy extends Command
855
+ {
856
+ /**
857
+ * {@inheritdoc}
858
+ */
859
+ public function getId()
860
+ {
861
+ return 'DECRBY';
862
+ }
863
+ }
864
+
865
+ /**
866
+ * @link http://redis.io/commands/get
867
+ * @author Daniele Alessandri <suppakilla@gmail.com>
868
+ */
869
+ class StringGet extends Command
870
+ {
871
+ /**
872
+ * {@inheritdoc}
873
+ */
874
+ public function getId()
875
+ {
876
+ return 'GET';
877
+ }
878
+ }
879
+
880
+ /**
881
+ * @link http://redis.io/commands/bitop
882
+ * @author Daniele Alessandri <suppakilla@gmail.com>
883
+ */
884
+ class StringBitOp extends Command
885
+ {
886
+ /**
887
+ * {@inheritdoc}
888
+ */
889
+ public function getId()
890
+ {
891
+ return 'BITOP';
892
+ }
893
+
894
+ /**
895
+ * {@inheritdoc}
896
+ */
897
+ protected function filterArguments(array $arguments)
898
+ {
899
+ if (count($arguments) === 3 && is_array($arguments[2])) {
900
+ list($operation, $destination, ) = $arguments;
901
+ $arguments = $arguments[2];
902
+ array_unshift($arguments, $operation, $destination);
903
+ }
904
+
905
+ return $arguments;
906
+ }
907
+ }
908
+
909
+ /**
910
+ * @link http://redis.io/commands/bitcount
911
+ * @author Daniele Alessandri <suppakilla@gmail.com>
912
+ */
913
+ class StringBitCount extends Command
914
+ {
915
+ /**
916
+ * {@inheritdoc}
917
+ */
918
+ public function getId()
919
+ {
920
+ return 'BITCOUNT';
921
+ }
922
+ }
923
+
924
+ /**
925
+ * @link http://redis.io/commands/sunionstore
926
+ * @author Daniele Alessandri <suppakilla@gmail.com>
927
+ */
928
+ class SetUnionStore extends SetIntersectionStore
929
+ {
930
+ /**
931
+ * {@inheritdoc}
932
+ */
933
+ public function getId()
934
+ {
935
+ return 'SUNIONSTORE';
936
+ }
937
+ }
938
+
939
+ /**
940
+ * @link http://redis.io/commands/append
941
+ * @author Daniele Alessandri <suppakilla@gmail.com>
942
+ */
943
+ class StringAppend extends Command
944
+ {
945
+ /**
946
+ * {@inheritdoc}
947
+ */
948
+ public function getId()
949
+ {
950
+ return 'APPEND';
951
+ }
952
+ }
953
+
954
+ /**
955
+ * @link http://redis.io/commands/getbit
956
+ * @author Daniele Alessandri <suppakilla@gmail.com>
957
+ */
958
+ class StringGetBit extends Command
959
+ {
960
+ /**
961
+ * {@inheritdoc}
962
+ */
963
+ public function getId()
964
+ {
965
+ return 'GETBIT';
966
+ }
967
+ }
968
+
969
+ /**
970
+ * @link http://redis.io/commands/mget
971
+ * @author Daniele Alessandri <suppakilla@gmail.com>
972
+ */
973
+ class StringGetMultiple extends Command
974
+ {
975
+ /**
976
+ * {@inheritdoc}
977
+ */
978
+ public function getId()
979
+ {
980
+ return 'MGET';
981
+ }
982
+
983
+ /**
984
+ * {@inheritdoc}
985
+ */
986
+ protected function filterArguments(array $arguments)
987
+ {
988
+ return self::normalizeArguments($arguments);
989
+ }
990
+ }
991
+
992
+ /**
993
+ * @link http://redis.io/commands/incrbyfloat
994
+ * @author Daniele Alessandri <suppakilla@gmail.com>
995
+ */
996
+ class StringIncrementByFloat extends Command
997
+ {
998
+ /**
999
+ * {@inheritdoc}
1000
+ */
1001
+ public function getId()
1002
+ {
1003
+ return 'INCRBYFLOAT';
1004
+ }
1005
+ }
1006
+
1007
+ /**
1008
+ * @link http://redis.io/commands/psetex
1009
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1010
+ */
1011
+ class StringPreciseSetExpire extends StringSetExpire
1012
+ {
1013
+ /**
1014
+ * {@inheritdoc}
1015
+ */
1016
+ public function getId()
1017
+ {
1018
+ return 'PSETEX';
1019
+ }
1020
+ }
1021
+
1022
+ /**
1023
+ * @link http://redis.io/commands/incrby
1024
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1025
+ */
1026
+ class StringIncrementBy extends Command
1027
+ {
1028
+ /**
1029
+ * {@inheritdoc}
1030
+ */
1031
+ public function getId()
1032
+ {
1033
+ return 'INCRBY';
1034
+ }
1035
+ }
1036
+
1037
+ /**
1038
+ * @link http://redis.io/commands/incr
1039
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1040
+ */
1041
+ class StringIncrement extends Command
1042
+ {
1043
+ /**
1044
+ * {@inheritdoc}
1045
+ */
1046
+ public function getId()
1047
+ {
1048
+ return 'INCR';
1049
+ }
1050
+ }
1051
+
1052
+ /**
1053
+ * @link http://redis.io/commands/getrange
1054
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1055
+ */
1056
+ class StringGetRange extends Command
1057
+ {
1058
+ /**
1059
+ * {@inheritdoc}
1060
+ */
1061
+ public function getId()
1062
+ {
1063
+ return 'GETRANGE';
1064
+ }
1065
+ }
1066
+
1067
+ /**
1068
+ * @link http://redis.io/commands/getset
1069
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1070
+ */
1071
+ class StringGetSet extends Command
1072
+ {
1073
+ /**
1074
+ * {@inheritdoc}
1075
+ */
1076
+ public function getId()
1077
+ {
1078
+ return 'GETSET';
1079
+ }
1080
+ }
1081
+
1082
+ /**
1083
+ * @link http://redis.io/commands/sunion
1084
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1085
+ */
1086
+ class SetUnion extends SetIntersection
1087
+ {
1088
+ /**
1089
+ * {@inheritdoc}
1090
+ */
1091
+ public function getId()
1092
+ {
1093
+ return 'SUNION';
1094
+ }
1095
+ }
1096
+
1097
+ /**
1098
+ * @link http://redis.io/commands/sscan
1099
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1100
+ */
1101
+ class SetScan extends Command
1102
+ {
1103
+ /**
1104
+ * {@inheritdoc}
1105
+ */
1106
+ public function getId()
1107
+ {
1108
+ return 'SSCAN';
1109
+ }
1110
+
1111
+ /**
1112
+ * {@inheritdoc}
1113
+ */
1114
+ protected function filterArguments(array $arguments)
1115
+ {
1116
+ if (count($arguments) === 3 && is_array($arguments[2])) {
1117
+ $options = $this->prepareOptions(array_pop($arguments));
1118
+ $arguments = array_merge($arguments, $options);
1119
+ }
1120
+
1121
+ return $arguments;
1122
+ }
1123
+
1124
+ /**
1125
+ * Returns a list of options and modifiers compatible with Redis.
1126
+ *
1127
+ * @param array $options List of options.
1128
+ *
1129
+ * @return array
1130
+ */
1131
+ protected function prepareOptions($options)
1132
+ {
1133
+ $options = array_change_key_case($options, CASE_UPPER);
1134
+ $normalized = array();
1135
+
1136
+ if (!empty($options['MATCH'])) {
1137
+ $normalized[] = 'MATCH';
1138
+ $normalized[] = $options['MATCH'];
1139
+ }
1140
+
1141
+ if (!empty($options['COUNT'])) {
1142
+ $normalized[] = 'COUNT';
1143
+ $normalized[] = $options['COUNT'];
1144
+ }
1145
+
1146
+ return $normalized;
1147
+ }
1148
+ }
1149
+
1150
+ /**
1151
+ * @link http://redis.io/commands/scard
1152
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1153
+ */
1154
+ class SetCardinality extends Command
1155
+ {
1156
+ /**
1157
+ * {@inheritdoc}
1158
+ */
1159
+ public function getId()
1160
+ {
1161
+ return 'SCARD';
1162
+ }
1163
+ }
1164
+
1165
+ /**
1166
+ * @link http://redis.io/commands/sdiff
1167
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1168
+ */
1169
+ class SetDifference extends SetIntersection
1170
+ {
1171
+ /**
1172
+ * {@inheritdoc}
1173
+ */
1174
+ public function getId()
1175
+ {
1176
+ return 'SDIFF';
1177
+ }
1178
+ }
1179
+
1180
+ /**
1181
+ * @link http://redis.io/commands/sdiffstore
1182
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1183
+ */
1184
+ class SetDifferenceStore extends SetIntersectionStore
1185
+ {
1186
+ /**
1187
+ * {@inheritdoc}
1188
+ */
1189
+ public function getId()
1190
+ {
1191
+ return 'SDIFFSTORE';
1192
+ }
1193
+ }
1194
+
1195
+ /**
1196
+ * @link http://redis.io/commands/sadd
1197
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1198
+ */
1199
+ class SetAdd extends Command
1200
+ {
1201
+ /**
1202
+ * {@inheritdoc}
1203
+ */
1204
+ public function getId()
1205
+ {
1206
+ return 'SADD';
1207
+ }
1208
+
1209
+ /**
1210
+ * {@inheritdoc}
1211
+ */
1212
+ protected function filterArguments(array $arguments)
1213
+ {
1214
+ return self::normalizeVariadic($arguments);
1215
+ }
1216
+ }
1217
+
1218
+ /**
1219
+ * @link http://redis.io/commands/time
1220
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1221
+ */
1222
+ class ServerTime extends Command
1223
+ {
1224
+ /**
1225
+ * {@inheritdoc}
1226
+ */
1227
+ public function getId()
1228
+ {
1229
+ return 'TIME';
1230
+ }
1231
+ }
1232
+
1233
+ /**
1234
+ * @link http://redis.io/commands/slaveof
1235
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1236
+ */
1237
+ class ServerSlaveOf extends Command
1238
+ {
1239
+ /**
1240
+ * {@inheritdoc}
1241
+ */
1242
+ public function getId()
1243
+ {
1244
+ return 'SLAVEOF';
1245
+ }
1246
+
1247
+ /**
1248
+ * {@inheritdoc}
1249
+ */
1250
+ protected function filterArguments(array $arguments)
1251
+ {
1252
+ if (count($arguments) === 0 || $arguments[0] === 'NO ONE') {
1253
+ return array('NO', 'ONE');
1254
+ }
1255
+
1256
+ return $arguments;
1257
+ }
1258
+ }
1259
+
1260
+ /**
1261
+ * @link http://redis.io/commands/slowlog
1262
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1263
+ */
1264
+ class ServerSlowlog extends Command
1265
+ {
1266
+ /**
1267
+ * {@inheritdoc}
1268
+ */
1269
+ public function getId()
1270
+ {
1271
+ return 'SLOWLOG';
1272
+ }
1273
+
1274
+ /**
1275
+ * {@inheritdoc}
1276
+ */
1277
+ public function parseResponse($data)
1278
+ {
1279
+ if (is_array($data)) {
1280
+ $log = array();
1281
+
1282
+ foreach ($data as $index => $entry) {
1283
+ $log[$index] = array(
1284
+ 'id' => $entry[0],
1285
+ 'timestamp' => $entry[1],
1286
+ 'duration' => $entry[2],
1287
+ 'command' => $entry[3],
1288
+ );
1289
+ }
1290
+
1291
+ return $log;
1292
+ }
1293
+
1294
+ return $data;
1295
+ }
1296
+ }
1297
+
1298
+ /**
1299
+ * @link http://redis.io/commands/hexists
1300
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1301
+ */
1302
+ class HashExists extends Command
1303
+ {
1304
+ /**
1305
+ * {@inheritdoc}
1306
+ */
1307
+ public function getId()
1308
+ {
1309
+ return 'HEXISTS';
1310
+ }
1311
+
1312
+ /**
1313
+ * {@inheritdoc}
1314
+ */
1315
+ public function parseResponse($data)
1316
+ {
1317
+ return (bool) $data;
1318
+ }
1319
+ }
1320
+
1321
+ /**
1322
+ * @link http://redis.io/commands/hdel
1323
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1324
+ */
1325
+ class HashDelete extends Command
1326
+ {
1327
+ /**
1328
+ * {@inheritdoc}
1329
+ */
1330
+ public function getId()
1331
+ {
1332
+ return 'HDEL';
1333
+ }
1334
+
1335
+ /**
1336
+ * {@inheritdoc}
1337
+ */
1338
+ protected function filterArguments(array $arguments)
1339
+ {
1340
+ return self::normalizeVariadic($arguments);
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * @link http://redis.io/commands/srandmember
1346
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1347
+ */
1348
+ class SetRandomMember extends Command
1349
+ {
1350
+ /**
1351
+ * {@inheritdoc}
1352
+ */
1353
+ public function getId()
1354
+ {
1355
+ return 'SRANDMEMBER';
1356
+ }
1357
+ }
1358
+
1359
+ /**
1360
+ * @link http://redis.io/commands/srem
1361
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1362
+ */
1363
+ class SetRemove extends Command
1364
+ {
1365
+ /**
1366
+ * {@inheritdoc}
1367
+ */
1368
+ public function getId()
1369
+ {
1370
+ return 'SREM';
1371
+ }
1372
+
1373
+ /**
1374
+ * {@inheritdoc}
1375
+ */
1376
+ protected function filterArguments(array $arguments)
1377
+ {
1378
+ return self::normalizeVariadic($arguments);
1379
+ }
1380
+ }
1381
+
1382
+ /**
1383
+ * @link http://redis.io/commands/spop
1384
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1385
+ */
1386
+ class SetPop extends Command
1387
+ {
1388
+ /**
1389
+ * {@inheritdoc}
1390
+ */
1391
+ public function getId()
1392
+ {
1393
+ return 'SPOP';
1394
+ }
1395
+ }
1396
+
1397
+ /**
1398
+ * @link http://redis.io/commands/smove
1399
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1400
+ */
1401
+ class SetMove extends Command
1402
+ {
1403
+ /**
1404
+ * {@inheritdoc}
1405
+ */
1406
+ public function getId()
1407
+ {
1408
+ return 'SMOVE';
1409
+ }
1410
+
1411
+ /**
1412
+ * {@inheritdoc}
1413
+ */
1414
+ public function parseResponse($data)
1415
+ {
1416
+ return (bool) $data;
1417
+ }
1418
+ }
1419
+
1420
+ /**
1421
+ * @link http://redis.io/commands/sismember
1422
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1423
+ */
1424
+ class SetIsMember extends Command
1425
+ {
1426
+ /**
1427
+ * {@inheritdoc}
1428
+ */
1429
+ public function getId()
1430
+ {
1431
+ return 'SISMEMBER';
1432
+ }
1433
+
1434
+ /**
1435
+ * {@inheritdoc}
1436
+ */
1437
+ public function parseResponse($data)
1438
+ {
1439
+ return (bool) $data;
1440
+ }
1441
+ }
1442
+
1443
+ /**
1444
+ * @link http://redis.io/commands/smembers
1445
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1446
+ */
1447
+ class SetMembers extends Command
1448
+ {
1449
+ /**
1450
+ * {@inheritdoc}
1451
+ */
1452
+ public function getId()
1453
+ {
1454
+ return 'SMEMBERS';
1455
+ }
1456
+ }
1457
+
1458
+ /**
1459
+ * @link http://redis.io/commands/set
1460
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1461
+ */
1462
+ class StringSet extends Command
1463
+ {
1464
+ /**
1465
+ * {@inheritdoc}
1466
+ */
1467
+ public function getId()
1468
+ {
1469
+ return 'SET';
1470
+ }
1471
+ }
1472
+
1473
+ /**
1474
+ * @link http://redis.io/commands/setbit
1475
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1476
+ */
1477
+ class StringSetBit extends Command
1478
+ {
1479
+ /**
1480
+ * {@inheritdoc}
1481
+ */
1482
+ public function getId()
1483
+ {
1484
+ return 'SETBIT';
1485
+ }
1486
+ }
1487
+
1488
+ /**
1489
+ * @link http://redis.io/commands/zrank
1490
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1491
+ */
1492
+ class ZSetRank extends Command
1493
+ {
1494
+ /**
1495
+ * {@inheritdoc}
1496
+ */
1497
+ public function getId()
1498
+ {
1499
+ return 'ZRANK';
1500
+ }
1501
+ }
1502
+
1503
+ /**
1504
+ * @link http://redis.io/commands/zrem
1505
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1506
+ */
1507
+ class ZSetRemove extends Command
1508
+ {
1509
+ /**
1510
+ * {@inheritdoc}
1511
+ */
1512
+ public function getId()
1513
+ {
1514
+ return 'ZREM';
1515
+ }
1516
+
1517
+ /**
1518
+ * {@inheritdoc}
1519
+ */
1520
+ protected function filterArguments(array $arguments)
1521
+ {
1522
+ return self::normalizeVariadic($arguments);
1523
+ }
1524
+ }
1525
+
1526
+ /**
1527
+ * @link http://redis.io/commands/zremrangebylex
1528
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1529
+ */
1530
+ class ZSetRemoveRangeByLex extends Command
1531
+ {
1532
+ /**
1533
+ * {@inheritdoc}
1534
+ */
1535
+ public function getId()
1536
+ {
1537
+ return 'ZREMRANGEBYLEX';
1538
+ }
1539
+ }
1540
+
1541
+ /**
1542
+ * @link http://redis.io/commands/echo
1543
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1544
+ */
1545
+ class ConnectionEcho extends Command
1546
+ {
1547
+ /**
1548
+ * {@inheritdoc}
1549
+ */
1550
+ public function getId()
1551
+ {
1552
+ return 'ECHO';
1553
+ }
1554
+ }
1555
+
1556
+ /**
1557
+ * @link http://redis.io/commands/zrangebylex
1558
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1559
+ */
1560
+ class ZSetRangeByLex extends ZSetRange
1561
+ {
1562
+ /**
1563
+ * {@inheritdoc}
1564
+ */
1565
+ public function getId()
1566
+ {
1567
+ return 'ZRANGEBYLEX';
1568
+ }
1569
+
1570
+ /**
1571
+ * {@inheritdoc}
1572
+ */
1573
+ protected function prepareOptions($options)
1574
+ {
1575
+ $opts = array_change_key_case($options, CASE_UPPER);
1576
+ $finalizedOpts = array();
1577
+
1578
+ if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
1579
+ $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
1580
+
1581
+ $finalizedOpts[] = 'LIMIT';
1582
+ $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
1583
+ $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
1584
+ }
1585
+
1586
+ return $finalizedOpts;
1587
+ }
1588
+
1589
+ /**
1590
+ * {@inheritdoc}
1591
+ */
1592
+ protected function withScores()
1593
+ {
1594
+ return false;
1595
+ }
1596
+ }
1597
+
1598
+ /**
1599
+ * @link http://redis.io/commands/zlexcount
1600
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1601
+ */
1602
+ class ZSetLexCount extends Command
1603
+ {
1604
+ /**
1605
+ * {@inheritdoc}
1606
+ */
1607
+ public function getId()
1608
+ {
1609
+ return 'ZLEXCOUNT';
1610
+ }
1611
+ }
1612
+
1613
+ /**
1614
+ * @link http://redis.io/commands/ping
1615
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1616
+ */
1617
+ class ConnectionPing extends Command
1618
+ {
1619
+ /**
1620
+ * {@inheritdoc}
1621
+ */
1622
+ public function getId()
1623
+ {
1624
+ return 'PING';
1625
+ }
1626
+ }
1627
+
1628
+ /**
1629
+ * @link http://redis.io/commands/zremrangebyrank
1630
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1631
+ */
1632
+ class ZSetRemoveRangeByRank extends Command
1633
+ {
1634
+ /**
1635
+ * {@inheritdoc}
1636
+ */
1637
+ public function getId()
1638
+ {
1639
+ return 'ZREMRANGEBYRANK';
1640
+ }
1641
+ }
1642
+
1643
+ /**
1644
+ * @link http://redis.io/commands/zremrangebyscore
1645
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1646
+ */
1647
+ class ZSetRemoveRangeByScore extends Command
1648
+ {
1649
+ /**
1650
+ * {@inheritdoc}
1651
+ */
1652
+ public function getId()
1653
+ {
1654
+ return 'ZREMRANGEBYSCORE';
1655
+ }
1656
+ }
1657
+
1658
+ /**
1659
+ * @link http://redis.io/commands/zscore
1660
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1661
+ */
1662
+ class ZSetScore extends Command
1663
+ {
1664
+ /**
1665
+ * {@inheritdoc}
1666
+ */
1667
+ public function getId()
1668
+ {
1669
+ return 'ZSCORE';
1670
+ }
1671
+ }
1672
+
1673
+ /**
1674
+ * @link http://redis.io/commands/auth
1675
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1676
+ */
1677
+ class ConnectionAuth extends Command
1678
+ {
1679
+ /**
1680
+ * {@inheritdoc}
1681
+ */
1682
+ public function getId()
1683
+ {
1684
+ return 'AUTH';
1685
+ }
1686
+ }
1687
+
1688
+ /**
1689
+ * @link http://redis.io/commands/zscan
1690
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1691
+ */
1692
+ class ZSetScan extends Command
1693
+ {
1694
+ /**
1695
+ * {@inheritdoc}
1696
+ */
1697
+ public function getId()
1698
+ {
1699
+ return 'ZSCAN';
1700
+ }
1701
+
1702
+ /**
1703
+ * {@inheritdoc}
1704
+ */
1705
+ protected function filterArguments(array $arguments)
1706
+ {
1707
+ if (count($arguments) === 3 && is_array($arguments[2])) {
1708
+ $options = $this->prepareOptions(array_pop($arguments));
1709
+ $arguments = array_merge($arguments, $options);
1710
+ }
1711
+
1712
+ return $arguments;
1713
+ }
1714
+
1715
+ /**
1716
+ * Returns a list of options and modifiers compatible with Redis.
1717
+ *
1718
+ * @param array $options List of options.
1719
+ *
1720
+ * @return array
1721
+ */
1722
+ protected function prepareOptions($options)
1723
+ {
1724
+ $options = array_change_key_case($options, CASE_UPPER);
1725
+ $normalized = array();
1726
+
1727
+ if (!empty($options['MATCH'])) {
1728
+ $normalized[] = 'MATCH';
1729
+ $normalized[] = $options['MATCH'];
1730
+ }
1731
+
1732
+ if (!empty($options['COUNT'])) {
1733
+ $normalized[] = 'COUNT';
1734
+ $normalized[] = $options['COUNT'];
1735
+ }
1736
+
1737
+ return $normalized;
1738
+ }
1739
+
1740
+ /**
1741
+ * {@inheritdoc}
1742
+ */
1743
+ public function parseResponse($data)
1744
+ {
1745
+ if (is_array($data)) {
1746
+ $members = $data[1];
1747
+ $result = array();
1748
+
1749
+ for ($i = 0; $i < count($members); $i++) {
1750
+ $result[$members[$i]] = (float) $members[++$i];
1751
+ }
1752
+
1753
+ $data[1] = $result;
1754
+ }
1755
+
1756
+ return $data;
1757
+ }
1758
+ }
1759
+
1760
+ /**
1761
+ * @link http://redis.io/commands/zrevrank
1762
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1763
+ */
1764
+ class ZSetReverseRank extends Command
1765
+ {
1766
+ /**
1767
+ * {@inheritdoc}
1768
+ */
1769
+ public function getId()
1770
+ {
1771
+ return 'ZREVRANK';
1772
+ }
1773
+ }
1774
+
1775
+ /**
1776
+ * @link http://redis.io/commands/zrevrange
1777
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1778
+ */
1779
+ class ZSetReverseRange extends ZSetRange
1780
+ {
1781
+ /**
1782
+ * {@inheritdoc}
1783
+ */
1784
+ public function getId()
1785
+ {
1786
+ return 'ZREVRANGE';
1787
+ }
1788
+ }
1789
+
1790
+ /**
1791
+ * @link http://redis.io/commands/zrevrangebyscore
1792
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1793
+ */
1794
+ class ZSetReverseRangeByScore extends ZSetRangeByScore
1795
+ {
1796
+ /**
1797
+ * {@inheritdoc}
1798
+ */
1799
+ public function getId()
1800
+ {
1801
+ return 'ZREVRANGEBYSCORE';
1802
+ }
1803
+ }
1804
+
1805
+ /**
1806
+ * @link http://redis.io/commands/zinterstore
1807
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1808
+ */
1809
+ class ZSetIntersectionStore extends ZSetUnionStore
1810
+ {
1811
+ /**
1812
+ * {@inheritdoc}
1813
+ */
1814
+ public function getId()
1815
+ {
1816
+ return 'ZINTERSTORE';
1817
+ }
1818
+ }
1819
+
1820
+ /**
1821
+ * @link http://redis.io/commands/zincrby
1822
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1823
+ */
1824
+ class ZSetIncrementBy extends Command
1825
+ {
1826
+ /**
1827
+ * {@inheritdoc}
1828
+ */
1829
+ public function getId()
1830
+ {
1831
+ return 'ZINCRBY';
1832
+ }
1833
+ }
1834
+
1835
+ /**
1836
+ * @link http://redis.io/commands/setrange
1837
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1838
+ */
1839
+ class StringSetRange extends Command
1840
+ {
1841
+ /**
1842
+ * {@inheritdoc}
1843
+ */
1844
+ public function getId()
1845
+ {
1846
+ return 'SETRANGE';
1847
+ }
1848
+ }
1849
+
1850
+ /**
1851
+ * @link http://redis.io/commands/strlen
1852
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1853
+ */
1854
+ class StringStrlen extends Command
1855
+ {
1856
+ /**
1857
+ * {@inheritdoc}
1858
+ */
1859
+ public function getId()
1860
+ {
1861
+ return 'STRLEN';
1862
+ }
1863
+ }
1864
+
1865
+ /**
1866
+ * @link http://redis.io/commands/substr
1867
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1868
+ */
1869
+ class StringSubstr extends Command
1870
+ {
1871
+ /**
1872
+ * {@inheritdoc}
1873
+ */
1874
+ public function getId()
1875
+ {
1876
+ return 'SUBSTR';
1877
+ }
1878
+ }
1879
+
1880
+ /**
1881
+ * @link http://redis.io/commands/setnx
1882
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1883
+ */
1884
+ class StringSetPreserve extends Command
1885
+ {
1886
+ /**
1887
+ * {@inheritdoc}
1888
+ */
1889
+ public function getId()
1890
+ {
1891
+ return 'SETNX';
1892
+ }
1893
+
1894
+ /**
1895
+ * {@inheritdoc}
1896
+ */
1897
+ public function parseResponse($data)
1898
+ {
1899
+ return (bool) $data;
1900
+ }
1901
+ }
1902
+
1903
+ /**
1904
+ * @link http://redis.io/commands/msetnx
1905
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1906
+ */
1907
+ class StringSetMultiplePreserve extends StringSetMultiple
1908
+ {
1909
+ /**
1910
+ * {@inheritdoc}
1911
+ */
1912
+ public function getId()
1913
+ {
1914
+ return 'MSETNX';
1915
+ }
1916
+
1917
+ /**
1918
+ * {@inheritdoc}
1919
+ */
1920
+ public function parseResponse($data)
1921
+ {
1922
+ return (bool) $data;
1923
+ }
1924
+ }
1925
+
1926
+ /**
1927
+ * @link http://redis.io/commands/select
1928
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1929
+ */
1930
+ class ConnectionSelect extends Command
1931
+ {
1932
+ /**
1933
+ * {@inheritdoc}
1934
+ */
1935
+ public function getId()
1936
+ {
1937
+ return 'SELECT';
1938
+ }
1939
+ }
1940
+
1941
+ /**
1942
+ * @link http://redis.io/commands/quit
1943
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1944
+ */
1945
+ class ConnectionQuit extends Command
1946
+ {
1947
+ /**
1948
+ * {@inheritdoc}
1949
+ */
1950
+ public function getId()
1951
+ {
1952
+ return 'QUIT';
1953
+ }
1954
+ }
1955
+
1956
+ /**
1957
+ * @link http://redis.io/commands/discard
1958
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1959
+ */
1960
+ class TransactionDiscard extends Command
1961
+ {
1962
+ /**
1963
+ * {@inheritdoc}
1964
+ */
1965
+ public function getId()
1966
+ {
1967
+ return 'DISCARD';
1968
+ }
1969
+ }
1970
+
1971
+ /**
1972
+ * @link http://redis.io/commands/exec
1973
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1974
+ */
1975
+ class TransactionExec extends Command
1976
+ {
1977
+ /**
1978
+ * {@inheritdoc}
1979
+ */
1980
+ public function getId()
1981
+ {
1982
+ return 'EXEC';
1983
+ }
1984
+ }
1985
+
1986
+ /**
1987
+ * @link http://redis.io/commands/zcard
1988
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1989
+ */
1990
+ class ZSetCardinality extends Command
1991
+ {
1992
+ /**
1993
+ * {@inheritdoc}
1994
+ */
1995
+ public function getId()
1996
+ {
1997
+ return 'ZCARD';
1998
+ }
1999
+ }
2000
+
2001
+ /**
2002
+ * @link http://redis.io/commands/zcount
2003
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2004
+ */
2005
+ class ZSetCount extends Command
2006
+ {
2007
+ /**
2008
+ * {@inheritdoc}
2009
+ */
2010
+ public function getId()
2011
+ {
2012
+ return 'ZCOUNT';
2013
+ }
2014
+ }
2015
+
2016
+ /**
2017
+ * @link http://redis.io/commands/zadd
2018
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2019
+ */
2020
+ class ZSetAdd extends Command
2021
+ {
2022
+ /**
2023
+ * {@inheritdoc}
2024
+ */
2025
+ public function getId()
2026
+ {
2027
+ return 'ZADD';
2028
+ }
2029
+
2030
+ /**
2031
+ * {@inheritdoc}
2032
+ */
2033
+ protected function filterArguments(array $arguments)
2034
+ {
2035
+ if (count($arguments) === 2 && is_array($arguments[1])) {
2036
+ $flattened = array($arguments[0]);
2037
+
2038
+ foreach ($arguments[1] as $member => $score) {
2039
+ $flattened[] = $score;
2040
+ $flattened[] = $member;
2041
+ }
2042
+
2043
+ return $flattened;
2044
+ }
2045
+
2046
+ return $arguments;
2047
+ }
2048
+ }
2049
+
2050
+ /**
2051
+ * @link http://redis.io/commands/watch
2052
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2053
+ */
2054
+ class TransactionWatch extends Command
2055
+ {
2056
+ /**
2057
+ * {@inheritdoc}
2058
+ */
2059
+ public function getId()
2060
+ {
2061
+ return 'WATCH';
2062
+ }
2063
+
2064
+ /**
2065
+ * {@inheritdoc}
2066
+ */
2067
+ protected function filterArguments(array $arguments)
2068
+ {
2069
+ if (isset($arguments[0]) && is_array($arguments[0])) {
2070
+ return $arguments[0];
2071
+ }
2072
+
2073
+ return $arguments;
2074
+ }
2075
+ }
2076
+
2077
+ /**
2078
+ * @link http://redis.io/commands/multi
2079
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2080
+ */
2081
+ class TransactionMulti extends Command
2082
+ {
2083
+ /**
2084
+ * {@inheritdoc}
2085
+ */
2086
+ public function getId()
2087
+ {
2088
+ return 'MULTI';
2089
+ }
2090
+ }
2091
+
2092
+ /**
2093
+ * @link http://redis.io/commands/unwatch
2094
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2095
+ */
2096
+ class TransactionUnwatch extends Command
2097
+ {
2098
+ /**
2099
+ * {@inheritdoc}
2100
+ */
2101
+ public function getId()
2102
+ {
2103
+ return 'UNWATCH';
2104
+ }
2105
+ }
2106
+
2107
+ /**
2108
+ * @link http://redis.io/commands/shutdown
2109
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2110
+ */
2111
+ class ServerShutdown extends Command
2112
+ {
2113
+ /**
2114
+ * {@inheritdoc}
2115
+ */
2116
+ public function getId()
2117
+ {
2118
+ return 'SHUTDOWN';
2119
+ }
2120
+ }
2121
+
2122
+ /**
2123
+ * @link http://redis.io/topics/sentinel
2124
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2125
+ */
2126
+ class ServerSentinel extends Command
2127
+ {
2128
+ /**
2129
+ * {@inheritdoc}
2130
+ */
2131
+ public function getId()
2132
+ {
2133
+ return 'SENTINEL';
2134
+ }
2135
+
2136
+ /**
2137
+ * {@inheritdoc}
2138
+ */
2139
+ public function parseResponse($data)
2140
+ {
2141
+ switch (strtolower($this->getArgument(0))) {
2142
+ case 'masters':
2143
+ case 'slaves':
2144
+ return self::processMastersOrSlaves($data);
2145
+
2146
+ default:
2147
+ return $data;
2148
+ }
2149
+ }
2150
+
2151
+ /**
2152
+ * Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES.
2153
+ *
2154
+ * @param array $servers List of Redis servers.
2155
+ *
2156
+ * @return array
2157
+ */
2158
+ protected static function processMastersOrSlaves(array $servers)
2159
+ {
2160
+ foreach ($servers as $idx => $node) {
2161
+ $processed = array();
2162
+ $count = count($node);
2163
+
2164
+ for ($i = 0; $i < $count; $i++) {
2165
+ $processed[$node[$i]] = $node[++$i];
2166
+ }
2167
+
2168
+ $servers[$idx] = $processed;
2169
+ }
2170
+
2171
+ return $servers;
2172
+ }
2173
+ }
2174
+
2175
+ /**
2176
+ * @link http://redis.io/commands/type
2177
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2178
+ */
2179
+ class KeyType extends Command
2180
+ {
2181
+ /**
2182
+ * {@inheritdoc}
2183
+ */
2184
+ public function getId()
2185
+ {
2186
+ return 'TYPE';
2187
+ }
2188
+ }
2189
+
2190
+ /**
2191
+ * @link http://redis.io/commands/lindex
2192
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2193
+ */
2194
+ class ListIndex extends Command
2195
+ {
2196
+ /**
2197
+ * {@inheritdoc}
2198
+ */
2199
+ public function getId()
2200
+ {
2201
+ return 'LINDEX';
2202
+ }
2203
+ }
2204
+
2205
+ /**
2206
+ * @link http://redis.io/commands/linsert
2207
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2208
+ */
2209
+ class ListInsert extends Command
2210
+ {
2211
+ /**
2212
+ * {@inheritdoc}
2213
+ */
2214
+ public function getId()
2215
+ {
2216
+ return 'LINSERT';
2217
+ }
2218
+ }
2219
+
2220
+ /**
2221
+ * @link http://redis.io/commands/hscan
2222
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2223
+ */
2224
+ class HashScan extends Command
2225
+ {
2226
+ /**
2227
+ * {@inheritdoc}
2228
+ */
2229
+ public function getId()
2230
+ {
2231
+ return 'HSCAN';
2232
+ }
2233
+
2234
+ /**
2235
+ * {@inheritdoc}
2236
+ */
2237
+ protected function filterArguments(array $arguments)
2238
+ {
2239
+ if (count($arguments) === 3 && is_array($arguments[2])) {
2240
+ $options = $this->prepareOptions(array_pop($arguments));
2241
+ $arguments = array_merge($arguments, $options);
2242
+ }
2243
+
2244
+ return $arguments;
2245
+ }
2246
+
2247
+ /**
2248
+ * Returns a list of options and modifiers compatible with Redis.
2249
+ *
2250
+ * @param array $options List of options.
2251
+ *
2252
+ * @return array
2253
+ */
2254
+ protected function prepareOptions($options)
2255
+ {
2256
+ $options = array_change_key_case($options, CASE_UPPER);
2257
+ $normalized = array();
2258
+
2259
+ if (!empty($options['MATCH'])) {
2260
+ $normalized[] = 'MATCH';
2261
+ $normalized[] = $options['MATCH'];
2262
+ }
2263
+
2264
+ if (!empty($options['COUNT'])) {
2265
+ $normalized[] = 'COUNT';
2266
+ $normalized[] = $options['COUNT'];
2267
+ }
2268
+
2269
+ return $normalized;
2270
+ }
2271
+
2272
+ /**
2273
+ * {@inheritdoc}
2274
+ */
2275
+ public function parseResponse($data)
2276
+ {
2277
+ if (is_array($data)) {
2278
+ $fields = $data[1];
2279
+ $result = array();
2280
+
2281
+ for ($i = 0; $i < count($fields); $i++) {
2282
+ $result[$fields[$i]] = $fields[++$i];
2283
+ }
2284
+
2285
+ $data[1] = $result;
2286
+ }
2287
+
2288
+ return $data;
2289
+ }
2290
+ }
2291
+
2292
+ /**
2293
+ * @link http://redis.io/commands/sort
2294
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2295
+ */
2296
+ class KeySort extends Command
2297
+ {
2298
+ /**
2299
+ * {@inheritdoc}
2300
+ */
2301
+ public function getId()
2302
+ {
2303
+ return 'SORT';
2304
+ }
2305
+
2306
+ /**
2307
+ * {@inheritdoc}
2308
+ */
2309
+ protected function filterArguments(array $arguments)
2310
+ {
2311
+ if (count($arguments) === 1) {
2312
+ return $arguments;
2313
+ }
2314
+
2315
+ $query = array($arguments[0]);
2316
+ $sortParams = array_change_key_case($arguments[1], CASE_UPPER);
2317
+
2318
+ if (isset($sortParams['BY'])) {
2319
+ $query[] = 'BY';
2320
+ $query[] = $sortParams['BY'];
2321
+ }
2322
+
2323
+ if (isset($sortParams['GET'])) {
2324
+ $getargs = $sortParams['GET'];
2325
+
2326
+ if (is_array($getargs)) {
2327
+ foreach ($getargs as $getarg) {
2328
+ $query[] = 'GET';
2329
+ $query[] = $getarg;
2330
+ }
2331
+ } else {
2332
+ $query[] = 'GET';
2333
+ $query[] = $getargs;
2334
+ }
2335
+ }
2336
+
2337
+ if (isset($sortParams['LIMIT']) &&
2338
+ is_array($sortParams['LIMIT']) &&
2339
+ count($sortParams['LIMIT']) == 2) {
2340
+
2341
+ $query[] = 'LIMIT';
2342
+ $query[] = $sortParams['LIMIT'][0];
2343
+ $query[] = $sortParams['LIMIT'][1];
2344
+ }
2345
+
2346
+ if (isset($sortParams['SORT'])) {
2347
+ $query[] = strtoupper($sortParams['SORT']);
2348
+ }
2349
+
2350
+ if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) {
2351
+ $query[] = 'ALPHA';
2352
+ }
2353
+
2354
+ if (isset($sortParams['STORE'])) {
2355
+ $query[] = 'STORE';
2356
+ $query[] = $sortParams['STORE'];
2357
+ }
2358
+
2359
+ return $query;
2360
+ }
2361
+ }
2362
+
2363
+ /**
2364
+ * @link http://redis.io/commands/restore
2365
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2366
+ */
2367
+ class KeyRestore extends Command
2368
+ {
2369
+ /**
2370
+ * {@inheritdoc}
2371
+ */
2372
+ public function getId()
2373
+ {
2374
+ return 'RESTORE';
2375
+ }
2376
+ }
2377
+
2378
+ /**
2379
+ * @link http://redis.io/commands/scan
2380
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2381
+ */
2382
+ class KeyScan extends Command
2383
+ {
2384
+ /**
2385
+ * {@inheritdoc}
2386
+ */
2387
+ public function getId()
2388
+ {
2389
+ return 'SCAN';
2390
+ }
2391
+
2392
+ /**
2393
+ * {@inheritdoc}
2394
+ */
2395
+ protected function filterArguments(array $arguments)
2396
+ {
2397
+ if (count($arguments) === 2 && is_array($arguments[1])) {
2398
+ $options = $this->prepareOptions(array_pop($arguments));
2399
+ $arguments = array_merge($arguments, $options);
2400
+ }
2401
+
2402
+ return $arguments;
2403
+ }
2404
+
2405
+ /**
2406
+ * Returns a list of options and modifiers compatible with Redis.
2407
+ *
2408
+ * @param array $options List of options.
2409
+ *
2410
+ * @return array
2411
+ */
2412
+ protected function prepareOptions($options)
2413
+ {
2414
+ $options = array_change_key_case($options, CASE_UPPER);
2415
+ $normalized = array();
2416
+
2417
+ if (!empty($options['MATCH'])) {
2418
+ $normalized[] = 'MATCH';
2419
+ $normalized[] = $options['MATCH'];
2420
+ }
2421
+
2422
+ if (!empty($options['COUNT'])) {
2423
+ $normalized[] = 'COUNT';
2424
+ $normalized[] = $options['COUNT'];
2425
+ }
2426
+
2427
+ return $normalized;
2428
+ }
2429
+ }
2430
+
2431
+ /**
2432
+ * @link http://redis.io/commands/llen
2433
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2434
+ */
2435
+ class ListLength extends Command
2436
+ {
2437
+ /**
2438
+ * {@inheritdoc}
2439
+ */
2440
+ public function getId()
2441
+ {
2442
+ return 'LLEN';
2443
+ }
2444
+ }
2445
+
2446
+ /**
2447
+ * @link http://redis.io/commands/lpop
2448
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2449
+ */
2450
+ class ListPopFirst extends Command
2451
+ {
2452
+ /**
2453
+ * {@inheritdoc}
2454
+ */
2455
+ public function getId()
2456
+ {
2457
+ return 'LPOP';
2458
+ }
2459
+ }
2460
+
2461
+ /**
2462
+ * @link http://redis.io/commands/brpoplpush
2463
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2464
+ */
2465
+ class ListPopLastPushHeadBlocking extends Command
2466
+ {
2467
+ /**
2468
+ * {@inheritdoc}
2469
+ */
2470
+ public function getId()
2471
+ {
2472
+ return 'BRPOPLPUSH';
2473
+ }
2474
+ }
2475
+
2476
+ /**
2477
+ * @link http://redis.io/commands/lpush
2478
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2479
+ */
2480
+ class ListPushHead extends ListPushTail
2481
+ {
2482
+ /**
2483
+ * {@inheritdoc}
2484
+ */
2485
+ public function getId()
2486
+ {
2487
+ return 'LPUSH';
2488
+ }
2489
+ }
2490
+
2491
+ /**
2492
+ * @link http://redis.io/commands/rpoplpush
2493
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2494
+ */
2495
+ class ListPopLastPushHead extends Command
2496
+ {
2497
+ /**
2498
+ * {@inheritdoc}
2499
+ */
2500
+ public function getId()
2501
+ {
2502
+ return 'RPOPLPUSH';
2503
+ }
2504
+ }
2505
+
2506
+ /**
2507
+ * @link http://redis.io/commands/brpop
2508
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2509
+ */
2510
+ class ListPopLastBlocking extends ListPopFirstBlocking
2511
+ {
2512
+ /**
2513
+ * {@inheritdoc}
2514
+ */
2515
+ public function getId()
2516
+ {
2517
+ return 'BRPOP';
2518
+ }
2519
+ }
2520
+
2521
+ /**
2522
+ * @link http://redis.io/commands/hlen
2523
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2524
+ */
2525
+ class HashLength extends Command
2526
+ {
2527
+ /**
2528
+ * {@inheritdoc}
2529
+ */
2530
+ public function getId()
2531
+ {
2532
+ return 'HLEN';
2533
+ }
2534
+ }
2535
+
2536
+ /**
2537
+ * @link http://redis.io/commands/rpop
2538
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2539
+ */
2540
+ class ListPopLast extends Command
2541
+ {
2542
+ /**
2543
+ * {@inheritdoc}
2544
+ */
2545
+ public function getId()
2546
+ {
2547
+ return 'RPOP';
2548
+ }
2549
+ }
2550
+
2551
+ /**
2552
+ * @link http://redis.io/commands/renamenx
2553
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2554
+ */
2555
+ class KeyRenamePreserve extends KeyRename
2556
+ {
2557
+ /**
2558
+ * {@inheritdoc}
2559
+ */
2560
+ public function getId()
2561
+ {
2562
+ return 'RENAMENX';
2563
+ }
2564
+
2565
+ /**
2566
+ * {@inheritdoc}
2567
+ */
2568
+ public function parseResponse($data)
2569
+ {
2570
+ return (bool) $data;
2571
+ }
2572
+ }
2573
+
2574
+ /**
2575
+ * @link http://redis.io/commands/hset
2576
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2577
+ */
2578
+ class HashSet extends Command
2579
+ {
2580
+ /**
2581
+ * {@inheritdoc}
2582
+ */
2583
+ public function getId()
2584
+ {
2585
+ return 'HSET';
2586
+ }
2587
+
2588
+ /**
2589
+ * {@inheritdoc}
2590
+ */
2591
+ public function parseResponse($data)
2592
+ {
2593
+ return (bool) $data;
2594
+ }
2595
+ }
2596
+
2597
+ /**
2598
+ * @link http://redis.io/commands/dump
2599
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2600
+ */
2601
+ class KeyDump extends Command
2602
+ {
2603
+ /**
2604
+ * {@inheritdoc}
2605
+ */
2606
+ public function getId()
2607
+ {
2608
+ return 'DUMP';
2609
+ }
2610
+ }
2611
+
2612
+ /**
2613
+ * @link http://redis.io/commands/exists
2614
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2615
+ */
2616
+ class KeyExists extends Command
2617
+ {
2618
+ /**
2619
+ * {@inheritdoc}
2620
+ */
2621
+ public function getId()
2622
+ {
2623
+ return 'EXISTS';
2624
+ }
2625
+
2626
+ /**
2627
+ * {@inheritdoc}
2628
+ */
2629
+ public function parseResponse($data)
2630
+ {
2631
+ return (bool) $data;
2632
+ }
2633
+ }
2634
+
2635
+ /**
2636
+ * @link http://redis.io/commands/hsetnx
2637
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2638
+ */
2639
+ class HashSetPreserve extends Command
2640
+ {
2641
+ /**
2642
+ * {@inheritdoc}
2643
+ */
2644
+ public function getId()
2645
+ {
2646
+ return 'HSETNX';
2647
+ }
2648
+
2649
+ /**
2650
+ * {@inheritdoc}
2651
+ */
2652
+ public function parseResponse($data)
2653
+ {
2654
+ return (bool) $data;
2655
+ }
2656
+ }
2657
+
2658
+ /**
2659
+ * @link http://redis.io/commands/del
2660
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2661
+ */
2662
+ class KeyDelete extends Command
2663
+ {
2664
+ /**
2665
+ * {@inheritdoc}
2666
+ */
2667
+ public function getId()
2668
+ {
2669
+ return 'DEL';
2670
+ }
2671
+
2672
+ /**
2673
+ * {@inheritdoc}
2674
+ */
2675
+ protected function filterArguments(array $arguments)
2676
+ {
2677
+ return self::normalizeArguments($arguments);
2678
+ }
2679
+ }
2680
+
2681
+ /**
2682
+ * @link http://redis.io/commands/pfmerge
2683
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2684
+ */
2685
+ class HyperLogLogMerge extends Command
2686
+ {
2687
+ /**
2688
+ * {@inheritdoc}
2689
+ */
2690
+ public function getId()
2691
+ {
2692
+ return 'PFMERGE';
2693
+ }
2694
+
2695
+ /**
2696
+ * {@inheritdoc}
2697
+ */
2698
+ protected function filterArguments(array $arguments)
2699
+ {
2700
+ return self::normalizeArguments($arguments);
2701
+ }
2702
+ }
2703
+
2704
+ /**
2705
+ * @link http://redis.io/commands/pfadd
2706
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2707
+ */
2708
+ class HyperLogLogAdd extends Command
2709
+ {
2710
+ /**
2711
+ * {@inheritdoc}
2712
+ */
2713
+ public function getId()
2714
+ {
2715
+ return 'PFADD';
2716
+ }
2717
+
2718
+ /**
2719
+ * {@inheritdoc}
2720
+ */
2721
+ protected function filterArguments(array $arguments)
2722
+ {
2723
+ return self::normalizeVariadic($arguments);
2724
+ }
2725
+
2726
+ /**
2727
+ * {@inheritdoc}
2728
+ */
2729
+ public function parseResponse($data)
2730
+ {
2731
+ return (bool) $data;
2732
+ }
2733
+ }
2734
+
2735
+ /**
2736
+ * @link http://redis.io/commands/pfcount
2737
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2738
+ */
2739
+ class HyperLogLogCount extends Command
2740
+ {
2741
+ /**
2742
+ * {@inheritdoc}
2743
+ */
2744
+ public function getId()
2745
+ {
2746
+ return 'PFCOUNT';
2747
+ }
2748
+
2749
+ /**
2750
+ * {@inheritdoc}
2751
+ */
2752
+ protected function filterArguments(array $arguments)
2753
+ {
2754
+ return self::normalizeArguments($arguments);
2755
+ }
2756
+ }
2757
+
2758
+ /**
2759
+ * @link http://redis.io/commands/hmset
2760
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2761
+ */
2762
+ class HashSetMultiple extends Command
2763
+ {
2764
+ /**
2765
+ * {@inheritdoc}
2766
+ */
2767
+ public function getId()
2768
+ {
2769
+ return 'HMSET';
2770
+ }
2771
+
2772
+ /**
2773
+ * {@inheritdoc}
2774
+ */
2775
+ protected function filterArguments(array $arguments)
2776
+ {
2777
+ if (count($arguments) === 2 && is_array($arguments[1])) {
2778
+ $flattenedKVs = array($arguments[0]);
2779
+ $args = $arguments[1];
2780
+
2781
+ foreach ($args as $k => $v) {
2782
+ $flattenedKVs[] = $k;
2783
+ $flattenedKVs[] = $v;
2784
+ }
2785
+
2786
+ return $flattenedKVs;
2787
+ }
2788
+
2789
+ return $arguments;
2790
+ }
2791
+ }
2792
+
2793
+ /**
2794
+ * @link http://redis.io/commands/keys
2795
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2796
+ */
2797
+ class KeyKeys extends Command
2798
+ {
2799
+ /**
2800
+ * {@inheritdoc}
2801
+ */
2802
+ public function getId()
2803
+ {
2804
+ return 'KEYS';
2805
+ }
2806
+ }
2807
+
2808
+ /**
2809
+ * @link http://redis.io/commands/pttl
2810
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2811
+ */
2812
+ class KeyPreciseTimeToLive extends KeyTimeToLive
2813
+ {
2814
+ /**
2815
+ * {@inheritdoc}
2816
+ */
2817
+ public function getId()
2818
+ {
2819
+ return 'PTTL';
2820
+ }
2821
+ }
2822
+
2823
+ /**
2824
+ * @link http://redis.io/commands/randomkey
2825
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2826
+ */
2827
+ class KeyRandom extends Command
2828
+ {
2829
+ /**
2830
+ * {@inheritdoc}
2831
+ */
2832
+ public function getId()
2833
+ {
2834
+ return 'RANDOMKEY';
2835
+ }
2836
+
2837
+ /**
2838
+ * {@inheritdoc}
2839
+ */
2840
+ public function parseResponse($data)
2841
+ {
2842
+ return $data !== '' ? $data : null;
2843
+ }
2844
+ }
2845
+
2846
+ /**
2847
+ * @link http://redis.io/commands/pexpireat
2848
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2849
+ */
2850
+ class KeyPreciseExpireAt extends KeyExpireAt
2851
+ {
2852
+ /**
2853
+ * {@inheritdoc}
2854
+ */
2855
+ public function getId()
2856
+ {
2857
+ return 'PEXPIREAT';
2858
+ }
2859
+ }
2860
+
2861
+ /**
2862
+ * @link http://redis.io/commands/pexpire
2863
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2864
+ */
2865
+ class KeyPreciseExpire extends KeyExpire
2866
+ {
2867
+ /**
2868
+ * {@inheritdoc}
2869
+ */
2870
+ public function getId()
2871
+ {
2872
+ return 'PEXPIRE';
2873
+ }
2874
+ }
2875
+
2876
+ /**
2877
+ * @link http://redis.io/commands/move
2878
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2879
+ */
2880
+ class KeyMove extends Command
2881
+ {
2882
+ /**
2883
+ * {@inheritdoc}
2884
+ */
2885
+ public function getId()
2886
+ {
2887
+ return 'MOVE';
2888
+ }
2889
+
2890
+ /**
2891
+ * {@inheritdoc}
2892
+ */
2893
+ public function parseResponse($data)
2894
+ {
2895
+ return (bool) $data;
2896
+ }
2897
+ }
2898
+
2899
+ /**
2900
+ * @link http://redis.io/commands/persist
2901
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2902
+ */
2903
+ class KeyPersist extends Command
2904
+ {
2905
+ /**
2906
+ * {@inheritdoc}
2907
+ */
2908
+ public function getId()
2909
+ {
2910
+ return 'PERSIST';
2911
+ }
2912
+
2913
+ /**
2914
+ * {@inheritdoc}
2915
+ */
2916
+ public function parseResponse($data)
2917
+ {
2918
+ return (bool) $data;
2919
+ }
2920
+ }
2921
+
2922
+ /**
2923
+ * @link http://redis.io/commands/lpushx
2924
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2925
+ */
2926
+ class ListPushHeadX extends Command
2927
+ {
2928
+ /**
2929
+ * {@inheritdoc}
2930
+ */
2931
+ public function getId()
2932
+ {
2933
+ return 'LPUSHX';
2934
+ }
2935
+ }
2936
+
2937
+ /**
2938
+ * @link http://redis.io/commands/hkeys
2939
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2940
+ */
2941
+ class HashKeys extends Command
2942
+ {
2943
+ /**
2944
+ * {@inheritdoc}
2945
+ */
2946
+ public function getId()
2947
+ {
2948
+ return 'HKEYS';
2949
+ }
2950
+ }
2951
+
2952
+ /**
2953
+ * @link http://redis.io/commands/hmget
2954
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2955
+ */
2956
+ class HashGetMultiple extends Command
2957
+ {
2958
+ /**
2959
+ * {@inheritdoc}
2960
+ */
2961
+ public function getId()
2962
+ {
2963
+ return 'HMGET';
2964
+ }
2965
+
2966
+ /**
2967
+ * {@inheritdoc}
2968
+ */
2969
+ protected function filterArguments(array $arguments)
2970
+ {
2971
+ return self::normalizeVariadic($arguments);
2972
+ }
2973
+ }
2974
+
2975
+ /**
2976
+ * @link http://redis.io/commands/hgetall
2977
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2978
+ */
2979
+ class HashGetAll extends Command
2980
+ {
2981
+ /**
2982
+ * {@inheritdoc}
2983
+ */
2984
+ public function getId()
2985
+ {
2986
+ return 'HGETALL';
2987
+ }
2988
+
2989
+ /**
2990
+ * {@inheritdoc}
2991
+ */
2992
+ public function parseResponse($data)
2993
+ {
2994
+ $result = array();
2995
+
2996
+ for ($i = 0; $i < count($data); $i++) {
2997
+ $result[$data[$i]] = $data[++$i];
2998
+ }
2999
+
3000
+ return $result;
3001
+ }
3002
+ }
3003
+
3004
+ /**
3005
+ * @link http://redis.io/commands/flushall
3006
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3007
+ */
3008
+ class ServerFlushAll extends Command
3009
+ {
3010
+ /**
3011
+ * {@inheritdoc}
3012
+ */
3013
+ public function getId()
3014
+ {
3015
+ return 'FLUSHALL';
3016
+ }
3017
+ }
3018
+
3019
+ /**
3020
+ * @link http://redis.io/commands/dbsize
3021
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3022
+ */
3023
+ class ServerDatabaseSize extends Command
3024
+ {
3025
+ /**
3026
+ * {@inheritdoc}
3027
+ */
3028
+ public function getId()
3029
+ {
3030
+ return 'DBSIZE';
3031
+ }
3032
+ }
3033
+
3034
+ /**
3035
+ * @link http://redis.io/commands/config-set
3036
+ * @link http://redis.io/commands/config-get
3037
+ * @link http://redis.io/commands/config-resetstat
3038
+ * @link http://redis.io/commands/config-rewrite
3039
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3040
+ */
3041
+ class ServerConfig extends Command
3042
+ {
3043
+ /**
3044
+ * {@inheritdoc}
3045
+ */
3046
+ public function getId()
3047
+ {
3048
+ return 'CONFIG';
3049
+ }
3050
+
3051
+ /**
3052
+ * {@inheritdoc}
3053
+ */
3054
+ public function parseResponse($data)
3055
+ {
3056
+ if (is_array($data)) {
3057
+ $result = array();
3058
+
3059
+ for ($i = 0; $i < count($data); $i++) {
3060
+ $result[$data[$i]] = $data[++$i];
3061
+ }
3062
+
3063
+ return $result;
3064
+ }
3065
+
3066
+ return $data;
3067
+ }
3068
+ }
3069
+
3070
+ /**
3071
+ * @link http://redis.io/commands/client-list
3072
+ * @link http://redis.io/commands/client-kill
3073
+ * @link http://redis.io/commands/client-getname
3074
+ * @link http://redis.io/commands/client-setname
3075
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3076
+ */
3077
+ class ServerClient extends Command
3078
+ {
3079
+ /**
3080
+ * {@inheritdoc}
3081
+ */
3082
+ public function getId()
3083
+ {
3084
+ return 'CLIENT';
3085
+ }
3086
+
3087
+ /**
3088
+ * {@inheritdoc}
3089
+ */
3090
+ public function parseResponse($data)
3091
+ {
3092
+ $args = array_change_key_case($this->getArguments(), CASE_UPPER);
3093
+
3094
+ switch (strtoupper($args[0])) {
3095
+ case 'LIST':
3096
+ return $this->parseClientList($data);
3097
+ case 'KILL':
3098
+ case 'GETNAME':
3099
+ case 'SETNAME':
3100
+ default:
3101
+ return $data;
3102
+ }
3103
+ }
3104
+
3105
+ /**
3106
+ * Parses the response to CLIENT LIST and returns a structured list.
3107
+ *
3108
+ * @param string $data Response buffer.
3109
+ *
3110
+ * @return array
3111
+ */
3112
+ protected function parseClientList($data)
3113
+ {
3114
+ $clients = array();
3115
+
3116
+ foreach (explode("\n", $data, -1) as $clientData) {
3117
+ $client = array();
3118
+
3119
+ foreach (explode(' ', $clientData) as $kv) {
3120
+ @list($k, $v) = explode('=', $kv);
3121
+ $client[$k] = $v;
3122
+ }
3123
+
3124
+ $clients[] = $client;
3125
+ }
3126
+
3127
+ return $clients;
3128
+ }
3129
+ }
3130
+
3131
+ /**
3132
+ * @link http://redis.io/commands/command
3133
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3134
+ */
3135
+ class ServerCommand extends Command
3136
+ {
3137
+ /**
3138
+ * {@inheritdoc}
3139
+ */
3140
+ public function getId()
3141
+ {
3142
+ return 'COMMAND';
3143
+ }
3144
+ }
3145
+
3146
+ /**
3147
+ * @link http://redis.io/commands/flushdb
3148
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3149
+ */
3150
+ class ServerFlushDatabase extends Command
3151
+ {
3152
+ /**
3153
+ * {@inheritdoc}
3154
+ */
3155
+ public function getId()
3156
+ {
3157
+ return 'FLUSHDB';
3158
+ }
3159
+ }
3160
+
3161
+ /**
3162
+ * @link http://redis.io/commands/hget
3163
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3164
+ */
3165
+ class HashGet extends Command
3166
+ {
3167
+ /**
3168
+ * {@inheritdoc}
3169
+ */
3170
+ public function getId()
3171
+ {
3172
+ return 'HGET';
3173
+ }
3174
+ }
3175
+
3176
+ /**
3177
+ * @link http://redis.io/commands/save
3178
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3179
+ */
3180
+ class ServerSave extends Command
3181
+ {
3182
+ /**
3183
+ * {@inheritdoc}
3184
+ */
3185
+ public function getId()
3186
+ {
3187
+ return 'SAVE';
3188
+ }
3189
+ }
3190
+
3191
+ /**
3192
+ * @link http://redis.io/commands/script
3193
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3194
+ */
3195
+ class ServerScript extends Command
3196
+ {
3197
+ /**
3198
+ * {@inheritdoc}
3199
+ */
3200
+ public function getId()
3201
+ {
3202
+ return 'SCRIPT';
3203
+ }
3204
+ }
3205
+
3206
+ /**
3207
+ * @link http://redis.io/commands/object
3208
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3209
+ */
3210
+ class ServerObject extends Command
3211
+ {
3212
+ /**
3213
+ * {@inheritdoc}
3214
+ */
3215
+ public function getId()
3216
+ {
3217
+ return 'OBJECT';
3218
+ }
3219
+ }
3220
+
3221
+ /**
3222
+ * @link http://redis.io/commands/monitor
3223
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3224
+ */
3225
+ class ServerMonitor extends Command
3226
+ {
3227
+ /**
3228
+ * {@inheritdoc}
3229
+ */
3230
+ public function getId()
3231
+ {
3232
+ return 'MONITOR';
3233
+ }
3234
+ }
3235
+
3236
+ /**
3237
+ * @link http://redis.io/commands/info
3238
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3239
+ */
3240
+ class ServerInfoV26x extends ServerInfo
3241
+ {
3242
+ /**
3243
+ * {@inheritdoc}
3244
+ */
3245
+ public function parseResponse($data)
3246
+ {
3247
+ $info = array();
3248
+ $current = null;
3249
+ $infoLines = preg_split('/\r?\n/', $data);
3250
+
3251
+ if (isset($infoLines[0]) && $infoLines[0][0] !== '#') {
3252
+ return parent::parseResponse($data);
3253
+ }
3254
+
3255
+ foreach ($infoLines as $row) {
3256
+ if ($row === '') {
3257
+ continue;
3258
+ }
3259
+
3260
+ if (preg_match('/^# (\w+)$/', $row, $matches)) {
3261
+ $info[$matches[1]] = array();
3262
+ $current = &$info[$matches[1]];
3263
+ continue;
3264
+ }
3265
+
3266
+ list($k, $v) = $this->parseRow($row);
3267
+ $current[$k] = $v;
3268
+ }
3269
+
3270
+ return $info;
3271
+ }
3272
+ }
3273
+
3274
+ /**
3275
+ * @link http://redis.io/commands/lastsave
3276
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3277
+ */
3278
+ class ServerLastSave extends Command
3279
+ {
3280
+ /**
3281
+ * {@inheritdoc}
3282
+ */
3283
+ public function getId()
3284
+ {
3285
+ return 'LASTSAVE';
3286
+ }
3287
+ }
3288
+
3289
+ /**
3290
+ * @link http://redis.io/commands/bgsave
3291
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3292
+ */
3293
+ class ServerBackgroundSave extends Command
3294
+ {
3295
+ /**
3296
+ * {@inheritdoc}
3297
+ */
3298
+ public function getId()
3299
+ {
3300
+ return 'BGSAVE';
3301
+ }
3302
+
3303
+ /**
3304
+ * {@inheritdoc}
3305
+ */
3306
+ public function parseResponse($data)
3307
+ {
3308
+ return $data === 'Background saving started' ? true : $data;
3309
+ }
3310
+ }
3311
+
3312
+ /**
3313
+ * @link http://redis.io/commands/bgrewriteaof
3314
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3315
+ */
3316
+ class ServerBackgroundRewriteAOF extends Command
3317
+ {
3318
+ /**
3319
+ * {@inheritdoc}
3320
+ */
3321
+ public function getId()
3322
+ {
3323
+ return 'BGREWRITEAOF';
3324
+ }
3325
+
3326
+ /**
3327
+ * {@inheritdoc}
3328
+ */
3329
+ public function parseResponse($data)
3330
+ {
3331
+ return $data == 'Background append only file rewriting started';
3332
+ }
3333
+ }
3334
+
3335
+ /**
3336
+ * @link http://redis.io/commands/ltrim
3337
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3338
+ */
3339
+ class ListTrim extends Command
3340
+ {
3341
+ /**
3342
+ * {@inheritdoc}
3343
+ */
3344
+ public function getId()
3345
+ {
3346
+ return 'LTRIM';
3347
+ }
3348
+ }
3349
+
3350
+ /**
3351
+ * Defines a command whose keys can be prefixed.
3352
+ *
3353
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3354
+ */
3355
+ interface PrefixableCommandInterface extends CommandInterface
3356
+ {
3357
+ /**
3358
+ * Prefixes all the keys found in the arguments of the command.
3359
+ *
3360
+ * @param string $prefix String used to prefix the keys.
3361
+ */
3362
+ public function prefixKeys($prefix);
3363
+ }
3364
+
3365
+ /**
3366
+ * @link http://redis.io/commands/lset
3367
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3368
+ */
3369
+ class ListSet extends Command
3370
+ {
3371
+ /**
3372
+ * {@inheritdoc}
3373
+ */
3374
+ public function getId()
3375
+ {
3376
+ return 'LSET';
3377
+ }
3378
+ }
3379
+
3380
+ /**
3381
+ * @link http://redis.io/commands/lrem
3382
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3383
+ */
3384
+ class ListRemove extends Command
3385
+ {
3386
+ /**
3387
+ * {@inheritdoc}
3388
+ */
3389
+ public function getId()
3390
+ {
3391
+ return 'LREM';
3392
+ }
3393
+ }
3394
+
3395
+ /**
3396
+ * @link http://redis.io/commands/rpushx
3397
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3398
+ */
3399
+ class ListPushTailX extends Command
3400
+ {
3401
+ /**
3402
+ * {@inheritdoc}
3403
+ */
3404
+ public function getId()
3405
+ {
3406
+ return 'RPUSHX';
3407
+ }
3408
+ }
3409
+
3410
+ /**
3411
+ * @link http://redis.io/commands/lrange
3412
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3413
+ */
3414
+ class ListRange extends Command
3415
+ {
3416
+ /**
3417
+ * {@inheritdoc}
3418
+ */
3419
+ public function getId()
3420
+ {
3421
+ return 'LRANGE';
3422
+ }
3423
+ }
3424
+
3425
+ /**
3426
+ * @link http://redis.io/commands/publish
3427
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3428
+ */
3429
+ class PubSubPublish extends Command
3430
+ {
3431
+ /**
3432
+ * {@inheritdoc}
3433
+ */
3434
+ public function getId()
3435
+ {
3436
+ return 'PUBLISH';
3437
+ }
3438
+ }
3439
+
3440
+ /**
3441
+ * @link http://redis.io/commands/pubsub
3442
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3443
+ */
3444
+ class PubSubPubsub extends Command
3445
+ {
3446
+ /**
3447
+ * {@inheritdoc}
3448
+ */
3449
+ public function getId()
3450
+ {
3451
+ return 'PUBSUB';
3452
+ }
3453
+
3454
+ /**
3455
+ * {@inheritdoc}
3456
+ */
3457
+ public function parseResponse($data)
3458
+ {
3459
+ switch (strtolower($this->getArgument(0))) {
3460
+ case 'numsub':
3461
+ return self::processNumsub($data);
3462
+
3463
+ default:
3464
+ return $data;
3465
+ }
3466
+ }
3467
+
3468
+ /**
3469
+ * Returns the processed response to PUBSUB NUMSUB.
3470
+ *
3471
+ * @param array $channels List of channels
3472
+ *
3473
+ * @return array
3474
+ */
3475
+ protected static function processNumsub(array $channels)
3476
+ {
3477
+ $processed = array();
3478
+ $count = count($channels);
3479
+
3480
+ for ($i = 0; $i < $count; $i++) {
3481
+ $processed[$channels[$i]] = $channels[++$i];
3482
+ }
3483
+
3484
+ return $processed;
3485
+ }
3486
+ }
3487
+
3488
+ /**
3489
+ * Class for generic "anonymous" Redis commands.
3490
+ *
3491
+ * This command class does not filter input arguments or parse responses, but
3492
+ * can be used to leverage the standard Predis API to execute any command simply
3493
+ * by providing the needed arguments following the command signature as defined
3494
+ * by Redis in its documentation.
3495
+ *
3496
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3497
+ */
3498
+ class RawCommand implements CommandInterface
3499
+ {
3500
+ private $slot;
3501
+ private $commandID;
3502
+ private $arguments;
3503
+
3504
+ /**
3505
+ * @param array $arguments Command ID and its arguments.
3506
+ */
3507
+ public function __construct(array $arguments)
3508
+ {
3509
+ if (!$arguments) {
3510
+ throw new InvalidArgumentException(
3511
+ 'The arguments array must contain at least the command ID.'
3512
+ );
3513
+ }
3514
+
3515
+ $this->commandID = strtoupper(array_shift($arguments));
3516
+ $this->arguments = $arguments;
3517
+ }
3518
+
3519
+ /**
3520
+ * Creates a new raw command using a variadic method.
3521
+ *
3522
+ * @param string $commandID Redis command ID.
3523
+ * @param string ... Arguments list for the command.
3524
+ *
3525
+ * @return CommandInterface
3526
+ */
3527
+ public static function create($commandID /* [ $arg, ... */)
3528
+ {
3529
+ $arguments = func_get_args();
3530
+ $command = new self($arguments);
3531
+
3532
+ return $command;
3533
+ }
3534
+
3535
+ /**
3536
+ * {@inheritdoc}
3537
+ */
3538
+ public function getId()
3539
+ {
3540
+ return $this->commandID;
3541
+ }
3542
+
3543
+ /**
3544
+ * {@inheritdoc}
3545
+ */
3546
+ public function setArguments(array $arguments)
3547
+ {
3548
+ $this->arguments = $arguments;
3549
+ unset($this->slot);
3550
+ }
3551
+
3552
+ /**
3553
+ * {@inheritdoc}
3554
+ */
3555
+ public function setRawArguments(array $arguments)
3556
+ {
3557
+ $this->setArguments($arguments);
3558
+ }
3559
+
3560
+ /**
3561
+ * {@inheritdoc}
3562
+ */
3563
+ public function getArguments()
3564
+ {
3565
+ return $this->arguments;
3566
+ }
3567
+
3568
+ /**
3569
+ * {@inheritdoc}
3570
+ */
3571
+ public function getArgument($index)
3572
+ {
3573
+ if (isset($this->arguments[$index])) {
3574
+ return $this->arguments[$index];
3575
+ }
3576
+ }
3577
+
3578
+ /**
3579
+ * {@inheritdoc}
3580
+ */
3581
+ public function setSlot($slot)
3582
+ {
3583
+ $this->slot = $slot;
3584
+ }
3585
+
3586
+ /**
3587
+ * {@inheritdoc}
3588
+ */
3589
+ public function getSlot()
3590
+ {
3591
+ if (isset($this->slot)) {
3592
+ return $this->slot;
3593
+ }
3594
+ }
3595
+
3596
+ /**
3597
+ * {@inheritdoc}
3598
+ */
3599
+ public function parseResponse($data)
3600
+ {
3601
+ return $data;
3602
+ }
3603
+ }
3604
+
3605
+ /**
3606
+ * Base class used to implement an higher level abstraction for commands based
3607
+ * on Lua scripting with EVAL and EVALSHA.
3608
+ *
3609
+ * @link http://redis.io/commands/eval
3610
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3611
+ */
3612
+ abstract class ScriptCommand extends ServerEvalSHA
3613
+ {
3614
+ /**
3615
+ * Gets the body of a Lua script.
3616
+ *
3617
+ * @return string
3618
+ */
3619
+ abstract public function getScript();
3620
+
3621
+ /**
3622
+ * Specifies the number of arguments that should be considered as keys.
3623
+ *
3624
+ * The default behaviour for the base class is to return 0 to indicate that
3625
+ * all the elements of the arguments array should be considered as keys, but
3626
+ * subclasses can enforce a static number of keys.
3627
+ *
3628
+ * @return int
3629
+ */
3630
+ protected function getKeysCount()
3631
+ {
3632
+ return 0;
3633
+ }
3634
+
3635
+ /**
3636
+ * Returns the elements from the arguments that are identified as keys.
3637
+ *
3638
+ * @return array
3639
+ */
3640
+ public function getKeys()
3641
+ {
3642
+ return array_slice($this->getArguments(), 2, $this->getKeysCount());
3643
+ }
3644
+
3645
+ /**
3646
+ * {@inheritdoc}
3647
+ */
3648
+ protected function filterArguments(array $arguments)
3649
+ {
3650
+ if (($numkeys = $this->getKeysCount()) && $numkeys < 0) {
3651
+ $numkeys = count($arguments) + $numkeys;
3652
+ }
3653
+
3654
+ return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments);
3655
+ }
3656
+
3657
+ /**
3658
+ * @return array
3659
+ */
3660
+ public function getEvalArguments()
3661
+ {
3662
+ $arguments = $this->getArguments();
3663
+ $arguments[0] = $this->getScript();
3664
+
3665
+ return $arguments;
3666
+ }
3667
+ }
3668
+
3669
+ /**
3670
+ * @link http://redis.io/commands/punsubscribe
3671
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3672
+ */
3673
+ class PubSubUnsubscribeByPattern extends PubSubUnsubscribe
3674
+ {
3675
+ /**
3676
+ * {@inheritdoc}
3677
+ */
3678
+ public function getId()
3679
+ {
3680
+ return 'PUNSUBSCRIBE';
3681
+ }
3682
+ }
3683
+
3684
+ /**
3685
+ * @link http://redis.io/commands/hincrby
3686
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3687
+ */
3688
+ class HashIncrementBy extends Command
3689
+ {
3690
+ /**
3691
+ * {@inheritdoc}
3692
+ */
3693
+ public function getId()
3694
+ {
3695
+ return 'HINCRBY';
3696
+ }
3697
+ }
3698
+
3699
+ /**
3700
+ * @link http://redis.io/commands/hincrbyfloat
3701
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3702
+ */
3703
+ class HashIncrementByFloat extends Command
3704
+ {
3705
+ /**
3706
+ * {@inheritdoc}
3707
+ */
3708
+ public function getId()
3709
+ {
3710
+ return 'HINCRBYFLOAT';
3711
+ }
3712
+ }
3713
+
3714
+ /**
3715
+ * @link http://redis.io/commands/psubscribe
3716
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3717
+ */
3718
+ class PubSubSubscribeByPattern extends PubSubSubscribe
3719
+ {
3720
+ /**
3721
+ * {@inheritdoc}
3722
+ */
3723
+ public function getId()
3724
+ {
3725
+ return 'PSUBSCRIBE';
3726
+ }
3727
+ }
3728
+
3729
+ /**
3730
+ * @link http://redis.io/commands/hvals
3731
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3732
+ */
3733
+ class HashValues extends Command
3734
+ {
3735
+ /**
3736
+ * {@inheritdoc}
3737
+ */
3738
+ public function getId()
3739
+ {
3740
+ return 'HVALS';
3741
+ }
3742
+ }
3743
+
3744
+ /* --------------------------------------------------------------------------- */
3745
+
3746
+ namespace Predis\Connection;
3747
+
3748
+ use InvalidArgumentException;
3749
+ use Predis\CommunicationException;
3750
+ use Predis\Command\CommandInterface;
3751
+ use Predis\Protocol\ProtocolException;
3752
+ use Predis\Protocol\ProtocolProcessorInterface;
3753
+ use Predis\Protocol\Text\ProtocolProcessor as TextProtocolProcessor;
3754
+ use UnexpectedValueException;
3755
+ use ReflectionClass;
3756
+ use Predis\Command\RawCommand;
3757
+ use Predis\NotSupportedException;
3758
+ use Predis\Response\Error as ErrorResponse;
3759
+ use Predis\Response\Status as StatusResponse;
3760
+
3761
+ /**
3762
+ * Defines a connection object used to communicate with one or multiple
3763
+ * Redis servers.
3764
+ *
3765
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3766
+ */
3767
+ interface ConnectionInterface
3768
+ {
3769
+ /**
3770
+ * Opens the connection to Redis.
3771
+ */
3772
+ public function connect();
3773
+
3774
+ /**
3775
+ * Closes the connection to Redis.
3776
+ */
3777
+ public function disconnect();
3778
+
3779
+ /**
3780
+ * Checks if the connection to Redis is considered open.
3781
+ *
3782
+ * @return bool
3783
+ */
3784
+ public function isConnected();
3785
+
3786
+ /**
3787
+ * Writes the request for the given command over the connection.
3788
+ *
3789
+ * @param CommandInterface $command Command instance.
3790
+ */
3791
+ public function writeRequest(CommandInterface $command);
3792
+
3793
+ /**
3794
+ * Reads the response to the given command from the connection.
3795
+ *
3796
+ * @param CommandInterface $command Command instance.
3797
+ *
3798
+ * @return mixed
3799
+ */
3800
+ public function readResponse(CommandInterface $command);
3801
+
3802
+ /**
3803
+ * Writes a request for the given command over the connection and reads back
3804
+ * the response returned by Redis.
3805
+ *
3806
+ * @param CommandInterface $command Command instance.
3807
+ *
3808
+ * @return mixed
3809
+ */
3810
+ public function executeCommand(CommandInterface $command);
3811
+ }
3812
+
3813
+ /**
3814
+ * Defines a connection used to communicate with a single Redis node.
3815
+ *
3816
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3817
+ */
3818
+ interface NodeConnectionInterface extends ConnectionInterface
3819
+ {
3820
+ /**
3821
+ * Returns a string representation of the connection.
3822
+ *
3823
+ * @return string
3824
+ */
3825
+ public function __toString();
3826
+
3827
+ /**
3828
+ * Returns the underlying resource used to communicate with Redis.
3829
+ *
3830
+ * @return mixed
3831
+ */
3832
+ public function getResource();
3833
+
3834
+ /**
3835
+ * Returns the parameters used to initialize the connection.
3836
+ *
3837
+ * @return ParametersInterface
3838
+ */
3839
+ public function getParameters();
3840
+
3841
+ /**
3842
+ * Pushes the given command into a queue of commands executed when
3843
+ * establishing the actual connection to Redis.
3844
+ *
3845
+ * @param CommandInterface $command Instance of a Redis command.
3846
+ */
3847
+ public function addConnectCommand(CommandInterface $command);
3848
+
3849
+ /**
3850
+ * Reads a response from the server.
3851
+ *
3852
+ * @return mixed
3853
+ */
3854
+ public function read();
3855
+ }
3856
+
3857
+ /**
3858
+ * Defines a virtual connection composed of multiple connection instances to
3859
+ * single Redis nodes.
3860
+ *
3861
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3862
+ */
3863
+ interface AggregateConnectionInterface extends ConnectionInterface
3864
+ {
3865
+ /**
3866
+ * Adds a connection instance to the aggregate connection.
3867
+ *
3868
+ * @param NodeConnectionInterface $connection Connection instance.
3869
+ */
3870
+ public function add(NodeConnectionInterface $connection);
3871
+
3872
+ /**
3873
+ * Removes the specified connection instance from the aggregate connection.
3874
+ *
3875
+ * @param NodeConnectionInterface $connection Connection instance.
3876
+ *
3877
+ * @return bool Returns true if the connection was in the pool.
3878
+ */
3879
+ public function remove(NodeConnectionInterface $connection);
3880
+
3881
+ /**
3882
+ * Returns the connection instance in charge for the given command.
3883
+ *
3884
+ * @param CommandInterface $command Command instance.
3885
+ *
3886
+ * @return NodeConnectionInterface
3887
+ */
3888
+ public function getConnection(CommandInterface $command);
3889
+
3890
+ /**
3891
+ * Returns a connection instance from the aggregate connection by its alias.
3892
+ *
3893
+ * @param string $connectionID Connection alias.
3894
+ *
3895
+ * @return NodeConnectionInterface|null
3896
+ */
3897
+ public function getConnectionById($connectionID);
3898
+ }
3899
+
3900
+ /**
3901
+ * Base class with the common logic used by connection classes to communicate
3902
+ * with Redis.
3903
+ *
3904
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3905
+ */
3906
+ abstract class AbstractConnection implements NodeConnectionInterface
3907
+ {
3908
+ private $resource;
3909
+ private $cachedId;
3910
+
3911
+ protected $parameters;
3912
+ protected $initCommands = array();
3913
+
3914
+ /**
3915
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
3916
+ */
3917
+ public function __construct(ParametersInterface $parameters)
3918
+ {
3919
+ $this->parameters = $this->assertParameters($parameters);
3920
+ }
3921
+
3922
+ /**
3923
+ * Disconnects from the server and destroys the underlying resource when
3924
+ * PHP's garbage collector kicks in.
3925
+ */
3926
+ public function __destruct()
3927
+ {
3928
+ $this->disconnect();
3929
+ }
3930
+
3931
+ /**
3932
+ * Checks some of the parameters used to initialize the connection.
3933
+ *
3934
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
3935
+ *
3936
+ * @return ParametersInterface
3937
+ */
3938
+ protected function assertParameters(ParametersInterface $parameters)
3939
+ {
3940
+ $scheme = $parameters->scheme;
3941
+
3942
+ if ($scheme !== 'tcp' && $scheme !== 'unix') {
3943
+ throw new InvalidArgumentException("Invalid scheme: '$scheme'.");
3944
+ }
3945
+
3946
+ if ($scheme === 'unix' && !isset($parameters->path)) {
3947
+ throw new InvalidArgumentException('Missing UNIX domain socket path.');
3948
+ }
3949
+
3950
+ return $parameters;
3951
+ }
3952
+
3953
+ /**
3954
+ * Creates the underlying resource used to communicate with Redis.
3955
+ *
3956
+ * @return mixed
3957
+ */
3958
+ abstract protected function createResource();
3959
+
3960
+ /**
3961
+ * {@inheritdoc}
3962
+ */
3963
+ public function isConnected()
3964
+ {
3965
+ return isset($this->resource);
3966
+ }
3967
+
3968
+ /**
3969
+ * {@inheritdoc}
3970
+ */
3971
+ public function connect()
3972
+ {
3973
+ if (!$this->isConnected()) {
3974
+ $this->resource = $this->createResource();
3975
+
3976
+ return true;
3977
+ }
3978
+
3979
+ return false;
3980
+ }
3981
+
3982
+ /**
3983
+ * {@inheritdoc}
3984
+ */
3985
+ public function disconnect()
3986
+ {
3987
+ unset($this->resource);
3988
+ }
3989
+
3990
+ /**
3991
+ * {@inheritdoc}
3992
+ */
3993
+ public function addConnectCommand(CommandInterface $command)
3994
+ {
3995
+ $this->initCommands[] = $command;
3996
+ }
3997
+
3998
+ /**
3999
+ * {@inheritdoc}
4000
+ */
4001
+ public function executeCommand(CommandInterface $command)
4002
+ {
4003
+ $this->writeRequest($command);
4004
+
4005
+ return $this->readResponse($command);
4006
+ }
4007
+
4008
+ /**
4009
+ * {@inheritdoc}
4010
+ */
4011
+ public function readResponse(CommandInterface $command)
4012
+ {
4013
+ return $this->read();
4014
+ }
4015
+
4016
+ /**
4017
+ * Helper method to handle connection errors.
4018
+ *
4019
+ * @param string $message Error message.
4020
+ * @param int $code Error code.
4021
+ */
4022
+ protected function onConnectionError($message, $code = null)
4023
+ {
4024
+ CommunicationException::handle(
4025
+ new ConnectionException(
4026
+ $this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]", $code
4027
+ )
4028
+ );
4029
+ }
4030
+
4031
+ /**
4032
+ * Helper method to handle protocol errors.
4033
+ *
4034
+ * @param string $message Error message.
4035
+ */
4036
+ protected function onProtocolError($message)
4037
+ {
4038
+ CommunicationException::handle(
4039
+ new ProtocolException(
4040
+ $this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]"
4041
+ )
4042
+ );
4043
+ }
4044
+
4045
+ /**
4046
+ * {@inheritdoc}
4047
+ */
4048
+ public function getResource()
4049
+ {
4050
+ if (isset($this->resource)) {
4051
+ return $this->resource;
4052
+ }
4053
+
4054
+ $this->connect();
4055
+
4056
+ return $this->resource;
4057
+ }
4058
+
4059
+ /**
4060
+ * {@inheritdoc}
4061
+ */
4062
+ public function getParameters()
4063
+ {
4064
+ return $this->parameters;
4065
+ }
4066
+
4067
+ /**
4068
+ * Gets an identifier for the connection.
4069
+ *
4070
+ * @return string
4071
+ */
4072
+ protected function getIdentifier()
4073
+ {
4074
+ if ($this->parameters->scheme === 'unix') {
4075
+ return $this->parameters->path;
4076
+ }
4077
+
4078
+ return "{$this->parameters->host}:{$this->parameters->port}";
4079
+ }
4080
+
4081
+ /**
4082
+ * {@inheritdoc}
4083
+ */
4084
+ public function __toString()
4085
+ {
4086
+ if (!isset($this->cachedId)) {
4087
+ $this->cachedId = $this->getIdentifier();
4088
+ }
4089
+
4090
+ return $this->cachedId;
4091
+ }
4092
+
4093
+ /**
4094
+ * {@inheritdoc}
4095
+ */
4096
+ public function __sleep()
4097
+ {
4098
+ return array('parameters', 'initCommands');
4099
+ }
4100
+ }
4101
+
4102
+ /**
4103
+ * Standard connection to Redis servers implemented on top of PHP's streams.
4104
+ * The connection parameters supported by this class are:
4105
+ *
4106
+ * - scheme: it can be either 'tcp' or 'unix'.
4107
+ * - host: hostname or IP address of the server.
4108
+ * - port: TCP port of the server.
4109
+ * - path: path of a UNIX domain socket when scheme is 'unix'.
4110
+ * - timeout: timeout to perform the connection.
4111
+ * - read_write_timeout: timeout of read / write operations.
4112
+ * - async_connect: performs the connection asynchronously.
4113
+ * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
4114
+ * - persistent: the connection is left intact after a GC collection.
4115
+ *
4116
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4117
+ */
4118
+ class StreamConnection extends AbstractConnection
4119
+ {
4120
+ /**
4121
+ * Disconnects from the server and destroys the underlying resource when the
4122
+ * garbage collector kicks in only if the connection has not been marked as
4123
+ * persistent.
4124
+ */
4125
+ public function __destruct()
4126
+ {
4127
+ if (isset($this->parameters->persistent) && $this->parameters->persistent) {
4128
+ return;
4129
+ }
4130
+
4131
+ $this->disconnect();
4132
+ }
4133
+
4134
+ /**
4135
+ * {@inheritdoc}
4136
+ */
4137
+ protected function createResource()
4138
+ {
4139
+ $initializer = "{$this->parameters->scheme}StreamInitializer";
4140
+ $resource = $this->$initializer($this->parameters);
4141
+
4142
+ return $resource;
4143
+ }
4144
+
4145
+ /**
4146
+ * Initializes a TCP stream resource.
4147
+ *
4148
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4149
+ *
4150
+ * @return resource
4151
+ */
4152
+ protected function tcpStreamInitializer(ParametersInterface $parameters)
4153
+ {
4154
+ $uri = "tcp://{$parameters->host}:{$parameters->port}";
4155
+ $flags = STREAM_CLIENT_CONNECT;
4156
+
4157
+ if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
4158
+ $flags |= STREAM_CLIENT_ASYNC_CONNECT;
4159
+ }
4160
+
4161
+ if (isset($parameters->persistent) && (bool) $parameters->persistent) {
4162
+ $flags |= STREAM_CLIENT_PERSISTENT;
4163
+ $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
4164
+ }
4165
+
4166
+ $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4167
+
4168
+ if (!$resource) {
4169
+ $this->onConnectionError(trim($errstr), $errno);
4170
+ }
4171
+
4172
+ if (isset($parameters->read_write_timeout)) {
4173
+ $rwtimeout = (float) $parameters->read_write_timeout;
4174
+ $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
4175
+ $timeoutSeconds = floor($rwtimeout);
4176
+ $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
4177
+ stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
4178
+ }
4179
+
4180
+ if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
4181
+ $socket = socket_import_stream($resource);
4182
+ socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
4183
+ }
4184
+
4185
+ return $resource;
4186
+ }
4187
+
4188
+ /**
4189
+ * Initializes a UNIX stream resource.
4190
+ *
4191
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4192
+ *
4193
+ * @return resource
4194
+ */
4195
+ protected function unixStreamInitializer(ParametersInterface $parameters)
4196
+ {
4197
+ $uri = "unix://{$parameters->path}";
4198
+ $flags = STREAM_CLIENT_CONNECT;
4199
+
4200
+ if ((bool) $parameters->persistent) {
4201
+ $flags |= STREAM_CLIENT_PERSISTENT;
4202
+ }
4203
+
4204
+ $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4205
+
4206
+ if (!$resource) {
4207
+ $this->onConnectionError(trim($errstr), $errno);
4208
+ }
4209
+
4210
+ return $resource;
4211
+ }
4212
+
4213
+ /**
4214
+ * {@inheritdoc}
4215
+ */
4216
+ public function connect()
4217
+ {
4218
+ if (parent::connect() && $this->initCommands) {
4219
+ foreach ($this->initCommands as $command) {
4220
+ $this->executeCommand($command);
4221
+ }
4222
+ }
4223
+ }
4224
+
4225
+ /**
4226
+ * {@inheritdoc}
4227
+ */
4228
+ public function disconnect()
4229
+ {
4230
+ if ($this->isConnected()) {
4231
+ fclose($this->getResource());
4232
+ parent::disconnect();
4233
+ }
4234
+ }
4235
+
4236
+ /**
4237
+ * Performs a write operation over the stream of the buffer containing a
4238
+ * command serialized with the Redis wire protocol.
4239
+ *
4240
+ * @param string $buffer Representation of a command in the Redis wire protocol.
4241
+ */
4242
+ protected function write($buffer)
4243
+ {
4244
+ $socket = $this->getResource();
4245
+
4246
+ while (($length = strlen($buffer)) > 0) {
4247
+ $written = @fwrite($socket, $buffer);
4248
+
4249
+ if ($length === $written) {
4250
+ return;
4251
+ }
4252
+
4253
+ if ($written === false || $written === 0) {
4254
+ $this->onConnectionError('Error while writing bytes to the server.');
4255
+ }
4256
+
4257
+ $buffer = substr($buffer, $written);
4258
+ }
4259
+ }
4260
+
4261
+ /**
4262
+ * {@inheritdoc}
4263
+ */
4264
+ public function read()
4265
+ {
4266
+ $socket = $this->getResource();
4267
+ $chunk = fgets($socket);
4268
+
4269
+ if ($chunk === false || $chunk === '') {
4270
+ $this->onConnectionError('Error while reading line from the server.');
4271
+ }
4272
+
4273
+ $prefix = $chunk[0];
4274
+ $payload = substr($chunk, 1, -2);
4275
+
4276
+ switch ($prefix) {
4277
+ case '+':
4278
+ return StatusResponse::get($payload);
4279
+
4280
+ case '$':
4281
+ $size = (int) $payload;
4282
+
4283
+ if ($size === -1) {
4284
+ return null;
4285
+ }
4286
+
4287
+ $bulkData = '';
4288
+ $bytesLeft = ($size += 2);
4289
+
4290
+ do {
4291
+ $chunk = fread($socket, min($bytesLeft, 4096));
4292
+
4293
+ if ($chunk === false || $chunk === '') {
4294
+ $this->onConnectionError('Error while reading bytes from the server.');
4295
+ }
4296
+
4297
+ $bulkData .= $chunk;
4298
+ $bytesLeft = $size - strlen($bulkData);
4299
+ } while ($bytesLeft > 0);
4300
+
4301
+ return substr($bulkData, 0, -2);
4302
+
4303
+ case '*':
4304
+ $count = (int) $payload;
4305
+
4306
+ if ($count === -1) {
4307
+ return null;
4308
+ }
4309
+
4310
+ $multibulk = array();
4311
+
4312
+ for ($i = 0; $i < $count; $i++) {
4313
+ $multibulk[$i] = $this->read();
4314
+ }
4315
+
4316
+ return $multibulk;
4317
+
4318
+ case ':':
4319
+ return (int) $payload;
4320
+
4321
+ case '-':
4322
+ return new ErrorResponse($payload);
4323
+
4324
+ default:
4325
+ $this->onProtocolError("Unknown response prefix: '$prefix'.");
4326
+
4327
+ return;
4328
+ }
4329
+ }
4330
+
4331
+ /**
4332
+ * {@inheritdoc}
4333
+ */
4334
+ public function writeRequest(CommandInterface $command)
4335
+ {
4336
+ $commandID = $command->getId();
4337
+ $arguments = $command->getArguments();
4338
+
4339
+ $cmdlen = strlen($commandID);
4340
+ $reqlen = count($arguments) + 1;
4341
+
4342
+ $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
4343
+
4344
+ for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
4345
+ $argument = $arguments[$i];
4346
+ $arglen = strlen($argument);
4347
+ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
4348
+ }
4349
+
4350
+ $this->write($buffer);
4351
+ }
4352
+ }
4353
+
4354
+ /**
4355
+ * Interface defining a container for connection parameters.
4356
+ *
4357
+ * The actual list of connection parameters depends on the features supported by
4358
+ * each connection backend class (please refer to their specific documentation),
4359
+ * but the most common parameters used through the library are:
4360
+ *
4361
+ * @property-read string scheme Connection scheme, such as 'tcp' or 'unix'.
4362
+ * @property-read string host IP address or hostname of Redis.
4363
+ * @property-read int port TCP port on which Redis is listening to.
4364
+ * @property-read string path Path of a UNIX domain socket file.
4365
+ * @property-read string alias Alias for the connection.
4366
+ * @property-read float timeout Timeout for the connect() operation.
4367
+ * @property-read float read_write_timeout Timeout for read() and write() operations.
4368
+ * @property-read bool async_connect Performs the connect() operation asynchronously.
4369
+ * @property-read bool tcp_nodelay Toggles the Nagle's algorithm for coalescing.
4370
+ * @property-read bool persistent Leaves the connection open after a GC collection.
4371
+ * @property-read string password Password to access Redis (see the AUTH command).
4372
+ * @property-read string database Database index (see the SELECT command).
4373
+ *
4374
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4375
+ */
4376
+ interface ParametersInterface
4377
+ {
4378
+ /**
4379
+ * Checks if the specified parameters is set.
4380
+ *
4381
+ * @param string $parameter Name of the parameter.
4382
+ *
4383
+ * @return bool
4384
+ */
4385
+ public function __isset($parameter);
4386
+
4387
+ /**
4388
+ * Returns the value of the specified parameter.
4389
+ *
4390
+ * @param string $parameter Name of the parameter.
4391
+ *
4392
+ * @return mixed|null
4393
+ */
4394
+ public function __get($parameter);
4395
+
4396
+ /**
4397
+ * Returns an array representation of the connection parameters.
4398
+ *
4399
+ * @return array
4400
+ */
4401
+ public function toArray();
4402
+ }
4403
+
4404
+ /**
4405
+ * Interface for classes providing a factory of connections to Redis nodes.
4406
+ *
4407
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4408
+ */
4409
+ interface FactoryInterface
4410
+ {
4411
+ /**
4412
+ * Defines or overrides the connection class identified by a scheme prefix.
4413
+ *
4414
+ * @param string $scheme Target connection scheme.
4415
+ * @param mixed $initializer Fully-qualified name of a class or a callable for lazy initialization.
4416
+ */
4417
+ public function define($scheme, $initializer);
4418
+
4419
+ /**
4420
+ * Undefines the connection identified by a scheme prefix.
4421
+ *
4422
+ * @param string $scheme Target connection scheme.
4423
+ */
4424
+ public function undefine($scheme);
4425
+
4426
+ /**
4427
+ * Creates a new connection object.
4428
+ *
4429
+ * @param mixed $parameters Initialization parameters for the connection.
4430
+ *
4431
+ * @return NodeConnectionInterface
4432
+ */
4433
+ public function create($parameters);
4434
+
4435
+ /**
4436
+ * Aggregates single connections into an aggregate connection instance.
4437
+ *
4438
+ * @param AggregateConnectionInterface $aggregate Aggregate connection instance.
4439
+ * @param array $parameters List of parameters for each connection.
4440
+ */
4441
+ public function aggregate(AggregateConnectionInterface $aggregate, array $parameters);
4442
+ }
4443
+
4444
+ /**
4445
+ * Defines a connection to communicate with a single Redis server that leverages
4446
+ * an external protocol processor to handle pluggable protocol handlers.
4447
+ *
4448
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4449
+ */
4450
+ interface CompositeConnectionInterface extends NodeConnectionInterface
4451
+ {
4452
+ /**
4453
+ * Returns the protocol processor used by the connection.
4454
+ */
4455
+ public function getProtocol();
4456
+
4457
+ /**
4458
+ * Writes the buffer containing over the connection.
4459
+ *
4460
+ * @param string $buffer String buffer to be sent over the connection.
4461
+ */
4462
+ public function writeBuffer($buffer);
4463
+
4464
+ /**
4465
+ * Reads the given number of bytes from the connection.
4466
+ *
4467
+ * @param int $length Number of bytes to read from the connection.
4468
+ *
4469
+ * @return string
4470
+ */
4471
+ public function readBuffer($length);
4472
+
4473
+ /**
4474
+ * Reads a line from the connection.
4475
+ *
4476
+ * @param string
4477
+ */
4478
+ public function readLine();
4479
+ }
4480
+
4481
+ /**
4482
+ * This class provides the implementation of a Predis connection that uses PHP's
4483
+ * streams for network communication and wraps the phpiredis C extension (PHP
4484
+ * bindings for hiredis) to parse and serialize the Redis protocol.
4485
+ *
4486
+ * This class is intended to provide an optional low-overhead alternative for
4487
+ * processing responses from Redis compared to the standard pure-PHP classes.
4488
+ * Differences in speed when dealing with short inline responses are practically
4489
+ * nonexistent, the actual speed boost is for big multibulk responses when this
4490
+ * protocol processor can parse and return responses very fast.
4491
+ *
4492
+ * For instructions on how to build and install the phpiredis extension, please
4493
+ * consult the repository of the project.
4494
+ *
4495
+ * The connection parameters supported by this class are:
4496
+ *
4497
+ * - scheme: it can be either 'tcp' or 'unix'.
4498
+ * - host: hostname or IP address of the server.
4499
+ * - port: TCP port of the server.
4500
+ * - path: path of a UNIX domain socket when scheme is 'unix'.
4501
+ * - timeout: timeout to perform the connection.
4502
+ * - read_write_timeout: timeout of read / write operations.
4503
+ * - async_connect: performs the connection asynchronously.
4504
+ * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
4505
+ * - persistent: the connection is left intact after a GC collection.
4506
+ *
4507
+ * @link https://github.com/nrk/phpiredis
4508
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4509
+ */
4510
+ class PhpiredisStreamConnection extends StreamConnection
4511
+ {
4512
+ private $reader;
4513
+
4514
+ /**
4515
+ * {@inheritdoc}
4516
+ */
4517
+ public function __construct(ParametersInterface $parameters)
4518
+ {
4519
+ $this->assertExtensions();
4520
+
4521
+ parent::__construct($parameters);
4522
+
4523
+ $this->reader = $this->createReader();
4524
+ }
4525
+
4526
+ /**
4527
+ * {@inheritdoc}
4528
+ */
4529
+ public function __destruct()
4530
+ {
4531
+ phpiredis_reader_destroy($this->reader);
4532
+
4533
+ parent::__destruct();
4534
+ }
4535
+
4536
+ /**
4537
+ * Checks if the phpiredis extension is loaded in PHP.
4538
+ */
4539
+ private function assertExtensions()
4540
+ {
4541
+ if (!extension_loaded('phpiredis')) {
4542
+ throw new NotSupportedException(
4543
+ 'The "phpiredis" extension is required by this connection backend.'
4544
+ );
4545
+ }
4546
+ }
4547
+
4548
+ /**
4549
+ * {@inheritdoc}
4550
+ */
4551
+ protected function tcpStreamInitializer(ParametersInterface $parameters)
4552
+ {
4553
+ $uri = "tcp://{$parameters->host}:{$parameters->port}";
4554
+ $flags = STREAM_CLIENT_CONNECT;
4555
+ $socket = null;
4556
+
4557
+ if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
4558
+ $flags |= STREAM_CLIENT_ASYNC_CONNECT;
4559
+ }
4560
+
4561
+ if (isset($parameters->persistent) && (bool) $parameters->persistent) {
4562
+ $flags |= STREAM_CLIENT_PERSISTENT;
4563
+ $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
4564
+ }
4565
+
4566
+ $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4567
+
4568
+ if (!$resource) {
4569
+ $this->onConnectionError(trim($errstr), $errno);
4570
+ }
4571
+
4572
+ if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
4573
+ $rwtimeout = (float) $parameters->read_write_timeout;
4574
+ $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
4575
+
4576
+ $timeout = array(
4577
+ 'sec' => $timeoutSeconds = floor($rwtimeout),
4578
+ 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
4579
+ );
4580
+
4581
+ $socket = $socket ?: socket_import_stream($resource);
4582
+ @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
4583
+ @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
4584
+ }
4585
+
4586
+ if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
4587
+ $socket = $socket ?: socket_import_stream($resource);
4588
+ socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
4589
+ }
4590
+
4591
+ return $resource;
4592
+ }
4593
+
4594
+ /**
4595
+ * Creates a new instance of the protocol reader resource.
4596
+ *
4597
+ * @return resource
4598
+ */
4599
+ private function createReader()
4600
+ {
4601
+ $reader = phpiredis_reader_create();
4602
+
4603
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
4604
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
4605
+
4606
+ return $reader;
4607
+ }
4608
+
4609
+ /**
4610
+ * Returns the underlying protocol reader resource.
4611
+ *
4612
+ * @return resource
4613
+ */
4614
+ protected function getReader()
4615
+ {
4616
+ return $this->reader;
4617
+ }
4618
+
4619
+ /**
4620
+ * Returns the handler used by the protocol reader for inline responses.
4621
+ *
4622
+ * @return \Closure
4623
+ */
4624
+ protected function getStatusHandler()
4625
+ {
4626
+ return function ($payload) {
4627
+ return StatusResponse::get($payload);
4628
+ };
4629
+ }
4630
+
4631
+ /**
4632
+ * Returns the handler used by the protocol reader for error responses.
4633
+ *
4634
+ * @return \Closure
4635
+ */
4636
+ protected function getErrorHandler()
4637
+ {
4638
+ return function ($errorMessage) {
4639
+ return new ErrorResponse($errorMessage);
4640
+ };
4641
+ }
4642
+
4643
+ /**
4644
+ * {@inheritdoc}
4645
+ */
4646
+ public function read()
4647
+ {
4648
+ $socket = $this->getResource();
4649
+ $reader = $this->reader;
4650
+
4651
+ while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
4652
+ $buffer = stream_socket_recvfrom($socket, 4096);
4653
+
4654
+ if ($buffer === false || $buffer === '') {
4655
+ $this->onConnectionError('Error while reading bytes from the server.');
4656
+ }
4657
+
4658
+ phpiredis_reader_feed($reader, $buffer);
4659
+ }
4660
+
4661
+ if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
4662
+ return phpiredis_reader_get_reply($reader);
4663
+ } else {
4664
+ $this->onProtocolError(phpiredis_reader_get_error($reader));
4665
+
4666
+ return;
4667
+ }
4668
+ }
4669
+
4670
+ /**
4671
+ * {@inheritdoc}
4672
+ */
4673
+ public function writeRequest(CommandInterface $command)
4674
+ {
4675
+ $arguments = $command->getArguments();
4676
+ array_unshift($arguments, $command->getId());
4677
+
4678
+ $this->write(phpiredis_format_command($arguments));
4679
+ }
4680
+
4681
+ /**
4682
+ * {@inheritdoc}
4683
+ */
4684
+ public function __wakeup()
4685
+ {
4686
+ $this->assertExtensions();
4687
+ $this->reader = $this->createReader();
4688
+ }
4689
+ }
4690
+
4691
+ /**
4692
+ * This class implements a Predis connection that actually talks with Webdis
4693
+ * instead of connecting directly to Redis. It relies on the cURL extension to
4694
+ * communicate with the web server and the phpiredis extension to parse the
4695
+ * protocol for responses returned in the http response bodies.
4696
+ *
4697
+ * Some features are not yet available or they simply cannot be implemented:
4698
+ * - Pipelining commands.
4699
+ * - Publish / Subscribe.
4700
+ * - MULTI / EXEC transactions (not yet supported by Webdis).
4701
+ *
4702
+ * The connection parameters supported by this class are:
4703
+ *
4704
+ * - scheme: must be 'http'.
4705
+ * - host: hostname or IP address of the server.
4706
+ * - port: TCP port of the server.
4707
+ * - timeout: timeout to perform the connection.
4708
+ * - user: username for authentication.
4709
+ * - pass: password for authentication.
4710
+ *
4711
+ * @link http://webd.is
4712
+ * @link http://github.com/nicolasff/webdis
4713
+ * @link http://github.com/seppo0010/phpiredis
4714
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4715
+ */
4716
+ class WebdisConnection implements NodeConnectionInterface
4717
+ {
4718
+ private $parameters;
4719
+ private $resource;
4720
+ private $reader;
4721
+
4722
+ /**
4723
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4724
+ */
4725
+ public function __construct(ParametersInterface $parameters)
4726
+ {
4727
+ $this->assertExtensions();
4728
+
4729
+ if ($parameters->scheme !== 'http') {
4730
+ throw new InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'.");
4731
+ }
4732
+
4733
+ $this->parameters = $parameters;
4734
+
4735
+ $this->resource = $this->createCurl();
4736
+ $this->reader = $this->createReader();
4737
+ }
4738
+
4739
+ /**
4740
+ * Frees the underlying cURL and protocol reader resources when the garbage
4741
+ * collector kicks in.
4742
+ */
4743
+ public function __destruct()
4744
+ {
4745
+ curl_close($this->resource);
4746
+ phpiredis_reader_destroy($this->reader);
4747
+ }
4748
+
4749
+ /**
4750
+ * Helper method used to throw on unsupported methods.
4751
+ *
4752
+ * @param string $method Name of the unsupported method.
4753
+ *
4754
+ * @throws NotSupportedException
4755
+ */
4756
+ private function throwNotSupportedException($method)
4757
+ {
4758
+ $class = __CLASS__;
4759
+ throw new NotSupportedException("The method $class::$method() is not supported.");
4760
+ }
4761
+
4762
+ /**
4763
+ * Checks if the cURL and phpiredis extensions are loaded in PHP.
4764
+ */
4765
+ private function assertExtensions()
4766
+ {
4767
+ if (!extension_loaded('curl')) {
4768
+ throw new NotSupportedException(
4769
+ 'The "curl" extension is required by this connection backend.'
4770
+ );
4771
+ }
4772
+
4773
+ if (!extension_loaded('phpiredis')) {
4774
+ throw new NotSupportedException(
4775
+ 'The "phpiredis" extension is required by this connection backend.'
4776
+ );
4777
+ }
4778
+ }
4779
+
4780
+ /**
4781
+ * Initializes cURL.
4782
+ *
4783
+ * @return resource
4784
+ */
4785
+ private function createCurl()
4786
+ {
4787
+ $parameters = $this->getParameters();
4788
+
4789
+ $options = array(
4790
+ CURLOPT_FAILONERROR => true,
4791
+ CURLOPT_CONNECTTIMEOUT_MS => $parameters->timeout * 1000,
4792
+ CURLOPT_URL => "{$parameters->scheme}://{$parameters->host}:{$parameters->port}",
4793
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
4794
+ CURLOPT_POST => true,
4795
+ CURLOPT_WRITEFUNCTION => array($this, 'feedReader'),
4796
+ );
4797
+
4798
+ if (isset($parameters->user, $parameters->pass)) {
4799
+ $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}";
4800
+ }
4801
+
4802
+ curl_setopt_array($resource = curl_init(), $options);
4803
+
4804
+ return $resource;
4805
+ }
4806
+
4807
+ /**
4808
+ * Initializes the phpiredis protocol reader.
4809
+ *
4810
+ * @return resource
4811
+ */
4812
+ private function createReader()
4813
+ {
4814
+ $reader = phpiredis_reader_create();
4815
+
4816
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
4817
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
4818
+
4819
+ return $reader;
4820
+ }
4821
+
4822
+ /**
4823
+ * Returns the handler used by the protocol reader for inline responses.
4824
+ *
4825
+ * @return \Closure
4826
+ */
4827
+ protected function getStatusHandler()
4828
+ {
4829
+ return function ($payload) {
4830
+ return StatusResponse::get($payload);
4831
+ };
4832
+ }
4833
+
4834
+ /**
4835
+ * Returns the handler used by the protocol reader for error responses.
4836
+ *
4837
+ * @return \Closure
4838
+ */
4839
+ protected function getErrorHandler()
4840
+ {
4841
+ return function ($payload) {
4842
+ return new ErrorResponse($payload);
4843
+ };
4844
+ }
4845
+
4846
+ /**
4847
+ * Feeds the phpredis reader resource with the data read from the network.
4848
+ *
4849
+ * @param resource $resource Reader resource.
4850
+ * @param string $buffer Buffer of data read from a connection.
4851
+ *
4852
+ * @return int
4853
+ */
4854
+ protected function feedReader($resource, $buffer)
4855
+ {
4856
+ phpiredis_reader_feed($this->reader, $buffer);
4857
+
4858
+ return strlen($buffer);
4859
+ }
4860
+
4861
+ /**
4862
+ * {@inheritdoc}
4863
+ */
4864
+ public function connect()
4865
+ {
4866
+ // NOOP
4867
+ }
4868
+
4869
+ /**
4870
+ * {@inheritdoc}
4871
+ */
4872
+ public function disconnect()
4873
+ {
4874
+ // NOOP
4875
+ }
4876
+
4877
+ /**
4878
+ * {@inheritdoc}
4879
+ */
4880
+ public function isConnected()
4881
+ {
4882
+ return true;
4883
+ }
4884
+
4885
+ /**
4886
+ * Checks if the specified command is supported by this connection class.
4887
+ *
4888
+ * @param CommandInterface $command Command instance.
4889
+ *
4890
+ * @return string
4891
+ *
4892
+ * @throws NotSupportedException
4893
+ */
4894
+ protected function getCommandId(CommandInterface $command)
4895
+ {
4896
+ switch ($commandID = $command->getId()) {
4897
+ case 'AUTH':
4898
+ case 'SELECT':
4899
+ case 'MULTI':
4900
+ case 'EXEC':
4901
+ case 'WATCH':
4902
+ case 'UNWATCH':
4903
+ case 'DISCARD':
4904
+ case 'MONITOR':
4905
+ throw new NotSupportedException("Command '$commandID' is not allowed by Webdis.");
4906
+
4907
+ default:
4908
+ return $commandID;
4909
+ }
4910
+ }
4911
+
4912
+ /**
4913
+ * {@inheritdoc}
4914
+ */
4915
+ public function writeRequest(CommandInterface $command)
4916
+ {
4917
+ $this->throwNotSupportedException(__FUNCTION__);
4918
+ }
4919
+
4920
+ /**
4921
+ * {@inheritdoc}
4922
+ */
4923
+ public function readResponse(CommandInterface $command)
4924
+ {
4925
+ $this->throwNotSupportedException(__FUNCTION__);
4926
+ }
4927
+
4928
+ /**
4929
+ * {@inheritdoc}
4930
+ */
4931
+ public function executeCommand(CommandInterface $command)
4932
+ {
4933
+ $resource = $this->resource;
4934
+ $commandId = $this->getCommandId($command);
4935
+
4936
+ if ($arguments = $command->getArguments()) {
4937
+ $arguments = implode('/', array_map('urlencode', $arguments));
4938
+ $serializedCommand = "$commandId/$arguments.raw";
4939
+ } else {
4940
+ $serializedCommand = "$commandId.raw";
4941
+ }
4942
+
4943
+ curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand);
4944
+
4945
+ if (curl_exec($resource) === false) {
4946
+ $error = curl_error($resource);
4947
+ $errno = curl_errno($resource);
4948
+
4949
+ throw new ConnectionException($this, trim($error), $errno);
4950
+ }
4951
+
4952
+ if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) {
4953
+ throw new ProtocolException($this, phpiredis_reader_get_error($this->reader));
4954
+ }
4955
+
4956
+ return phpiredis_reader_get_reply($this->reader);
4957
+ }
4958
+
4959
+ /**
4960
+ * {@inheritdoc}
4961
+ */
4962
+ public function getResource()
4963
+ {
4964
+ return $this->resource;
4965
+ }
4966
+
4967
+ /**
4968
+ * {@inheritdoc}
4969
+ */
4970
+ public function getParameters()
4971
+ {
4972
+ return $this->parameters;
4973
+ }
4974
+
4975
+ /**
4976
+ * {@inheritdoc}
4977
+ */
4978
+ public function addConnectCommand(CommandInterface $command)
4979
+ {
4980
+ $this->throwNotSupportedException(__FUNCTION__);
4981
+ }
4982
+
4983
+ /**
4984
+ * {@inheritdoc}
4985
+ */
4986
+ public function read()
4987
+ {
4988
+ $this->throwNotSupportedException(__FUNCTION__);
4989
+ }
4990
+
4991
+ /**
4992
+ * {@inheritdoc}
4993
+ */
4994
+ public function __toString()
4995
+ {
4996
+ return "{$this->parameters->host}:{$this->parameters->port}";
4997
+ }
4998
+
4999
+ /**
5000
+ * {@inheritdoc}
5001
+ */
5002
+ public function __sleep()
5003
+ {
5004
+ return array('parameters');
5005
+ }
5006
+
5007
+ /**
5008
+ * {@inheritdoc}
5009
+ */
5010
+ public function __wakeup()
5011
+ {
5012
+ $this->assertExtensions();
5013
+
5014
+ $this->resource = $this->createCurl();
5015
+ $this->reader = $this->createReader();
5016
+ }
5017
+ }
5018
+
5019
+ /**
5020
+ * This class provides the implementation of a Predis connection that uses the
5021
+ * PHP socket extension for network communication and wraps the phpiredis C
5022
+ * extension (PHP bindings for hiredis) to parse the Redis protocol.
5023
+ *
5024
+ * This class is intended to provide an optional low-overhead alternative for
5025
+ * processing responses from Redis compared to the standard pure-PHP classes.
5026
+ * Differences in speed when dealing with short inline responses are practically
5027
+ * nonexistent, the actual speed boost is for big multibulk responses when this
5028
+ * protocol processor can parse and return responses very fast.
5029
+ *
5030
+ * For instructions on how to build and install the phpiredis extension, please
5031
+ * consult the repository of the project.
5032
+ *
5033
+ * The connection parameters supported by this class are:
5034
+ *
5035
+ * - scheme: it can be either 'tcp' or 'unix'.
5036
+ * - host: hostname or IP address of the server.
5037
+ * - port: TCP port of the server.
5038
+ * - path: path of a UNIX domain socket when scheme is 'unix'.
5039
+ * - timeout: timeout to perform the connection.
5040
+ * - read_write_timeout: timeout of read / write operations.
5041
+ *
5042
+ * @link http://github.com/nrk/phpiredis
5043
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5044
+ */
5045
+ class PhpiredisSocketConnection extends AbstractConnection
5046
+ {
5047
+ private $reader;
5048
+
5049
+ /**
5050
+ * {@inheritdoc}
5051
+ */
5052
+ public function __construct(ParametersInterface $parameters)
5053
+ {
5054
+ $this->assertExtensions();
5055
+
5056
+ parent::__construct($parameters);
5057
+
5058
+ $this->reader = $this->createReader();
5059
+ }
5060
+
5061
+ /**
5062
+ * Disconnects from the server and destroys the underlying resource and the
5063
+ * protocol reader resource when PHP's garbage collector kicks in.
5064
+ */
5065
+ public function __destruct()
5066
+ {
5067
+ phpiredis_reader_destroy($this->reader);
5068
+
5069
+ parent::__destruct();
5070
+ }
5071
+
5072
+ /**
5073
+ * Checks if the socket and phpiredis extensions are loaded in PHP.
5074
+ */
5075
+ protected function assertExtensions()
5076
+ {
5077
+ if (!extension_loaded('sockets')) {
5078
+ throw new NotSupportedException(
5079
+ 'The "sockets" extension is required by this connection backend.'
5080
+ );
5081
+ }
5082
+
5083
+ if (!extension_loaded('phpiredis')) {
5084
+ throw new NotSupportedException(
5085
+ 'The "phpiredis" extension is required by this connection backend.'
5086
+ );
5087
+ }
5088
+ }
5089
+
5090
+ /**
5091
+ * {@inheritdoc}
5092
+ */
5093
+ protected function assertParameters(ParametersInterface $parameters)
5094
+ {
5095
+ if (isset($parameters->persistent)) {
5096
+ throw new NotSupportedException(
5097
+ "Persistent connections are not supported by this connection backend."
5098
+ );
5099
+ }
5100
+
5101
+ return parent::assertParameters($parameters);
5102
+ }
5103
+
5104
+ /**
5105
+ * Creates a new instance of the protocol reader resource.
5106
+ *
5107
+ * @return resource
5108
+ */
5109
+ private function createReader()
5110
+ {
5111
+ $reader = phpiredis_reader_create();
5112
+
5113
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
5114
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
5115
+
5116
+ return $reader;
5117
+ }
5118
+
5119
+ /**
5120
+ * Returns the underlying protocol reader resource.
5121
+ *
5122
+ * @return resource
5123
+ */
5124
+ protected function getReader()
5125
+ {
5126
+ return $this->reader;
5127
+ }
5128
+
5129
+ /**
5130
+ * Returns the handler used by the protocol reader for inline responses.
5131
+ *
5132
+ * @return \Closure
5133
+ */
5134
+ private function getStatusHandler()
5135
+ {
5136
+ return function ($payload) {
5137
+ return StatusResponse::get($payload);
5138
+ };
5139
+ }
5140
+
5141
+ /**
5142
+ * Returns the handler used by the protocol reader for error responses.
5143
+ *
5144
+ * @return \Closure
5145
+ */
5146
+ protected function getErrorHandler()
5147
+ {
5148
+ return function ($payload) {
5149
+ return new ErrorResponse($payload);
5150
+ };
5151
+ }
5152
+
5153
+ /**
5154
+ * Helper method used to throw exceptions on socket errors.
5155
+ */
5156
+ private function emitSocketError()
5157
+ {
5158
+ $errno = socket_last_error();
5159
+ $errstr = socket_strerror($errno);
5160
+
5161
+ $this->disconnect();
5162
+
5163
+ $this->onConnectionError(trim($errstr), $errno);
5164
+ }
5165
+
5166
+ /**
5167
+ * {@inheritdoc}
5168
+ */
5169
+ protected function createResource()
5170
+ {
5171
+ $isUnix = $this->parameters->scheme === 'unix';
5172
+ $domain = $isUnix ? AF_UNIX : AF_INET;
5173
+ $protocol = $isUnix ? 0 : SOL_TCP;
5174
+
5175
+ $socket = @call_user_func('socket_create', $domain, SOCK_STREAM, $protocol);
5176
+
5177
+ if (!is_resource($socket)) {
5178
+ $this->emitSocketError();
5179
+ }
5180
+
5181
+ $this->setSocketOptions($socket, $this->parameters);
5182
+
5183
+ return $socket;
5184
+ }
5185
+
5186
+ /**
5187
+ * Sets options on the socket resource from the connection parameters.
5188
+ *
5189
+ * @param resource $socket Socket resource.
5190
+ * @param ParametersInterface $parameters Parameters used to initialize the connection.
5191
+ */
5192
+ private function setSocketOptions($socket, ParametersInterface $parameters)
5193
+ {
5194
+ if ($parameters->scheme !== 'tcp') {
5195
+ return;
5196
+ }
5197
+
5198
+ if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) {
5199
+ $this->emitSocketError();
5200
+ }
5201
+
5202
+ if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
5203
+ $this->emitSocketError();
5204
+ }
5205
+
5206
+ if (isset($parameters->read_write_timeout)) {
5207
+ $rwtimeout = (float) $parameters->read_write_timeout;
5208
+ $timeoutSec = floor($rwtimeout);
5209
+ $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000;
5210
+
5211
+ $timeout = array(
5212
+ 'sec' => $timeoutSec,
5213
+ 'usec' => $timeoutUsec,
5214
+ );
5215
+
5216
+ if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) {
5217
+ $this->emitSocketError();
5218
+ }
5219
+
5220
+ if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) {
5221
+ $this->emitSocketError();
5222
+ }
5223
+ }
5224
+ }
5225
+
5226
+ /**
5227
+ * Gets the address from the connection parameters.
5228
+ *
5229
+ * @param ParametersInterface $parameters Parameters used to initialize the connection.
5230
+ *
5231
+ * @return string
5232
+ */
5233
+ protected static function getAddress(ParametersInterface $parameters)
5234
+ {
5235
+ if ($parameters->scheme === 'unix') {
5236
+ return $parameters->path;
5237
+ }
5238
+
5239
+ $host = $parameters->host;
5240
+
5241
+ if (ip2long($host) === false) {
5242
+ if (false === $addresses = gethostbynamel($host)) {
5243
+ return false;
5244
+ }
5245
+
5246
+ return $addresses[array_rand($addresses)];
5247
+ }
5248
+
5249
+ return $host;
5250
+ }
5251
+
5252
+ /**
5253
+ * Opens the actual connection to the server with a timeout.
5254
+ *
5255
+ * @param ParametersInterface $parameters Parameters used to initialize the connection.
5256
+ *
5257
+ * @return string
5258
+ */
5259
+ private function connectWithTimeout(ParametersInterface $parameters)
5260
+ {
5261
+ if (false === $host = self::getAddress($parameters)) {
5262
+ $this->onConnectionError("Cannot resolve the address of '$parameters->host'.");
5263
+ }
5264
+
5265
+ $socket = $this->getResource();
5266
+
5267
+ socket_set_nonblock($socket);
5268
+
5269
+ if (@socket_connect($socket, $host, (int) $parameters->port) === false) {
5270
+ $error = socket_last_error();
5271
+
5272
+ if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) {
5273
+ $this->emitSocketError();
5274
+ }
5275
+ }
5276
+
5277
+ socket_set_block($socket);
5278
+
5279
+ $null = null;
5280
+ $selectable = array($socket);
5281
+
5282
+ $timeout = (float) $parameters->timeout;
5283
+ $timeoutSecs = floor($timeout);
5284
+ $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000;
5285
+
5286
+ $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs);
5287
+
5288
+ if ($selected === 2) {
5289
+ $this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED);
5290
+ }
5291
+ if ($selected === 0) {
5292
+ $this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT);
5293
+ }
5294
+ if ($selected === false) {
5295
+ $this->emitSocketError();
5296
+ }
5297
+ }
5298
+
5299
+ /**
5300
+ * {@inheritdoc}
5301
+ */
5302
+ public function connect()
5303
+ {
5304
+ if (parent::connect()) {
5305
+ $this->connectWithTimeout($this->parameters);
5306
+
5307
+ if ($this->initCommands) {
5308
+ foreach ($this->initCommands as $command) {
5309
+ $this->executeCommand($command);
5310
+ }
5311
+ }
5312
+ }
5313
+ }
5314
+
5315
+ /**
5316
+ * {@inheritdoc}
5317
+ */
5318
+ public function disconnect()
5319
+ {
5320
+ if ($this->isConnected()) {
5321
+ socket_close($this->getResource());
5322
+ parent::disconnect();
5323
+ }
5324
+ }
5325
+
5326
+ /**
5327
+ * {@inheritdoc}
5328
+ */
5329
+ protected function write($buffer)
5330
+ {
5331
+ $socket = $this->getResource();
5332
+
5333
+ while (($length = strlen($buffer)) > 0) {
5334
+ $written = socket_write($socket, $buffer, $length);
5335
+
5336
+ if ($length === $written) {
5337
+ return;
5338
+ }
5339
+
5340
+ if ($written === false) {
5341
+ $this->onConnectionError('Error while writing bytes to the server.');
5342
+ }
5343
+
5344
+ $buffer = substr($buffer, $written);
5345
+ }
5346
+ }
5347
+
5348
+ /**
5349
+ * {@inheritdoc}
5350
+ */
5351
+ public function read()
5352
+ {
5353
+ $socket = $this->getResource();
5354
+ $reader = $this->reader;
5355
+
5356
+ while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
5357
+ if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '') {
5358
+ $this->emitSocketError();
5359
+ }
5360
+
5361
+ phpiredis_reader_feed($reader, $buffer);
5362
+ }
5363
+
5364
+ if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
5365
+ return phpiredis_reader_get_reply($reader);
5366
+ } else {
5367
+ $this->onProtocolError(phpiredis_reader_get_error($reader));
5368
+
5369
+ return;
5370
+ }
5371
+ }
5372
+
5373
+ /**
5374
+ * {@inheritdoc}
5375
+ */
5376
+ public function writeRequest(CommandInterface $command)
5377
+ {
5378
+ $arguments = $command->getArguments();
5379
+ array_unshift($arguments, $command->getId());
5380
+
5381
+ $this->write(phpiredis_format_command($arguments));
5382
+ }
5383
+
5384
+ /**
5385
+ * {@inheritdoc}
5386
+ */
5387
+ public function __wakeup()
5388
+ {
5389
+ $this->assertExtensions();
5390
+ $this->reader = $this->createReader();
5391
+ }
5392
+ }
5393
+
5394
+ /**
5395
+ * Connection abstraction to Redis servers based on PHP's stream that uses an
5396
+ * external protocol processor defining the protocol used for the communication.
5397
+ *
5398
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5399
+ */
5400
+ class CompositeStreamConnection extends StreamConnection implements CompositeConnectionInterface
5401
+ {
5402
+ protected $protocol;
5403
+
5404
+ /**
5405
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
5406
+ * @param ProtocolProcessorInterface $protocol Protocol processor.
5407
+ */
5408
+ public function __construct(
5409
+ ParametersInterface $parameters,
5410
+ ProtocolProcessorInterface $protocol = null
5411
+ ) {
5412
+ $this->parameters = $this->assertParameters($parameters);
5413
+ $this->protocol = $protocol ?: new TextProtocolProcessor();
5414
+ }
5415
+
5416
+ /**
5417
+ * {@inheritdoc}
5418
+ */
5419
+ public function getProtocol()
5420
+ {
5421
+ return $this->protocol;
5422
+ }
5423
+
5424
+ /**
5425
+ * {@inheritdoc}
5426
+ */
5427
+ public function writeBuffer($buffer)
5428
+ {
5429
+ $this->write($buffer);
5430
+ }
5431
+
5432
+ /**
5433
+ * {@inheritdoc}
5434
+ */
5435
+ public function readBuffer($length)
5436
+ {
5437
+ if ($length <= 0) {
5438
+ throw new InvalidArgumentException('Length parameter must be greater than 0.');
5439
+ }
5440
+
5441
+ $value = '';
5442
+ $socket = $this->getResource();
5443
+
5444
+ do {
5445
+ $chunk = fread($socket, $length);
5446
+
5447
+ if ($chunk === false || $chunk === '') {
5448
+ $this->onConnectionError('Error while reading bytes from the server.');
5449
+ }
5450
+
5451
+ $value .= $chunk;
5452
+ } while (($length -= strlen($chunk)) > 0);
5453
+
5454
+ return $value;
5455
+ }
5456
+
5457
+ /**
5458
+ * {@inheritdoc}
5459
+ */
5460
+ public function readLine()
5461
+ {
5462
+ $value = '';
5463
+ $socket = $this->getResource();
5464
+
5465
+ do {
5466
+ $chunk = fgets($socket);
5467
+
5468
+ if ($chunk === false || $chunk === '') {
5469
+ $this->onConnectionError('Error while reading line from the server.');
5470
+ }
5471
+
5472
+ $value .= $chunk;
5473
+ } while (substr($value, -2) !== "\r\n");
5474
+
5475
+ return substr($value, 0, -2);
5476
+ }
5477
+
5478
+ /**
5479
+ * {@inheritdoc}
5480
+ */
5481
+ public function writeRequest(CommandInterface $command)
5482
+ {
5483
+ $this->protocol->write($this, $command);
5484
+ }
5485
+
5486
+ /**
5487
+ * {@inheritdoc}
5488
+ */
5489
+ public function read()
5490
+ {
5491
+ return $this->protocol->read($this);
5492
+ }
5493
+
5494
+ /**
5495
+ * {@inheritdoc}
5496
+ */
5497
+ public function __sleep()
5498
+ {
5499
+ return array_merge(parent::__sleep(), array('protocol'));
5500
+ }
5501
+ }
5502
+
5503
+ /**
5504
+ * Exception class that identifies connection-related errors.
5505
+ *
5506
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5507
+ */
5508
+ class ConnectionException extends CommunicationException
5509
+ {
5510
+ }
5511
+
5512
+ /**
5513
+ * Container for connection parameters used to initialize connections to Redis.
5514
+ *
5515
+ * {@inheritdoc}
5516
+ *
5517
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5518
+ */
5519
+ class Parameters implements ParametersInterface
5520
+ {
5521
+ private $parameters;
5522
+
5523
+ private static $defaults = array(
5524
+ 'scheme' => 'tcp',
5525
+ 'host' => '127.0.0.1',
5526
+ 'port' => 6379,
5527
+ 'timeout' => 5.0,
5528
+ );
5529
+
5530
+ /**
5531
+ * @param array $parameters Named array of connection parameters.
5532
+ */
5533
+ public function __construct(array $parameters = array())
5534
+ {
5535
+ $this->parameters = $this->filter($parameters) + $this->getDefaults();
5536
+ }
5537
+
5538
+ /**
5539
+ * Returns some default parameters with their values.
5540
+ *
5541
+ * @return array
5542
+ */
5543
+ protected function getDefaults()
5544
+ {
5545
+ return self::$defaults;
5546
+ }
5547
+
5548
+ /**
5549
+ * Creates a new instance by supplying the initial parameters either in the
5550
+ * form of an URI string or a named array.
5551
+ *
5552
+ * @param array|string $parameters Set of connection parameters.
5553
+ *
5554
+ * @return Parameters
5555
+ */
5556
+ public static function create($parameters)
5557
+ {
5558
+ if (is_string($parameters)) {
5559
+ $parameters = static::parse($parameters);
5560
+ }
5561
+
5562
+ return new static($parameters ?: array());
5563
+ }
5564
+
5565
+ /**
5566
+ * Parses an URI string returning an array of connection parameters.
5567
+ *
5568
+ * @param string $uri URI string.
5569
+ *
5570
+ * @return array
5571
+ */
5572
+ public static function parse($uri)
5573
+ {
5574
+ if (stripos($uri, 'unix') === 0) {
5575
+ // Hack to support URIs for UNIX sockets with minimal effort.
5576
+ $uri = str_ireplace('unix:///', 'unix://localhost/', $uri);
5577
+ }
5578
+
5579
+ if (!($parsed = parse_url($uri)) || !isset($parsed['host'])) {
5580
+ throw new InvalidArgumentException("Invalid parameters URI: $uri");
5581
+ }
5582
+
5583
+ if (isset($parsed['query'])) {
5584
+ parse_str($parsed['query'], $queryarray);
5585
+ unset($parsed['query']);
5586
+
5587
+ $parsed = array_merge($parsed, $queryarray);
5588
+ }
5589
+
5590
+ return $parsed;
5591
+ }
5592
+
5593
+ /**
5594
+ * Validates and converts each value of the connection parameters array.
5595
+ *
5596
+ * @param array $parameters Connection parameters.
5597
+ *
5598
+ * @return array
5599
+ */
5600
+ protected function filter(array $parameters)
5601
+ {
5602
+ return $parameters ?: array();
5603
+ }
5604
+
5605
+ /**
5606
+ * {@inheritdoc}
5607
+ */
5608
+ public function __get($parameter)
5609
+ {
5610
+ if (isset($this->parameters[$parameter])) {
5611
+ return $this->parameters[$parameter];
5612
+ }
5613
+ }
5614
+
5615
+ /**
5616
+ * {@inheritdoc}
5617
+ */
5618
+ public function __isset($parameter)
5619
+ {
5620
+ return isset($this->parameters[$parameter]);
5621
+ }
5622
+
5623
+ /**
5624
+ * {@inheritdoc}
5625
+ */
5626
+ public function toArray()
5627
+ {
5628
+ return $this->parameters;
5629
+ }
5630
+
5631
+ /**
5632
+ * {@inheritdoc}
5633
+ */
5634
+ public function __sleep()
5635
+ {
5636
+ return array('parameters');
5637
+ }
5638
+ }
5639
+
5640
+ /**
5641
+ * Standard connection factory for creating connections to Redis nodes.
5642
+ *
5643
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5644
+ */
5645
+ class Factory implements FactoryInterface
5646
+ {
5647
+ protected $schemes = array(
5648
+ 'tcp' => 'Predis\Connection\StreamConnection',
5649
+ 'unix' => 'Predis\Connection\StreamConnection',
5650
+ 'http' => 'Predis\Connection\WebdisConnection',
5651
+ );
5652
+
5653
+ /**
5654
+ * Checks if the provided argument represents a valid connection class
5655
+ * implementing Predis\Connection\NodeConnectionInterface. Optionally,
5656
+ * callable objects are used for lazy initialization of connection objects.
5657
+ *
5658
+ * @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
5659
+ *
5660
+ * @return mixed
5661
+ */
5662
+ protected function checkInitializer($initializer)
5663
+ {
5664
+ if (is_callable($initializer)) {
5665
+ return $initializer;
5666
+ }
5667
+
5668
+ $class = new ReflectionClass($initializer);
5669
+
5670
+ if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) {
5671
+ throw new InvalidArgumentException(
5672
+ 'A connection initializer must be a valid connection class or a callable object.'
5673
+ );
5674
+ }
5675
+
5676
+ return $initializer;
5677
+ }
5678
+
5679
+ /**
5680
+ * {@inheritdoc}
5681
+ */
5682
+ public function define($scheme, $initializer)
5683
+ {
5684
+ $this->schemes[$scheme] = $this->checkInitializer($initializer);
5685
+ }
5686
+
5687
+ /**
5688
+ * {@inheritdoc}
5689
+ */
5690
+ public function undefine($scheme)
5691
+ {
5692
+ unset($this->schemes[$scheme]);
5693
+ }
5694
+
5695
+ /**
5696
+ * {@inheritdoc}
5697
+ */
5698
+ public function create($parameters)
5699
+ {
5700
+ if (!$parameters instanceof ParametersInterface) {
5701
+ $parameters = $this->createParameters($parameters);
5702
+ }
5703
+
5704
+ $scheme = $parameters->scheme;
5705
+
5706
+ if (!isset($this->schemes[$scheme])) {
5707
+ throw new InvalidArgumentException("Unknown connection scheme: '$scheme'.");
5708
+ }
5709
+
5710
+ $initializer = $this->schemes[$scheme];
5711
+
5712
+ if (is_callable($initializer)) {
5713
+ $connection = call_user_func($initializer, $parameters, $this);
5714
+ } else {
5715
+ $connection = new $initializer($parameters);
5716
+ $this->prepareConnection($connection);
5717
+ }
5718
+
5719
+ if (!$connection instanceof NodeConnectionInterface) {
5720
+ throw new UnexpectedValueException(
5721
+ "Objects returned by connection initializers must implement ".
5722
+ "'Predis\Connection\NodeConnectionInterface'."
5723
+ );
5724
+ }
5725
+
5726
+ return $connection;
5727
+ }
5728
+
5729
+ /**
5730
+ * {@inheritdoc}
5731
+ */
5732
+ public function aggregate(AggregateConnectionInterface $connection, array $parameters)
5733
+ {
5734
+ foreach ($parameters as $node) {
5735
+ $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node));
5736
+ }
5737
+ }
5738
+
5739
+ /**
5740
+ * Creates a connection parameters instance from the supplied argument.
5741
+ *
5742
+ * @param mixed $parameters Original connection parameters.
5743
+ *
5744
+ * @return ParametersInterface
5745
+ */
5746
+ protected function createParameters($parameters)
5747
+ {
5748
+ return Parameters::create($parameters);
5749
+ }
5750
+
5751
+ /**
5752
+ * Prepares a connection instance after its initialization.
5753
+ *
5754
+ * @param NodeConnectionInterface $connection Connection instance.
5755
+ */
5756
+ protected function prepareConnection(NodeConnectionInterface $connection)
5757
+ {
5758
+ $parameters = $connection->getParameters();
5759
+
5760
+ if (isset($parameters->password)) {
5761
+ $connection->addConnectCommand(
5762
+ new RawCommand(array('AUTH', $parameters->password))
5763
+ );
5764
+ }
5765
+
5766
+ if (isset($parameters->database)) {
5767
+ $connection->addConnectCommand(
5768
+ new RawCommand(array('SELECT', $parameters->database))
5769
+ );
5770
+ }
5771
+ }
5772
+ }
5773
+
5774
+ /* --------------------------------------------------------------------------- */
5775
+
5776
+ namespace Predis\Profile;
5777
+
5778
+ use InvalidArgumentException;
5779
+ use ReflectionClass;
5780
+ use Predis\ClientException;
5781
+ use Predis\Command\CommandInterface;
5782
+ use Predis\Command\Processor\ProcessorInterface;
5783
+
5784
+ /**
5785
+ * A profile defines all the features and commands supported by certain versions
5786
+ * of Redis. Instances of Predis\Client should use a server profile matching the
5787
+ * version of Redis being used.
5788
+ *
5789
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5790
+ */
5791
+ interface ProfileInterface
5792
+ {
5793
+ /**
5794
+ * Returns the profile version corresponding to the Redis version.
5795
+ *
5796
+ * @return string
5797
+ */
5798
+ public function getVersion();
5799
+
5800
+ /**
5801
+ * Checks if the profile supports the specified command.
5802
+ *
5803
+ * @param string $commandID Command ID.
5804
+ *
5805
+ * @return bool
5806
+ */
5807
+ public function supportsCommand($commandID);
5808
+
5809
+ /**
5810
+ * Checks if the profile supports the specified list of commands.
5811
+ *
5812
+ * @param array $commandIDs List of command IDs.
5813
+ *
5814
+ * @return string
5815
+ */
5816
+ public function supportsCommands(array $commandIDs);
5817
+
5818
+ /**
5819
+ * Creates a new command instance.
5820
+ *
5821
+ * @param string $commandID Command ID.
5822
+ * @param array $arguments Arguments for the command.
5823
+ *
5824
+ * @return CommandInterface
5825
+ */
5826
+ public function createCommand($commandID, array $arguments = array());
5827
+ }
5828
+
5829
+ /**
5830
+ * Base class implementing common functionalities for Redis server profiles.
5831
+ *
5832
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5833
+ */
5834
+ abstract class RedisProfile implements ProfileInterface
5835
+ {
5836
+ private $commands;
5837
+ private $processor;
5838
+
5839
+ /**
5840
+ *
5841
+ */
5842
+ public function __construct()
5843
+ {
5844
+ $this->commands = $this->getSupportedCommands();
5845
+ }
5846
+
5847
+ /**
5848
+ * Returns a map of all the commands supported by the profile and their
5849
+ * actual PHP classes.
5850
+ *
5851
+ * @return array
5852
+ */
5853
+ abstract protected function getSupportedCommands();
5854
+
5855
+ /**
5856
+ * {@inheritdoc}
5857
+ */
5858
+ public function supportsCommand($commandID)
5859
+ {
5860
+ return isset($this->commands[strtoupper($commandID)]);
5861
+ }
5862
+
5863
+ /**
5864
+ * {@inheritdoc}
5865
+ */
5866
+ public function supportsCommands(array $commandIDs)
5867
+ {
5868
+ foreach ($commandIDs as $commandID) {
5869
+ if (!$this->supportsCommand($commandID)) {
5870
+ return false;
5871
+ }
5872
+ }
5873
+
5874
+ return true;
5875
+ }
5876
+
5877
+ /**
5878
+ * Returns the fully-qualified name of a class representing the specified
5879
+ * command ID registered in the current server profile.
5880
+ *
5881
+ * @param string $commandID Command ID.
5882
+ *
5883
+ * @return string|null
5884
+ */
5885
+ public function getCommandClass($commandID)
5886
+ {
5887
+ if (isset($this->commands[$commandID = strtoupper($commandID)])) {
5888
+ return $this->commands[$commandID];
5889
+ }
5890
+ }
5891
+
5892
+ /**
5893
+ * {@inheritdoc}
5894
+ */
5895
+ public function createCommand($commandID, array $arguments = array())
5896
+ {
5897
+ $commandID = strtoupper($commandID);
5898
+
5899
+ if (!isset($this->commands[$commandID])) {
5900
+ throw new ClientException("Command '$commandID' is not a registered Redis command.");
5901
+ }
5902
+
5903
+ $commandClass = $this->commands[$commandID];
5904
+ $command = new $commandClass();
5905
+ $command->setArguments($arguments);
5906
+
5907
+ if (isset($this->processor)) {
5908
+ $this->processor->process($command);
5909
+ }
5910
+
5911
+ return $command;
5912
+ }
5913
+
5914
+ /**
5915
+ * Defines a new command in the server profile.
5916
+ *
5917
+ * @param string $commandID Command ID.
5918
+ * @param string $class Fully-qualified name of a Predis\Command\CommandInterface.
5919
+ */
5920
+ public function defineCommand($commandID, $class)
5921
+ {
5922
+ $reflection = new ReflectionClass($class);
5923
+
5924
+ if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) {
5925
+ throw new InvalidArgumentException("The class '$class' is not a valid command class.");
5926
+ }
5927
+
5928
+ $this->commands[strtoupper($commandID)] = $class;
5929
+ }
5930
+
5931
+ /**
5932
+ * {@inheritdoc}
5933
+ */
5934
+ public function setProcessor(ProcessorInterface $processor = null)
5935
+ {
5936
+ $this->processor = $processor;
5937
+ }
5938
+
5939
+ /**
5940
+ * {@inheritdoc}
5941
+ */
5942
+ public function getProcessor()
5943
+ {
5944
+ return $this->processor;
5945
+ }
5946
+
5947
+ /**
5948
+ * Returns the version of server profile as its string representation.
5949
+ *
5950
+ * @return string
5951
+ */
5952
+ public function __toString()
5953
+ {
5954
+ return $this->getVersion();
5955
+ }
5956
+ }
5957
+
5958
+ /**
5959
+ * Server profile for Redis 3.0.
5960
+ *
5961
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5962
+ */
5963
+ class RedisVersion300 extends RedisProfile
5964
+ {
5965
+ /**
5966
+ * {@inheritdoc}
5967
+ */
5968
+ public function getVersion()
5969
+ {
5970
+ return '3.0';
5971
+ }
5972
+
5973
+ /**
5974
+ * {@inheritdoc}
5975
+ */
5976
+ public function getSupportedCommands()
5977
+ {
5978
+ return array(
5979
+ /* ---------------- Redis 1.2 ---------------- */
5980
+
5981
+ /* commands operating on the key space */
5982
+ 'EXISTS' => 'Predis\Command\KeyExists',
5983
+ 'DEL' => 'Predis\Command\KeyDelete',
5984
+ 'TYPE' => 'Predis\Command\KeyType',
5985
+ 'KEYS' => 'Predis\Command\KeyKeys',
5986
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
5987
+ 'RENAME' => 'Predis\Command\KeyRename',
5988
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
5989
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
5990
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
5991
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
5992
+ 'MOVE' => 'Predis\Command\KeyMove',
5993
+ 'SORT' => 'Predis\Command\KeySort',
5994
+ 'DUMP' => 'Predis\Command\KeyDump',
5995
+ 'RESTORE' => 'Predis\Command\KeyRestore',
5996
+
5997
+ /* commands operating on string values */
5998
+ 'SET' => 'Predis\Command\StringSet',
5999
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6000
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6001
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6002
+ 'GET' => 'Predis\Command\StringGet',
6003
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6004
+ 'GETSET' => 'Predis\Command\StringGetSet',
6005
+ 'INCR' => 'Predis\Command\StringIncrement',
6006
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6007
+ 'DECR' => 'Predis\Command\StringDecrement',
6008
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6009
+
6010
+ /* commands operating on lists */
6011
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6012
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6013
+ 'LLEN' => 'Predis\Command\ListLength',
6014
+ 'LRANGE' => 'Predis\Command\ListRange',
6015
+ 'LTRIM' => 'Predis\Command\ListTrim',
6016
+ 'LINDEX' => 'Predis\Command\ListIndex',
6017
+ 'LSET' => 'Predis\Command\ListSet',
6018
+ 'LREM' => 'Predis\Command\ListRemove',
6019
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6020
+ 'RPOP' => 'Predis\Command\ListPopLast',
6021
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6022
+
6023
+ /* commands operating on sets */
6024
+ 'SADD' => 'Predis\Command\SetAdd',
6025
+ 'SREM' => 'Predis\Command\SetRemove',
6026
+ 'SPOP' => 'Predis\Command\SetPop',
6027
+ 'SMOVE' => 'Predis\Command\SetMove',
6028
+ 'SCARD' => 'Predis\Command\SetCardinality',
6029
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6030
+ 'SINTER' => 'Predis\Command\SetIntersection',
6031
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6032
+ 'SUNION' => 'Predis\Command\SetUnion',
6033
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6034
+ 'SDIFF' => 'Predis\Command\SetDifference',
6035
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6036
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6037
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6038
+
6039
+ /* commands operating on sorted sets */
6040
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6041
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6042
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6043
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6044
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6045
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6046
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6047
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6048
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6049
+
6050
+ /* connection related commands */
6051
+ 'PING' => 'Predis\Command\ConnectionPing',
6052
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6053
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6054
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6055
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6056
+
6057
+ /* remote server control commands */
6058
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
6059
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6060
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6061
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6062
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6063
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6064
+ 'SAVE' => 'Predis\Command\ServerSave',
6065
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6066
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6067
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6068
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6069
+
6070
+ /* ---------------- Redis 2.0 ---------------- */
6071
+
6072
+ /* commands operating on string values */
6073
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6074
+ 'APPEND' => 'Predis\Command\StringAppend',
6075
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6076
+
6077
+ /* commands operating on lists */
6078
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6079
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6080
+
6081
+ /* commands operating on sorted sets */
6082
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6083
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6084
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6085
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6086
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6087
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6088
+
6089
+ /* commands operating on hashes */
6090
+ 'HSET' => 'Predis\Command\HashSet',
6091
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6092
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6093
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6094
+ 'HGET' => 'Predis\Command\HashGet',
6095
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6096
+ 'HDEL' => 'Predis\Command\HashDelete',
6097
+ 'HEXISTS' => 'Predis\Command\HashExists',
6098
+ 'HLEN' => 'Predis\Command\HashLength',
6099
+ 'HKEYS' => 'Predis\Command\HashKeys',
6100
+ 'HVALS' => 'Predis\Command\HashValues',
6101
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6102
+
6103
+ /* transactions */
6104
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6105
+ 'EXEC' => 'Predis\Command\TransactionExec',
6106
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6107
+
6108
+ /* publish - subscribe */
6109
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6110
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6111
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6112
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6113
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6114
+
6115
+ /* remote server control commands */
6116
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6117
+
6118
+ /* ---------------- Redis 2.2 ---------------- */
6119
+
6120
+ /* commands operating on the key space */
6121
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6122
+
6123
+ /* commands operating on string values */
6124
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6125
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6126
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6127
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6128
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6129
+
6130
+ /* commands operating on lists */
6131
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6132
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6133
+ 'LINSERT' => 'Predis\Command\ListInsert',
6134
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6135
+
6136
+ /* commands operating on sorted sets */
6137
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6138
+
6139
+ /* transactions */
6140
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6141
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6142
+
6143
+ /* remote server control commands */
6144
+ 'OBJECT' => 'Predis\Command\ServerObject',
6145
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6146
+
6147
+ /* ---------------- Redis 2.4 ---------------- */
6148
+
6149
+ /* remote server control commands */
6150
+ 'CLIENT' => 'Predis\Command\ServerClient',
6151
+
6152
+ /* ---------------- Redis 2.6 ---------------- */
6153
+
6154
+ /* commands operating on the key space */
6155
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6156
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6157
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6158
+
6159
+ /* commands operating on string values */
6160
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6161
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6162
+ 'BITOP' => 'Predis\Command\StringBitOp',
6163
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
6164
+
6165
+ /* commands operating on hashes */
6166
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6167
+
6168
+ /* scripting */
6169
+ 'EVAL' => 'Predis\Command\ServerEval',
6170
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6171
+ 'SCRIPT' => 'Predis\Command\ServerScript',
6172
+
6173
+ /* remote server control commands */
6174
+ 'TIME' => 'Predis\Command\ServerTime',
6175
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
6176
+
6177
+ /* ---------------- Redis 2.8 ---------------- */
6178
+
6179
+ /* commands operating on the key space */
6180
+ 'SCAN' => 'Predis\Command\KeyScan',
6181
+
6182
+ /* commands operating on sets */
6183
+ 'SSCAN' => 'Predis\Command\SetScan',
6184
+
6185
+ /* commands operating on sorted sets */
6186
+ 'ZSCAN' => 'Predis\Command\ZSetScan',
6187
+ 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
6188
+ 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
6189
+ 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
6190
+
6191
+ /* commands operating on hashes */
6192
+ 'HSCAN' => 'Predis\Command\HashScan',
6193
+
6194
+ /* publish - subscribe */
6195
+ 'PUBSUB' => 'Predis\Command\PubSubPubsub',
6196
+
6197
+ /* commands operating on HyperLogLog */
6198
+ 'PFADD' => 'Predis\Command\HyperLogLogAdd',
6199
+ 'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
6200
+ 'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
6201
+
6202
+ /* remote server control commands */
6203
+ 'COMMAND' => 'Predis\Command\ServerCommand',
6204
+
6205
+ /* ---------------- Redis 3.0 ---------------- */
6206
+
6207
+ );
6208
+ }
6209
+ }
6210
+
6211
+ /**
6212
+ * Server profile for Redis 2.6.
6213
+ *
6214
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6215
+ */
6216
+ class RedisVersion260 extends RedisProfile
6217
+ {
6218
+ /**
6219
+ * {@inheritdoc}
6220
+ */
6221
+ public function getVersion()
6222
+ {
6223
+ return '2.6';
6224
+ }
6225
+
6226
+ /**
6227
+ * {@inheritdoc}
6228
+ */
6229
+ public function getSupportedCommands()
6230
+ {
6231
+ return array(
6232
+ /* ---------------- Redis 1.2 ---------------- */
6233
+
6234
+ /* commands operating on the key space */
6235
+ 'EXISTS' => 'Predis\Command\KeyExists',
6236
+ 'DEL' => 'Predis\Command\KeyDelete',
6237
+ 'TYPE' => 'Predis\Command\KeyType',
6238
+ 'KEYS' => 'Predis\Command\KeyKeys',
6239
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6240
+ 'RENAME' => 'Predis\Command\KeyRename',
6241
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6242
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6243
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6244
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6245
+ 'MOVE' => 'Predis\Command\KeyMove',
6246
+ 'SORT' => 'Predis\Command\KeySort',
6247
+ 'DUMP' => 'Predis\Command\KeyDump',
6248
+ 'RESTORE' => 'Predis\Command\KeyRestore',
6249
+
6250
+ /* commands operating on string values */
6251
+ 'SET' => 'Predis\Command\StringSet',
6252
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6253
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6254
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6255
+ 'GET' => 'Predis\Command\StringGet',
6256
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6257
+ 'GETSET' => 'Predis\Command\StringGetSet',
6258
+ 'INCR' => 'Predis\Command\StringIncrement',
6259
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6260
+ 'DECR' => 'Predis\Command\StringDecrement',
6261
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6262
+
6263
+ /* commands operating on lists */
6264
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6265
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6266
+ 'LLEN' => 'Predis\Command\ListLength',
6267
+ 'LRANGE' => 'Predis\Command\ListRange',
6268
+ 'LTRIM' => 'Predis\Command\ListTrim',
6269
+ 'LINDEX' => 'Predis\Command\ListIndex',
6270
+ 'LSET' => 'Predis\Command\ListSet',
6271
+ 'LREM' => 'Predis\Command\ListRemove',
6272
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6273
+ 'RPOP' => 'Predis\Command\ListPopLast',
6274
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6275
+
6276
+ /* commands operating on sets */
6277
+ 'SADD' => 'Predis\Command\SetAdd',
6278
+ 'SREM' => 'Predis\Command\SetRemove',
6279
+ 'SPOP' => 'Predis\Command\SetPop',
6280
+ 'SMOVE' => 'Predis\Command\SetMove',
6281
+ 'SCARD' => 'Predis\Command\SetCardinality',
6282
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6283
+ 'SINTER' => 'Predis\Command\SetIntersection',
6284
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6285
+ 'SUNION' => 'Predis\Command\SetUnion',
6286
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6287
+ 'SDIFF' => 'Predis\Command\SetDifference',
6288
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6289
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6290
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6291
+
6292
+ /* commands operating on sorted sets */
6293
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6294
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6295
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6296
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6297
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6298
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6299
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6300
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6301
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6302
+
6303
+ /* connection related commands */
6304
+ 'PING' => 'Predis\Command\ConnectionPing',
6305
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6306
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6307
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6308
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6309
+
6310
+ /* remote server control commands */
6311
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
6312
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6313
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6314
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6315
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6316
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6317
+ 'SAVE' => 'Predis\Command\ServerSave',
6318
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6319
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6320
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6321
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6322
+
6323
+ /* ---------------- Redis 2.0 ---------------- */
6324
+
6325
+ /* commands operating on string values */
6326
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6327
+ 'APPEND' => 'Predis\Command\StringAppend',
6328
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6329
+
6330
+ /* commands operating on lists */
6331
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6332
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6333
+
6334
+ /* commands operating on sorted sets */
6335
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6336
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6337
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6338
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6339
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6340
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6341
+
6342
+ /* commands operating on hashes */
6343
+ 'HSET' => 'Predis\Command\HashSet',
6344
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6345
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6346
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6347
+ 'HGET' => 'Predis\Command\HashGet',
6348
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6349
+ 'HDEL' => 'Predis\Command\HashDelete',
6350
+ 'HEXISTS' => 'Predis\Command\HashExists',
6351
+ 'HLEN' => 'Predis\Command\HashLength',
6352
+ 'HKEYS' => 'Predis\Command\HashKeys',
6353
+ 'HVALS' => 'Predis\Command\HashValues',
6354
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6355
+
6356
+ /* transactions */
6357
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6358
+ 'EXEC' => 'Predis\Command\TransactionExec',
6359
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6360
+
6361
+ /* publish - subscribe */
6362
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6363
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6364
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6365
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6366
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6367
+
6368
+ /* remote server control commands */
6369
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6370
+
6371
+ /* ---------------- Redis 2.2 ---------------- */
6372
+
6373
+ /* commands operating on the key space */
6374
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6375
+
6376
+ /* commands operating on string values */
6377
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6378
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6379
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6380
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6381
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6382
+
6383
+ /* commands operating on lists */
6384
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6385
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6386
+ 'LINSERT' => 'Predis\Command\ListInsert',
6387
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6388
+
6389
+ /* commands operating on sorted sets */
6390
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6391
+
6392
+ /* transactions */
6393
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6394
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6395
+
6396
+ /* remote server control commands */
6397
+ 'OBJECT' => 'Predis\Command\ServerObject',
6398
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6399
+
6400
+ /* ---------------- Redis 2.4 ---------------- */
6401
+
6402
+ /* remote server control commands */
6403
+ 'CLIENT' => 'Predis\Command\ServerClient',
6404
+
6405
+ /* ---------------- Redis 2.6 ---------------- */
6406
+
6407
+ /* commands operating on the key space */
6408
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6409
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6410
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6411
+
6412
+ /* commands operating on string values */
6413
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6414
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6415
+ 'BITOP' => 'Predis\Command\StringBitOp',
6416
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
6417
+
6418
+ /* commands operating on hashes */
6419
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6420
+
6421
+ /* scripting */
6422
+ 'EVAL' => 'Predis\Command\ServerEval',
6423
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6424
+ 'SCRIPT' => 'Predis\Command\ServerScript',
6425
+
6426
+ /* remote server control commands */
6427
+ 'TIME' => 'Predis\Command\ServerTime',
6428
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
6429
+ );
6430
+ }
6431
+ }
6432
+
6433
+ /**
6434
+ * Server profile for Redis 2.8.
6435
+ *
6436
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6437
+ */
6438
+ class RedisVersion280 extends RedisProfile
6439
+ {
6440
+ /**
6441
+ * {@inheritdoc}
6442
+ */
6443
+ public function getVersion()
6444
+ {
6445
+ return '2.8';
6446
+ }
6447
+
6448
+ /**
6449
+ * {@inheritdoc}
6450
+ */
6451
+ public function getSupportedCommands()
6452
+ {
6453
+ return array(
6454
+ /* ---------------- Redis 1.2 ---------------- */
6455
+
6456
+ /* commands operating on the key space */
6457
+ 'EXISTS' => 'Predis\Command\KeyExists',
6458
+ 'DEL' => 'Predis\Command\KeyDelete',
6459
+ 'TYPE' => 'Predis\Command\KeyType',
6460
+ 'KEYS' => 'Predis\Command\KeyKeys',
6461
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6462
+ 'RENAME' => 'Predis\Command\KeyRename',
6463
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6464
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6465
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6466
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6467
+ 'MOVE' => 'Predis\Command\KeyMove',
6468
+ 'SORT' => 'Predis\Command\KeySort',
6469
+ 'DUMP' => 'Predis\Command\KeyDump',
6470
+ 'RESTORE' => 'Predis\Command\KeyRestore',
6471
+
6472
+ /* commands operating on string values */
6473
+ 'SET' => 'Predis\Command\StringSet',
6474
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6475
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6476
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6477
+ 'GET' => 'Predis\Command\StringGet',
6478
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6479
+ 'GETSET' => 'Predis\Command\StringGetSet',
6480
+ 'INCR' => 'Predis\Command\StringIncrement',
6481
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6482
+ 'DECR' => 'Predis\Command\StringDecrement',
6483
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6484
+
6485
+ /* commands operating on lists */
6486
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6487
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6488
+ 'LLEN' => 'Predis\Command\ListLength',
6489
+ 'LRANGE' => 'Predis\Command\ListRange',
6490
+ 'LTRIM' => 'Predis\Command\ListTrim',
6491
+ 'LINDEX' => 'Predis\Command\ListIndex',
6492
+ 'LSET' => 'Predis\Command\ListSet',
6493
+ 'LREM' => 'Predis\Command\ListRemove',
6494
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6495
+ 'RPOP' => 'Predis\Command\ListPopLast',
6496
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6497
+
6498
+ /* commands operating on sets */
6499
+ 'SADD' => 'Predis\Command\SetAdd',
6500
+ 'SREM' => 'Predis\Command\SetRemove',
6501
+ 'SPOP' => 'Predis\Command\SetPop',
6502
+ 'SMOVE' => 'Predis\Command\SetMove',
6503
+ 'SCARD' => 'Predis\Command\SetCardinality',
6504
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6505
+ 'SINTER' => 'Predis\Command\SetIntersection',
6506
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6507
+ 'SUNION' => 'Predis\Command\SetUnion',
6508
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6509
+ 'SDIFF' => 'Predis\Command\SetDifference',
6510
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6511
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6512
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6513
+
6514
+ /* commands operating on sorted sets */
6515
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6516
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6517
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6518
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6519
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6520
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6521
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6522
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6523
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6524
+
6525
+ /* connection related commands */
6526
+ 'PING' => 'Predis\Command\ConnectionPing',
6527
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6528
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6529
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6530
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6531
+
6532
+ /* remote server control commands */
6533
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
6534
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6535
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6536
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6537
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6538
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6539
+ 'SAVE' => 'Predis\Command\ServerSave',
6540
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6541
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6542
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6543
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6544
+
6545
+ /* ---------------- Redis 2.0 ---------------- */
6546
+
6547
+ /* commands operating on string values */
6548
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6549
+ 'APPEND' => 'Predis\Command\StringAppend',
6550
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6551
+
6552
+ /* commands operating on lists */
6553
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6554
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6555
+
6556
+ /* commands operating on sorted sets */
6557
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6558
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6559
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6560
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6561
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6562
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6563
+
6564
+ /* commands operating on hashes */
6565
+ 'HSET' => 'Predis\Command\HashSet',
6566
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6567
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6568
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6569
+ 'HGET' => 'Predis\Command\HashGet',
6570
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6571
+ 'HDEL' => 'Predis\Command\HashDelete',
6572
+ 'HEXISTS' => 'Predis\Command\HashExists',
6573
+ 'HLEN' => 'Predis\Command\HashLength',
6574
+ 'HKEYS' => 'Predis\Command\HashKeys',
6575
+ 'HVALS' => 'Predis\Command\HashValues',
6576
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6577
+
6578
+ /* transactions */
6579
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6580
+ 'EXEC' => 'Predis\Command\TransactionExec',
6581
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6582
+
6583
+ /* publish - subscribe */
6584
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6585
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6586
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6587
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6588
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6589
+
6590
+ /* remote server control commands */
6591
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6592
+
6593
+ /* ---------------- Redis 2.2 ---------------- */
6594
+
6595
+ /* commands operating on the key space */
6596
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6597
+
6598
+ /* commands operating on string values */
6599
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6600
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6601
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6602
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6603
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6604
+
6605
+ /* commands operating on lists */
6606
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6607
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6608
+ 'LINSERT' => 'Predis\Command\ListInsert',
6609
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6610
+
6611
+ /* commands operating on sorted sets */
6612
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6613
+
6614
+ /* transactions */
6615
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6616
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6617
+
6618
+ /* remote server control commands */
6619
+ 'OBJECT' => 'Predis\Command\ServerObject',
6620
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6621
+
6622
+ /* ---------------- Redis 2.4 ---------------- */
6623
+
6624
+ /* remote server control commands */
6625
+ 'CLIENT' => 'Predis\Command\ServerClient',
6626
+
6627
+ /* ---------------- Redis 2.6 ---------------- */
6628
+
6629
+ /* commands operating on the key space */
6630
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6631
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6632
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6633
+
6634
+ /* commands operating on string values */
6635
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6636
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6637
+ 'BITOP' => 'Predis\Command\StringBitOp',
6638
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
6639
+
6640
+ /* commands operating on hashes */
6641
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6642
+
6643
+ /* scripting */
6644
+ 'EVAL' => 'Predis\Command\ServerEval',
6645
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6646
+ 'SCRIPT' => 'Predis\Command\ServerScript',
6647
+
6648
+ /* remote server control commands */
6649
+ 'TIME' => 'Predis\Command\ServerTime',
6650
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
6651
+
6652
+ /* ---------------- Redis 2.8 ---------------- */
6653
+
6654
+ /* commands operating on the key space */
6655
+ 'SCAN' => 'Predis\Command\KeyScan',
6656
+
6657
+ /* commands operating on sets */
6658
+ 'SSCAN' => 'Predis\Command\SetScan',
6659
+
6660
+ /* commands operating on sorted sets */
6661
+ 'ZSCAN' => 'Predis\Command\ZSetScan',
6662
+ 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
6663
+ 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
6664
+ 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
6665
+
6666
+ /* commands operating on hashes */
6667
+ 'HSCAN' => 'Predis\Command\HashScan',
6668
+
6669
+ /* publish - subscribe */
6670
+ 'PUBSUB' => 'Predis\Command\PubSubPubsub',
6671
+
6672
+ /* commands operating on HyperLogLog */
6673
+ 'PFADD' => 'Predis\Command\HyperLogLogAdd',
6674
+ 'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
6675
+ 'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
6676
+
6677
+ /* remote server control commands */
6678
+ 'COMMAND' => 'Predis\Command\ServerCommand',
6679
+ );
6680
+ }
6681
+ }
6682
+
6683
+ /**
6684
+ * Server profile for Redis 2.4.
6685
+ *
6686
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6687
+ */
6688
+ class RedisVersion240 extends RedisProfile
6689
+ {
6690
+ /**
6691
+ * {@inheritdoc}
6692
+ */
6693
+ public function getVersion()
6694
+ {
6695
+ return '2.4';
6696
+ }
6697
+
6698
+ /**
6699
+ * {@inheritdoc}
6700
+ */
6701
+ public function getSupportedCommands()
6702
+ {
6703
+ return array(
6704
+ /* ---------------- Redis 1.2 ---------------- */
6705
+
6706
+ /* commands operating on the key space */
6707
+ 'EXISTS' => 'Predis\Command\KeyExists',
6708
+ 'DEL' => 'Predis\Command\KeyDelete',
6709
+ 'TYPE' => 'Predis\Command\KeyType',
6710
+ 'KEYS' => 'Predis\Command\KeyKeys',
6711
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6712
+ 'RENAME' => 'Predis\Command\KeyRename',
6713
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6714
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6715
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6716
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6717
+ 'MOVE' => 'Predis\Command\KeyMove',
6718
+ 'SORT' => 'Predis\Command\KeySort',
6719
+
6720
+ /* commands operating on string values */
6721
+ 'SET' => 'Predis\Command\StringSet',
6722
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6723
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6724
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6725
+ 'GET' => 'Predis\Command\StringGet',
6726
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6727
+ 'GETSET' => 'Predis\Command\StringGetSet',
6728
+ 'INCR' => 'Predis\Command\StringIncrement',
6729
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6730
+ 'DECR' => 'Predis\Command\StringDecrement',
6731
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6732
+
6733
+ /* commands operating on lists */
6734
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6735
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6736
+ 'LLEN' => 'Predis\Command\ListLength',
6737
+ 'LRANGE' => 'Predis\Command\ListRange',
6738
+ 'LTRIM' => 'Predis\Command\ListTrim',
6739
+ 'LINDEX' => 'Predis\Command\ListIndex',
6740
+ 'LSET' => 'Predis\Command\ListSet',
6741
+ 'LREM' => 'Predis\Command\ListRemove',
6742
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6743
+ 'RPOP' => 'Predis\Command\ListPopLast',
6744
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6745
+
6746
+ /* commands operating on sets */
6747
+ 'SADD' => 'Predis\Command\SetAdd',
6748
+ 'SREM' => 'Predis\Command\SetRemove',
6749
+ 'SPOP' => 'Predis\Command\SetPop',
6750
+ 'SMOVE' => 'Predis\Command\SetMove',
6751
+ 'SCARD' => 'Predis\Command\SetCardinality',
6752
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6753
+ 'SINTER' => 'Predis\Command\SetIntersection',
6754
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6755
+ 'SUNION' => 'Predis\Command\SetUnion',
6756
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6757
+ 'SDIFF' => 'Predis\Command\SetDifference',
6758
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6759
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6760
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6761
+
6762
+ /* commands operating on sorted sets */
6763
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6764
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6765
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6766
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6767
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6768
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6769
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6770
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6771
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6772
+
6773
+ /* connection related commands */
6774
+ 'PING' => 'Predis\Command\ConnectionPing',
6775
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6776
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6777
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6778
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6779
+
6780
+ /* remote server control commands */
6781
+ 'INFO' => 'Predis\Command\ServerInfo',
6782
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6783
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6784
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6785
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6786
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6787
+ 'SAVE' => 'Predis\Command\ServerSave',
6788
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6789
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6790
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6791
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6792
+
6793
+ /* ---------------- Redis 2.0 ---------------- */
6794
+
6795
+ /* commands operating on string values */
6796
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6797
+ 'APPEND' => 'Predis\Command\StringAppend',
6798
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6799
+
6800
+ /* commands operating on lists */
6801
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6802
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6803
+
6804
+ /* commands operating on sorted sets */
6805
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6806
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6807
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6808
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6809
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6810
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6811
+
6812
+ /* commands operating on hashes */
6813
+ 'HSET' => 'Predis\Command\HashSet',
6814
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6815
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6816
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6817
+ 'HGET' => 'Predis\Command\HashGet',
6818
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6819
+ 'HDEL' => 'Predis\Command\HashDelete',
6820
+ 'HEXISTS' => 'Predis\Command\HashExists',
6821
+ 'HLEN' => 'Predis\Command\HashLength',
6822
+ 'HKEYS' => 'Predis\Command\HashKeys',
6823
+ 'HVALS' => 'Predis\Command\HashValues',
6824
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6825
+
6826
+ /* transactions */
6827
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6828
+ 'EXEC' => 'Predis\Command\TransactionExec',
6829
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6830
+
6831
+ /* publish - subscribe */
6832
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6833
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6834
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6835
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6836
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6837
+
6838
+ /* remote server control commands */
6839
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6840
+
6841
+ /* ---------------- Redis 2.2 ---------------- */
6842
+
6843
+ /* commands operating on the key space */
6844
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6845
+
6846
+ /* commands operating on string values */
6847
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6848
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6849
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6850
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6851
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6852
+
6853
+ /* commands operating on lists */
6854
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6855
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6856
+ 'LINSERT' => 'Predis\Command\ListInsert',
6857
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6858
+
6859
+ /* commands operating on sorted sets */
6860
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6861
+
6862
+ /* transactions */
6863
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6864
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6865
+
6866
+ /* remote server control commands */
6867
+ 'OBJECT' => 'Predis\Command\ServerObject',
6868
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6869
+
6870
+ /* ---------------- Redis 2.4 ---------------- */
6871
+
6872
+ /* remote server control commands */
6873
+ 'CLIENT' => 'Predis\Command\ServerClient',
6874
+ );
6875
+ }
6876
+ }
6877
+
6878
+ /**
6879
+ * Server profile for Redis 2.0.
6880
+ *
6881
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6882
+ */
6883
+ class RedisVersion200 extends RedisProfile
6884
+ {
6885
+ /**
6886
+ * {@inheritdoc}
6887
+ */
6888
+ public function getVersion()
6889
+ {
6890
+ return '2.0';
6891
+ }
6892
+
6893
+ /**
6894
+ * {@inheritdoc}
6895
+ */
6896
+ public function getSupportedCommands()
6897
+ {
6898
+ return array(
6899
+ /* ---------------- Redis 1.2 ---------------- */
6900
+
6901
+ /* commands operating on the key space */
6902
+ 'EXISTS' => 'Predis\Command\KeyExists',
6903
+ 'DEL' => 'Predis\Command\KeyDelete',
6904
+ 'TYPE' => 'Predis\Command\KeyType',
6905
+ 'KEYS' => 'Predis\Command\KeyKeys',
6906
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6907
+ 'RENAME' => 'Predis\Command\KeyRename',
6908
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6909
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6910
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6911
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6912
+ 'MOVE' => 'Predis\Command\KeyMove',
6913
+ 'SORT' => 'Predis\Command\KeySort',
6914
+
6915
+ /* commands operating on string values */
6916
+ 'SET' => 'Predis\Command\StringSet',
6917
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6918
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6919
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6920
+ 'GET' => 'Predis\Command\StringGet',
6921
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6922
+ 'GETSET' => 'Predis\Command\StringGetSet',
6923
+ 'INCR' => 'Predis\Command\StringIncrement',
6924
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6925
+ 'DECR' => 'Predis\Command\StringDecrement',
6926
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6927
+
6928
+ /* commands operating on lists */
6929
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6930
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6931
+ 'LLEN' => 'Predis\Command\ListLength',
6932
+ 'LRANGE' => 'Predis\Command\ListRange',
6933
+ 'LTRIM' => 'Predis\Command\ListTrim',
6934
+ 'LINDEX' => 'Predis\Command\ListIndex',
6935
+ 'LSET' => 'Predis\Command\ListSet',
6936
+ 'LREM' => 'Predis\Command\ListRemove',
6937
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6938
+ 'RPOP' => 'Predis\Command\ListPopLast',
6939
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6940
+
6941
+ /* commands operating on sets */
6942
+ 'SADD' => 'Predis\Command\SetAdd',
6943
+ 'SREM' => 'Predis\Command\SetRemove',
6944
+ 'SPOP' => 'Predis\Command\SetPop',
6945
+ 'SMOVE' => 'Predis\Command\SetMove',
6946
+ 'SCARD' => 'Predis\Command\SetCardinality',
6947
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6948
+ 'SINTER' => 'Predis\Command\SetIntersection',
6949
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6950
+ 'SUNION' => 'Predis\Command\SetUnion',
6951
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6952
+ 'SDIFF' => 'Predis\Command\SetDifference',
6953
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6954
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6955
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6956
+
6957
+ /* commands operating on sorted sets */
6958
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6959
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6960
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6961
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6962
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6963
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6964
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6965
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6966
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6967
+
6968
+ /* connection related commands */
6969
+ 'PING' => 'Predis\Command\ConnectionPing',
6970
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6971
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6972
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6973
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6974
+
6975
+ /* remote server control commands */
6976
+ 'INFO' => 'Predis\Command\ServerInfo',
6977
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6978
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6979
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6980
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6981
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6982
+ 'SAVE' => 'Predis\Command\ServerSave',
6983
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6984
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6985
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6986
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6987
+
6988
+ /* ---------------- Redis 2.0 ---------------- */
6989
+
6990
+ /* commands operating on string values */
6991
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6992
+ 'APPEND' => 'Predis\Command\StringAppend',
6993
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6994
+
6995
+ /* commands operating on lists */
6996
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6997
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6998
+
6999
+ /* commands operating on sorted sets */
7000
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
7001
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
7002
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
7003
+ 'ZRANK' => 'Predis\Command\ZSetRank',
7004
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
7005
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
7006
+
7007
+ /* commands operating on hashes */
7008
+ 'HSET' => 'Predis\Command\HashSet',
7009
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
7010
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
7011
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
7012
+ 'HGET' => 'Predis\Command\HashGet',
7013
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
7014
+ 'HDEL' => 'Predis\Command\HashDelete',
7015
+ 'HEXISTS' => 'Predis\Command\HashExists',
7016
+ 'HLEN' => 'Predis\Command\HashLength',
7017
+ 'HKEYS' => 'Predis\Command\HashKeys',
7018
+ 'HVALS' => 'Predis\Command\HashValues',
7019
+ 'HGETALL' => 'Predis\Command\HashGetAll',
7020
+
7021
+ /* transactions */
7022
+ 'MULTI' => 'Predis\Command\TransactionMulti',
7023
+ 'EXEC' => 'Predis\Command\TransactionExec',
7024
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
7025
+
7026
+ /* publish - subscribe */
7027
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
7028
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
7029
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
7030
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
7031
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
7032
+
7033
+ /* remote server control commands */
7034
+ 'CONFIG' => 'Predis\Command\ServerConfig',
7035
+ );
7036
+ }
7037
+ }
7038
+
7039
+ /**
7040
+ * Server profile for the current unstable version of Redis.
7041
+ *
7042
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7043
+ */
7044
+ class RedisUnstable extends RedisVersion300
7045
+ {
7046
+ /**
7047
+ * {@inheritdoc}
7048
+ */
7049
+ public function getVersion()
7050
+ {
7051
+ return '3.0';
7052
+ }
7053
+
7054
+ /**
7055
+ * {@inheritdoc}
7056
+ */
7057
+ public function getSupportedCommands()
7058
+ {
7059
+ return array_merge(parent::getSupportedCommands(), array());
7060
+ }
7061
+ }
7062
+
7063
+ /**
7064
+ * Factory class for creating profile instances from strings.
7065
+ *
7066
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7067
+ */
7068
+ final class Factory
7069
+ {
7070
+ private static $profiles = array(
7071
+ '2.0' => 'Predis\Profile\RedisVersion200',
7072
+ '2.2' => 'Predis\Profile\RedisVersion220',
7073
+ '2.4' => 'Predis\Profile\RedisVersion240',
7074
+ '2.6' => 'Predis\Profile\RedisVersion260',
7075
+ '2.8' => 'Predis\Profile\RedisVersion280',
7076
+ '3.0' => 'Predis\Profile\RedisVersion300',
7077
+ 'default' => 'Predis\Profile\RedisVersion300',
7078
+ 'dev' => 'Predis\Profile\RedisUnstable',
7079
+ );
7080
+
7081
+ /**
7082
+ *
7083
+ */
7084
+ private function __construct()
7085
+ {
7086
+ // NOOP
7087
+ }
7088
+
7089
+ /**
7090
+ * Returns the default server profile.
7091
+ *
7092
+ * @return ProfileInterface
7093
+ */
7094
+ public static function getDefault()
7095
+ {
7096
+ return self::get('default');
7097
+ }
7098
+
7099
+ /**
7100
+ * Returns the development server profile.
7101
+ *
7102
+ * @return ProfileInterface
7103
+ */
7104
+ public static function getDevelopment()
7105
+ {
7106
+ return self::get('dev');
7107
+ }
7108
+
7109
+ /**
7110
+ * Registers a new server profile.
7111
+ *
7112
+ * @param string $alias Profile version or alias.
7113
+ * @param string $class FQN of a class implementing Predis\Profile\ProfileInterface.
7114
+ */
7115
+ public static function define($alias, $class)
7116
+ {
7117
+ $reflection = new ReflectionClass($class);
7118
+
7119
+ if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) {
7120
+ throw new InvalidArgumentException("The class '$class' is not a valid profile class.");
7121
+ }
7122
+
7123
+ self::$profiles[$alias] = $class;
7124
+ }
7125
+
7126
+ /**
7127
+ * Returns the specified server profile.
7128
+ *
7129
+ * @param string $version Profile version or alias.
7130
+ *
7131
+ * @return ProfileInterface
7132
+ *
7133
+ * @throws ClientException
7134
+ */
7135
+ public static function get($version)
7136
+ {
7137
+ if (!isset(self::$profiles[$version])) {
7138
+ throw new ClientException("Unknown server profile: '$version'.");
7139
+ }
7140
+
7141
+ $profile = self::$profiles[$version];
7142
+
7143
+ return new $profile();
7144
+ }
7145
+ }
7146
+
7147
+ /**
7148
+ * Server profile for Redis 2.2.
7149
+ *
7150
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7151
+ */
7152
+ class RedisVersion220 extends RedisProfile
7153
+ {
7154
+ /**
7155
+ * {@inheritdoc}
7156
+ */
7157
+ public function getVersion()
7158
+ {
7159
+ return '2.2';
7160
+ }
7161
+
7162
+ /**
7163
+ * {@inheritdoc}
7164
+ */
7165
+ public function getSupportedCommands()
7166
+ {
7167
+ return array(
7168
+ /* ---------------- Redis 1.2 ---------------- */
7169
+
7170
+ /* commands operating on the key space */
7171
+ 'EXISTS' => 'Predis\Command\KeyExists',
7172
+ 'DEL' => 'Predis\Command\KeyDelete',
7173
+ 'TYPE' => 'Predis\Command\KeyType',
7174
+ 'KEYS' => 'Predis\Command\KeyKeys',
7175
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
7176
+ 'RENAME' => 'Predis\Command\KeyRename',
7177
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
7178
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
7179
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
7180
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
7181
+ 'MOVE' => 'Predis\Command\KeyMove',
7182
+ 'SORT' => 'Predis\Command\KeySort',
7183
+
7184
+ /* commands operating on string values */
7185
+ 'SET' => 'Predis\Command\StringSet',
7186
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
7187
+ 'MSET' => 'Predis\Command\StringSetMultiple',
7188
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
7189
+ 'GET' => 'Predis\Command\StringGet',
7190
+ 'MGET' => 'Predis\Command\StringGetMultiple',
7191
+ 'GETSET' => 'Predis\Command\StringGetSet',
7192
+ 'INCR' => 'Predis\Command\StringIncrement',
7193
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
7194
+ 'DECR' => 'Predis\Command\StringDecrement',
7195
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
7196
+
7197
+ /* commands operating on lists */
7198
+ 'RPUSH' => 'Predis\Command\ListPushTail',
7199
+ 'LPUSH' => 'Predis\Command\ListPushHead',
7200
+ 'LLEN' => 'Predis\Command\ListLength',
7201
+ 'LRANGE' => 'Predis\Command\ListRange',
7202
+ 'LTRIM' => 'Predis\Command\ListTrim',
7203
+ 'LINDEX' => 'Predis\Command\ListIndex',
7204
+ 'LSET' => 'Predis\Command\ListSet',
7205
+ 'LREM' => 'Predis\Command\ListRemove',
7206
+ 'LPOP' => 'Predis\Command\ListPopFirst',
7207
+ 'RPOP' => 'Predis\Command\ListPopLast',
7208
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
7209
+
7210
+ /* commands operating on sets */
7211
+ 'SADD' => 'Predis\Command\SetAdd',
7212
+ 'SREM' => 'Predis\Command\SetRemove',
7213
+ 'SPOP' => 'Predis\Command\SetPop',
7214
+ 'SMOVE' => 'Predis\Command\SetMove',
7215
+ 'SCARD' => 'Predis\Command\SetCardinality',
7216
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
7217
+ 'SINTER' => 'Predis\Command\SetIntersection',
7218
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
7219
+ 'SUNION' => 'Predis\Command\SetUnion',
7220
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
7221
+ 'SDIFF' => 'Predis\Command\SetDifference',
7222
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
7223
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
7224
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
7225
+
7226
+ /* commands operating on sorted sets */
7227
+ 'ZADD' => 'Predis\Command\ZSetAdd',
7228
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
7229
+ 'ZREM' => 'Predis\Command\ZSetRemove',
7230
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
7231
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
7232
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
7233
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
7234
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
7235
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
7236
+
7237
+ /* connection related commands */
7238
+ 'PING' => 'Predis\Command\ConnectionPing',
7239
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
7240
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
7241
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
7242
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
7243
+
7244
+ /* remote server control commands */
7245
+ 'INFO' => 'Predis\Command\ServerInfo',
7246
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7247
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
7248
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
7249
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
7250
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
7251
+ 'SAVE' => 'Predis\Command\ServerSave',
7252
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
7253
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
7254
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
7255
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
7256
+
7257
+ /* ---------------- Redis 2.0 ---------------- */
7258
+
7259
+ /* commands operating on string values */
7260
+ 'SETEX' => 'Predis\Command\StringSetExpire',
7261
+ 'APPEND' => 'Predis\Command\StringAppend',
7262
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
7263
+
7264
+ /* commands operating on lists */
7265
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
7266
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
7267
+
7268
+ /* commands operating on sorted sets */
7269
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
7270
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
7271
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
7272
+ 'ZRANK' => 'Predis\Command\ZSetRank',
7273
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
7274
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
7275
+
7276
+ /* commands operating on hashes */
7277
+ 'HSET' => 'Predis\Command\HashSet',
7278
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
7279
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
7280
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
7281
+ 'HGET' => 'Predis\Command\HashGet',
7282
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
7283
+ 'HDEL' => 'Predis\Command\HashDelete',
7284
+ 'HEXISTS' => 'Predis\Command\HashExists',
7285
+ 'HLEN' => 'Predis\Command\HashLength',
7286
+ 'HKEYS' => 'Predis\Command\HashKeys',
7287
+ 'HVALS' => 'Predis\Command\HashValues',
7288
+ 'HGETALL' => 'Predis\Command\HashGetAll',
7289
+
7290
+ /* transactions */
7291
+ 'MULTI' => 'Predis\Command\TransactionMulti',
7292
+ 'EXEC' => 'Predis\Command\TransactionExec',
7293
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
7294
+
7295
+ /* publish - subscribe */
7296
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
7297
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
7298
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
7299
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
7300
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
7301
+
7302
+ /* remote server control commands */
7303
+ 'CONFIG' => 'Predis\Command\ServerConfig',
7304
+
7305
+ /* ---------------- Redis 2.2 ---------------- */
7306
+
7307
+ /* commands operating on the key space */
7308
+ 'PERSIST' => 'Predis\Command\KeyPersist',
7309
+
7310
+ /* commands operating on string values */
7311
+ 'STRLEN' => 'Predis\Command\StringStrlen',
7312
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
7313
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
7314
+ 'SETBIT' => 'Predis\Command\StringSetBit',
7315
+ 'GETBIT' => 'Predis\Command\StringGetBit',
7316
+
7317
+ /* commands operating on lists */
7318
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
7319
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
7320
+ 'LINSERT' => 'Predis\Command\ListInsert',
7321
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
7322
+
7323
+ /* commands operating on sorted sets */
7324
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
7325
+
7326
+ /* transactions */
7327
+ 'WATCH' => 'Predis\Command\TransactionWatch',
7328
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
7329
+
7330
+ /* remote server control commands */
7331
+ 'OBJECT' => 'Predis\Command\ServerObject',
7332
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
7333
+ );
7334
+ }
7335
+ }
7336
+
7337
+ /* --------------------------------------------------------------------------- */
7338
+
7339
+ namespace Predis;
7340
+
7341
+ use InvalidArgumentException;
7342
+ use UnexpectedValueException;
7343
+ use Predis\Command\CommandInterface;
7344
+ use Predis\Command\RawCommand;
7345
+ use Predis\Command\ScriptCommand;
7346
+ use Predis\Configuration\Options;
7347
+ use Predis\Configuration\OptionsInterface;
7348
+ use Predis\Connection\ConnectionInterface;
7349
+ use Predis\Connection\AggregateConnectionInterface;
7350
+ use Predis\Connection\ParametersInterface;
7351
+ use Predis\Monitor\Consumer as MonitorConsumer;
7352
+ use Predis\Pipeline\Pipeline;
7353
+ use Predis\PubSub\Consumer as PubSubConsumer;
7354
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
7355
+ use Predis\Response\ResponseInterface;
7356
+ use Predis\Response\ServerException;
7357
+ use Predis\Transaction\MultiExec as MultiExecTransaction;
7358
+ use Predis\Profile\ProfileInterface;
7359
+ use Exception;
7360
+ use Predis\Connection\NodeConnectionInterface;
7361
+
7362
+ /**
7363
+ * Base exception class for Predis-related errors.
7364
+ *
7365
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7366
+ */
7367
+ abstract class PredisException extends Exception
7368
+ {
7369
+ }
7370
+
7371
+ /**
7372
+ * Interface defining a client-side context such as a pipeline or transaction.
7373
+ *
7374
+ * @method $this del(array $keys)
7375
+ * @method $this dump($key)
7376
+ * @method $this exists($key)
7377
+ * @method $this expire($key, $seconds)
7378
+ * @method $this expireat($key, $timestamp)
7379
+ * @method $this keys($pattern)
7380
+ * @method $this move($key, $db)
7381
+ * @method $this object($subcommand, $key)
7382
+ * @method $this persist($key)
7383
+ * @method $this pexpire($key, $milliseconds)
7384
+ * @method $this pexpireat($key, $timestamp)
7385
+ * @method $this pttl($key)
7386
+ * @method $this randomkey()
7387
+ * @method $this rename($key, $target)
7388
+ * @method $this renamenx($key, $target)
7389
+ * @method $this scan($cursor, array $options = null)
7390
+ * @method $this sort($key, array $options = null)
7391
+ * @method $this ttl($key)
7392
+ * @method $this type($key)
7393
+ * @method $this append($key, $value)
7394
+ * @method $this bitcount($key, $start = null, $end = null)
7395
+ * @method $this bitop($operation, $destkey, $key)
7396
+ * @method $this decr($key)
7397
+ * @method $this decrby($key, $decrement)
7398
+ * @method $this get($key)
7399
+ * @method $this getbit($key, $offset)
7400
+ * @method $this getrange($key, $start, $end)
7401
+ * @method $this getset($key, $value)
7402
+ * @method $this incr($key)
7403
+ * @method $this incrby($key, $increment)
7404
+ * @method $this incrbyfloat($key, $increment)
7405
+ * @method $this mget(array $keys)
7406
+ * @method $this mset(array $dictionary)
7407
+ * @method $this msetnx(array $dictionary)
7408
+ * @method $this psetex($key, $milliseconds, $value)
7409
+ * @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
7410
+ * @method $this setbit($key, $offset, $value)
7411
+ * @method $this setex($key, $seconds, $value)
7412
+ * @method $this setnx($key, $value)
7413
+ * @method $this setrange($key, $offset, $value)
7414
+ * @method $this strlen($key)
7415
+ * @method $this hdel($key, array $fields)
7416
+ * @method $this hexists($key, $field)
7417
+ * @method $this hget($key, $field)
7418
+ * @method $this hgetall($key)
7419
+ * @method $this hincrby($key, $field, $increment)
7420
+ * @method $this hincrbyfloat($key, $field, $increment)
7421
+ * @method $this hkeys($key)
7422
+ * @method $this hlen($key)
7423
+ * @method $this hmget($key, array $fields)
7424
+ * @method $this hmset($key, array $dictionary)
7425
+ * @method $this hscan($key, $cursor, array $options = null)
7426
+ * @method $this hset($key, $field, $value)
7427
+ * @method $this hsetnx($key, $field, $value)
7428
+ * @method $this hvals($key)
7429
+ * @method $this blpop(array $keys, $timeout)
7430
+ * @method $this brpop(array $keys, $timeout)
7431
+ * @method $this brpoplpush($source, $destination, $timeout)
7432
+ * @method $this lindex($key, $index)
7433
+ * @method $this linsert($key, $whence, $pivot, $value)
7434
+ * @method $this llen($key)
7435
+ * @method $this lpop($key)
7436
+ * @method $this lpush($key, array $values)
7437
+ * @method $this lpushx($key, $value)
7438
+ * @method $this lrange($key, $start, $stop)
7439
+ * @method $this lrem($key, $count, $value)
7440
+ * @method $this lset($key, $index, $value)
7441
+ * @method $this ltrim($key, $start, $stop)
7442
+ * @method $this rpop($key)
7443
+ * @method $this rpoplpush($source, $destination)
7444
+ * @method $this rpush($key, array $values)
7445
+ * @method $this rpushx($key, $value)
7446
+ * @method $this sadd($key, array $members)
7447
+ * @method $this scard($key)
7448
+ * @method $this sdiff(array $keys)
7449
+ * @method $this sdiffstore($destination, array $keys)
7450
+ * @method $this sinter(array $keys)
7451
+ * @method $this sinterstore($destination, array $keys)
7452
+ * @method $this sismember($key, $member)
7453
+ * @method $this smembers($key)
7454
+ * @method $this smove($source, $destination, $member)
7455
+ * @method $this spop($key)
7456
+ * @method $this srandmember($key, $count = null)
7457
+ * @method $this srem($key, $member)
7458
+ * @method $this sscan($key, $cursor, array $options = null)
7459
+ * @method $this sunion(array $keys)
7460
+ * @method $this sunionstore($destination, array $keys)
7461
+ * @method $this zadd($key, array $membersAndScoresDictionary)
7462
+ * @method $this zcard($key)
7463
+ * @method $this zcount($key, $min, $max)
7464
+ * @method $this zinterstore($destination, array $keys, array $options = null)
7465
+ * @method $this zrange($key, $start, $stop, array $options = null)
7466
+ * @method $this zrangebyscore($key, $min, $max, array $options = null)
7467
+ * @method $this zrank($key, $member)
7468
+ * @method $this zrem($key, $member)
7469
+ * @method $this zremrangebyrank($key, $start, $stop)
7470
+ * @method $this zremrangebyscore($key, $min, $max)
7471
+ * @method $this zrevrange($key, $start, $stop, array $options = null)
7472
+ * @method $this zrevrangebyscore($key, $min, $max, array $options = null)
7473
+ * @method $this zrevrank($key, $member)
7474
+ * @method $this zunionstore($destination, array $keys, array $options = null)
7475
+ * @method $this zscore($key, $member)
7476
+ * @method $this zscan($key, $cursor, array $options = null)
7477
+ * @method $this zrangebylex($key, $start, $stop, array $options = null)
7478
+ * @method $this zremrangebylex($key, $min, $max)
7479
+ * @method $this zlexcount($key, $min, $max)
7480
+ * @method $this pfadd($key, array $elements)
7481
+ * @method $this pfmerge($destinationKey, array $sourceKeys)
7482
+ * @method $this pfcount(array $keys)
7483
+ * @method $this pubsub($subcommand, $argument)
7484
+ * @method $this publish($channel, $message)
7485
+ * @method $this discard()
7486
+ * @method $this exec()
7487
+ * @method $this multi()
7488
+ * @method $this unwatch()
7489
+ * @method $this watch($key)
7490
+ * @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7491
+ * @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7492
+ * @method $this script($subcommand, $argument = null)
7493
+ * @method $this auth($password)
7494
+ * @method $this echo($message)
7495
+ * @method $this ping($message = null)
7496
+ * @method $this select($database)
7497
+ * @method $this bgrewriteaof()
7498
+ * @method $this bgsave()
7499
+ * @method $this client($subcommand, $argument = null)
7500
+ * @method $this config($subcommand, $argument = null)
7501
+ * @method $this dbsize()
7502
+ * @method $this flushall()
7503
+ * @method $this flushdb()
7504
+ * @method $this info($section = null)
7505
+ * @method $this lastsave()
7506
+ * @method $this save()
7507
+ * @method $this slaveof($host, $port)
7508
+ * @method $this slowlog($subcommand, $argument = null)
7509
+ * @method $this time()
7510
+ * @method $this command()
7511
+ *
7512
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7513
+ */
7514
+ interface ClientContextInterface
7515
+ {
7516
+
7517
+ /**
7518
+ * Sends the specified command instance to Redis.
7519
+ *
7520
+ * @param CommandInterface $command Command instance.
7521
+ *
7522
+ * @return mixed
7523
+ */
7524
+ public function executeCommand(CommandInterface $command);
7525
+
7526
+ /**
7527
+ * Sends the specified command with its arguments to Redis.
7528
+ *
7529
+ * @param string $method Command ID.
7530
+ * @param array $arguments Arguments for the command.
7531
+ *
7532
+ * @return mixed
7533
+ */
7534
+ public function __call($method, $arguments);
7535
+
7536
+ /**
7537
+ * Starts the execution of the context.
7538
+ *
7539
+ * @param mixed $callable Optional callback for execution.
7540
+ *
7541
+ * @return array
7542
+ */
7543
+ public function execute($callable = null);
7544
+ }
7545
+
7546
+ /**
7547
+ * Base exception class for network-related errors.
7548
+ *
7549
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7550
+ */
7551
+ abstract class CommunicationException extends PredisException
7552
+ {
7553
+ private $connection;
7554
+
7555
+ /**
7556
+ * @param NodeConnectionInterface $connection Connection that generated the exception.
7557
+ * @param string $message Error message.
7558
+ * @param int $code Error code.
7559
+ * @param Exception $innerException Inner exception for wrapping the original error.
7560
+ */
7561
+ public function __construct(
7562
+ NodeConnectionInterface $connection,
7563
+ $message = null,
7564
+ $code = null,
7565
+ Exception $innerException = null
7566
+ ) {
7567
+ parent::__construct($message, $code, $innerException);
7568
+ $this->connection = $connection;
7569
+ }
7570
+
7571
+ /**
7572
+ * Gets the connection that generated the exception.
7573
+ *
7574
+ * @return NodeConnectionInterface
7575
+ */
7576
+ public function getConnection()
7577
+ {
7578
+ return $this->connection;
7579
+ }
7580
+
7581
+ /**
7582
+ * Indicates if the receiver should reset the underlying connection.
7583
+ *
7584
+ * @return bool
7585
+ */
7586
+ public function shouldResetConnection()
7587
+ {
7588
+ return true;
7589
+ }
7590
+
7591
+ /**
7592
+ * Helper method to handle exceptions generated by a connection object.
7593
+ *
7594
+ * @param CommunicationException $exception Exception.
7595
+ *
7596
+ * @throws CommunicationException
7597
+ */
7598
+ public static function handle(CommunicationException $exception)
7599
+ {
7600
+ if ($exception->shouldResetConnection()) {
7601
+ $connection = $exception->getConnection();
7602
+
7603
+ if ($connection->isConnected()) {
7604
+ $connection->disconnect();
7605
+ }
7606
+ }
7607
+
7608
+ throw $exception;
7609
+ }
7610
+ }
7611
+
7612
+ /**
7613
+ * Interface defining a client able to execute commands against Redis.
7614
+ *
7615
+ * All the commands exposed by the client generally have the same signature as
7616
+ * described by the Redis documentation, but some of them offer an additional
7617
+ * and more friendly interface to ease programming which is described in the
7618
+ * following list of methods:
7619
+ *
7620
+ * @method int del(array $keys)
7621
+ * @method string dump($key)
7622
+ * @method int exists($key)
7623
+ * @method int expire($key, $seconds)
7624
+ * @method int expireat($key, $timestamp)
7625
+ * @method array keys($pattern)
7626
+ * @method int move($key, $db)
7627
+ * @method mixed object($subcommand, $key)
7628
+ * @method int persist($key)
7629
+ * @method int pexpire($key, $milliseconds)
7630
+ * @method int pexpireat($key, $timestamp)
7631
+ * @method int pttl($key)
7632
+ * @method string randomkey()
7633
+ * @method mixed rename($key, $target)
7634
+ * @method int renamenx($key, $target)
7635
+ * @method array scan($cursor, array $options = null)
7636
+ * @method array sort($key, array $options = null)
7637
+ * @method int ttl($key)
7638
+ * @method mixed type($key)
7639
+ * @method int append($key, $value)
7640
+ * @method int bitcount($key, $start = null, $end = null)
7641
+ * @method int bitop($operation, $destkey, $key)
7642
+ * @method int decr($key)
7643
+ * @method int decrby($key, $decrement)
7644
+ * @method string get($key)
7645
+ * @method int getbit($key, $offset)
7646
+ * @method string getrange($key, $start, $end)
7647
+ * @method string getset($key, $value)
7648
+ * @method int incr($key)
7649
+ * @method int incrby($key, $increment)
7650
+ * @method string incrbyfloat($key, $increment)
7651
+ * @method array mget(array $keys)
7652
+ * @method mixed mset(array $dictionary)
7653
+ * @method int msetnx(array $dictionary)
7654
+ * @method mixed psetex($key, $milliseconds, $value)
7655
+ * @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
7656
+ * @method int setbit($key, $offset, $value)
7657
+ * @method int setex($key, $seconds, $value)
7658
+ * @method int setnx($key, $value)
7659
+ * @method int setrange($key, $offset, $value)
7660
+ * @method int strlen($key)
7661
+ * @method int hdel($key, array $fields)
7662
+ * @method int hexists($key, $field)
7663
+ * @method string hget($key, $field)
7664
+ * @method array hgetall($key)
7665
+ * @method int hincrby($key, $field, $increment)
7666
+ * @method string hincrbyfloat($key, $field, $increment)
7667
+ * @method array hkeys($key)
7668
+ * @method int hlen($key)
7669
+ * @method array hmget($key, array $fields)
7670
+ * @method mixed hmset($key, array $dictionary)
7671
+ * @method array hscan($key, $cursor, array $options = null)
7672
+ * @method int hset($key, $field, $value)
7673
+ * @method int hsetnx($key, $field, $value)
7674
+ * @method array hvals($key)
7675
+ * @method array blpop(array $keys, $timeout)
7676
+ * @method array brpop(array $keys, $timeout)
7677
+ * @method array brpoplpush($source, $destination, $timeout)
7678
+ * @method string lindex($key, $index)
7679
+ * @method int linsert($key, $whence, $pivot, $value)
7680
+ * @method int llen($key)
7681
+ * @method string lpop($key)
7682
+ * @method int lpush($key, array $values)
7683
+ * @method int lpushx($key, $value)
7684
+ * @method array lrange($key, $start, $stop)
7685
+ * @method int lrem($key, $count, $value)
7686
+ * @method mixed lset($key, $index, $value)
7687
+ * @method mixed ltrim($key, $start, $stop)
7688
+ * @method string rpop($key)
7689
+ * @method string rpoplpush($source, $destination)
7690
+ * @method int rpush($key, array $values)
7691
+ * @method int rpushx($key, $value)
7692
+ * @method int sadd($key, array $members)
7693
+ * @method int scard($key)
7694
+ * @method array sdiff(array $keys)
7695
+ * @method int sdiffstore($destination, array $keys)
7696
+ * @method array sinter(array $keys)
7697
+ * @method int sinterstore($destination, array $keys)
7698
+ * @method int sismember($key, $member)
7699
+ * @method array smembers($key)
7700
+ * @method int smove($source, $destination, $member)
7701
+ * @method string spop($key)
7702
+ * @method string srandmember($key, $count = null)
7703
+ * @method int srem($key, $member)
7704
+ * @method array sscan($key, $cursor, array $options = null)
7705
+ * @method array sunion(array $keys)
7706
+ * @method int sunionstore($destination, array $keys)
7707
+ * @method int zadd($key, array $membersAndScoresDictionary)
7708
+ * @method int zcard($key)
7709
+ * @method string zcount($key, $min, $max)
7710
+ * @method int zinterstore($destination, array $keys, array $options = null)
7711
+ * @method array zrange($key, $start, $stop, array $options = null)
7712
+ * @method array zrangebyscore($key, $min, $max, array $options = null)
7713
+ * @method int zrank($key, $member)
7714
+ * @method int zrem($key, $member)
7715
+ * @method int zremrangebyrank($key, $start, $stop)
7716
+ * @method int zremrangebyscore($key, $min, $max)
7717
+ * @method array zrevrange($key, $start, $stop, array $options = null)
7718
+ * @method array zrevrangebyscore($key, $min, $max, array $options = null)
7719
+ * @method int zrevrank($key, $member)
7720
+ * @method int zunionstore($destination, array $keys, array $options = null)
7721
+ * @method string zscore($key, $member)
7722
+ * @method array zscan($key, $cursor, array $options = null)
7723
+ * @method array zrangebylex($key, $start, $stop, array $options = null)
7724
+ * @method int zremrangebylex($key, $min, $max)
7725
+ * @method int zlexcount($key, $min, $max)
7726
+ * @method int pfadd($key, array $elements)
7727
+ * @method mixed pfmerge($destinationKey, array $sourceKeys)
7728
+ * @method int pfcount(array $keys)
7729
+ * @method mixed pubsub($subcommand, $argument)
7730
+ * @method int publish($channel, $message)
7731
+ * @method mixed discard()
7732
+ * @method array exec()
7733
+ * @method mixed multi()
7734
+ * @method mixed unwatch()
7735
+ * @method mixed watch($key)
7736
+ * @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7737
+ * @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7738
+ * @method mixed script($subcommand, $argument = null)
7739
+ * @method mixed auth($password)
7740
+ * @method string echo($message)
7741
+ * @method mixed ping($message = null)
7742
+ * @method mixed select($database)
7743
+ * @method mixed bgrewriteaof()
7744
+ * @method mixed bgsave()
7745
+ * @method mixed client($subcommand, $argument = null)
7746
+ * @method mixed config($subcommand, $argument = null)
7747
+ * @method int dbsize()
7748
+ * @method mixed flushall()
7749
+ * @method mixed flushdb()
7750
+ * @method array info($section = null)
7751
+ * @method int lastsave()
7752
+ * @method mixed save()
7753
+ * @method mixed slaveof($host, $port)
7754
+ * @method mixed slowlog($subcommand, $argument = null)
7755
+ * @method array time()
7756
+ * @method array command()
7757
+ *
7758
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7759
+ */
7760
+ interface ClientInterface
7761
+ {
7762
+ /**
7763
+ * Returns the server profile used by the client.
7764
+ *
7765
+ * @return ProfileInterface
7766
+ */
7767
+ public function getProfile();
7768
+
7769
+ /**
7770
+ * Returns the client options specified upon initialization.
7771
+ *
7772
+ * @return OptionsInterface
7773
+ */
7774
+ public function getOptions();
7775
+
7776
+ /**
7777
+ * Opens the underlying connection to the server.
7778
+ */
7779
+ public function connect();
7780
+
7781
+ /**
7782
+ * Closes the underlying connection from the server.
7783
+ */
7784
+ public function disconnect();
7785
+
7786
+ /**
7787
+ * Returns the underlying connection instance.
7788
+ *
7789
+ * @return ConnectionInterface
7790
+ */
7791
+ public function getConnection();
7792
+
7793
+ /**
7794
+ * Creates a new instance of the specified Redis command.
7795
+ *
7796
+ * @param string $method Command ID.
7797
+ * @param array $arguments Arguments for the command.
7798
+ *
7799
+ * @return CommandInterface
7800
+ */
7801
+ public function createCommand($method, $arguments = array());
7802
+
7803
+ /**
7804
+ * Executes the specified Redis command.
7805
+ *
7806
+ * @param CommandInterface $command Command instance.
7807
+ *
7808
+ * @return mixed
7809
+ */
7810
+ public function executeCommand(CommandInterface $command);
7811
+
7812
+ /**
7813
+ * Creates a Redis command with the specified arguments and sends a request
7814
+ * to the server.
7815
+ *
7816
+ * @param string $method Command ID.
7817
+ * @param array $arguments Arguments for the command.
7818
+ *
7819
+ * @return mixed
7820
+ */
7821
+ public function __call($method, $arguments);
7822
+ }
7823
+
7824
+ /**
7825
+ * Exception class thrown when trying to use features not supported by certain
7826
+ * classes or abstractions of Predis.
7827
+ *
7828
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7829
+ */
7830
+ class NotSupportedException extends PredisException
7831
+ {
7832
+ }
7833
+
7834
+ /**
7835
+ * Exception class that identifies client-side errors.
7836
+ *
7837
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7838
+ */
7839
+ class ClientException extends PredisException
7840
+ {
7841
+ }
7842
+
7843
+ /**
7844
+ * Client class used for connecting and executing commands on Redis.
7845
+ *
7846
+ * This is the main high-level abstraction of Predis upon which various other
7847
+ * abstractions are built. Internally it aggregates various other classes each
7848
+ * one with its own responsibility and scope.
7849
+ *
7850
+ * {@inheritdoc}
7851
+ *
7852
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7853
+ */
7854
+ class Client implements ClientInterface
7855
+ {
7856
+ const VERSION = '1.0.0';
7857
+
7858
+ protected $connection;
7859
+ protected $options;
7860
+ private $profile;
7861
+
7862
+ /**
7863
+ * @param mixed $parameters Connection parameters for one or more servers.
7864
+ * @param mixed $options Options to configure some behaviours of the client.
7865
+ */
7866
+ public function __construct($parameters = null, $options = null)
7867
+ {
7868
+ $this->options = $this->createOptions($options ?: array());
7869
+ $this->connection = $this->createConnection($parameters ?: array());
7870
+ $this->profile = $this->options->profile;
7871
+ }
7872
+
7873
+ /**
7874
+ * Creates a new instance of Predis\Configuration\Options from different
7875
+ * types of arguments or simply returns the passed argument if it is an
7876
+ * instance of Predis\Configuration\OptionsInterface.
7877
+ *
7878
+ * @param mixed $options Client options.
7879
+ *
7880
+ * @return OptionsInterface
7881
+ */
7882
+ protected function createOptions($options)
7883
+ {
7884
+ if (is_array($options)) {
7885
+ return new Options($options);
7886
+ }
7887
+
7888
+ if ($options instanceof OptionsInterface) {
7889
+ return $options;
7890
+ }
7891
+
7892
+ throw new InvalidArgumentException("Invalid type for client options.");
7893
+ }
7894
+
7895
+ /**
7896
+ * Creates single or aggregate connections from different types of arguments
7897
+ * (string, array) or returns the passed argument if it is an instance of a
7898
+ * class implementing Predis\Connection\ConnectionInterface.
7899
+ *
7900
+ * Accepted types for connection parameters are:
7901
+ *
7902
+ * - Instance of Predis\Connection\ConnectionInterface.
7903
+ * - Instance of Predis\Connection\ParametersInterface.
7904
+ * - Array
7905
+ * - String
7906
+ * - Callable
7907
+ *
7908
+ * @param mixed $parameters Connection parameters or connection instance.
7909
+ *
7910
+ * @return ConnectionInterface
7911
+ */
7912
+ protected function createConnection($parameters)
7913
+ {
7914
+ if ($parameters instanceof ConnectionInterface) {
7915
+ return $parameters;
7916
+ }
7917
+
7918
+ if ($parameters instanceof ParametersInterface || is_string($parameters)) {
7919
+ return $this->options->connections->create($parameters);
7920
+ }
7921
+
7922
+ if (is_array($parameters)) {
7923
+ if (!isset($parameters[0])) {
7924
+ return $this->options->connections->create($parameters);
7925
+ }
7926
+
7927
+ $options = $this->options;
7928
+
7929
+ if ($options->defined('aggregate')) {
7930
+ $initializer = $this->getConnectionInitializerWrapper($options->aggregate);
7931
+ $connection = $initializer($parameters, $options);
7932
+ } else {
7933
+ if ($options->defined('replication') && $replication = $options->replication) {
7934
+ $connection = $replication;
7935
+ } else {
7936
+ $connection = $options->cluster;
7937
+ }
7938
+
7939
+ $options->connections->aggregate($connection, $parameters);
7940
+ }
7941
+
7942
+ return $connection;
7943
+ }
7944
+
7945
+ if (is_callable($parameters)) {
7946
+ $initializer = $this->getConnectionInitializerWrapper($parameters);
7947
+ $connection = $initializer($this->options);
7948
+
7949
+ return $connection;
7950
+ }
7951
+
7952
+ throw new InvalidArgumentException('Invalid type for connection parameters.');
7953
+ }
7954
+
7955
+ /**
7956
+ * Wraps a callable to make sure that its returned value represents a valid
7957
+ * connection type.
7958
+ *
7959
+ * @param mixed $callable
7960
+ *
7961
+ * @return \Closure
7962
+ */
7963
+ protected function getConnectionInitializerWrapper($callable)
7964
+ {
7965
+ return function () use ($callable) {
7966
+ $connection = call_user_func_array($callable, func_get_args());
7967
+
7968
+ if (!$connection instanceof ConnectionInterface) {
7969
+ throw new UnexpectedValueException(
7970
+ 'The callable connection initializer returned an invalid type.'
7971
+ );
7972
+ }
7973
+
7974
+ return $connection;
7975
+ };
7976
+ }
7977
+
7978
+ /**
7979
+ * {@inheritdoc}
7980
+ */
7981
+ public function getProfile()
7982
+ {
7983
+ return $this->profile;
7984
+ }
7985
+
7986
+ /**
7987
+ * {@inheritdoc}
7988
+ */
7989
+ public function getOptions()
7990
+ {
7991
+ return $this->options;
7992
+ }
7993
+
7994
+ /**
7995
+ * Creates a new client instance for the specified connection ID or alias,
7996
+ * only when working with an aggregate connection (cluster, replication).
7997
+ * The new client instances uses the same options of the original one.
7998
+ *
7999
+ * @param string $connectionID Identifier of a connection.
8000
+ *
8001
+ * @return Client
8002
+ */
8003
+ public function getClientFor($connectionID)
8004
+ {
8005
+ if (!$connection = $this->getConnectionById($connectionID)) {
8006
+ throw new InvalidArgumentException("Invalid connection ID: $connectionID.");
8007
+ }
8008
+
8009
+ return new static($connection, $this->options);
8010
+ }
8011
+
8012
+ /**
8013
+ * Opens the underlying connection and connects to the server.
8014
+ */
8015
+ public function connect()
8016
+ {
8017
+ $this->connection->connect();
8018
+ }
8019
+
8020
+ /**
8021
+ * Closes the underlying connection and disconnects from the server.
8022
+ */
8023
+ public function disconnect()
8024
+ {
8025
+ $this->connection->disconnect();
8026
+ }
8027
+
8028
+ /**
8029
+ * Closes the underlying connection and disconnects from the server.
8030
+ *
8031
+ * This is the same as `Client::disconnect()` as it does not actually send
8032
+ * the `QUIT` command to Redis, but simply closes the connection.
8033
+ */
8034
+ public function quit()
8035
+ {
8036
+ $this->disconnect();
8037
+ }
8038
+
8039
+ /**
8040
+ * Returns the current state of the underlying connection.
8041
+ *
8042
+ * @return bool
8043
+ */
8044
+ public function isConnected()
8045
+ {
8046
+ return $this->connection->isConnected();
8047
+ }
8048
+
8049
+ /**
8050
+ * {@inheritdoc}
8051
+ */
8052
+ public function getConnection()
8053
+ {
8054
+ return $this->connection;
8055
+ }
8056
+
8057
+ /**
8058
+ * Retrieves the specified connection from the aggregate connection when the
8059
+ * client is in cluster or replication mode.
8060
+ *
8061
+ * @param string $connectionID Index or alias of the single connection.
8062
+ *
8063
+ * @return Connection\NodeConnectionInterface
8064
+ *
8065
+ * @throws NotSupportedException
8066
+ */
8067
+ public function getConnectionById($connectionID)
8068
+ {
8069
+ if (!$this->connection instanceof AggregateConnectionInterface) {
8070
+ throw new NotSupportedException(
8071
+ 'Retrieving connections by ID is supported only by aggregate connections.'
8072
+ );
8073
+ }
8074
+
8075
+ return $this->connection->getConnectionById($connectionID);
8076
+ }
8077
+
8078
+ /**
8079
+ * Executes a command without filtering its arguments, parsing the response,
8080
+ * applying any prefix to keys or throwing exceptions on Redis errors even
8081
+ * regardless of client options.
8082
+ *
8083
+ * It is possibile to indentify Redis error responses from normal responses
8084
+ * using the second optional argument which is populated by reference.
8085
+ *
8086
+ * @param array $arguments Command arguments as defined by the command signature.
8087
+ * @param bool $error Set to TRUE when Redis returned an error response.
8088
+ *
8089
+ * @return mixed
8090
+ */
8091
+ public function executeRaw(array $arguments, &$error = null)
8092
+ {
8093
+ $error = false;
8094
+
8095
+ $response = $this->connection->executeCommand(
8096
+ new RawCommand($arguments)
8097
+ );
8098
+
8099
+ if ($response instanceof ResponseInterface) {
8100
+ if ($response instanceof ErrorResponseInterface) {
8101
+ $error = true;
8102
+ }
8103
+
8104
+ return (string) $response;
8105
+ }
8106
+
8107
+ return $response;
8108
+ }
8109
+
8110
+ /**
8111
+ * {@inheritdoc}
8112
+ */
8113
+ public function __call($commandID, $arguments)
8114
+ {
8115
+ return $this->executeCommand(
8116
+ $this->createCommand($commandID, $arguments)
8117
+ );
8118
+ }
8119
+
8120
+ /**
8121
+ * {@inheritdoc}
8122
+ */
8123
+ public function createCommand($commandID, $arguments = array())
8124
+ {
8125
+ return $this->profile->createCommand($commandID, $arguments);
8126
+ }
8127
+
8128
+ /**
8129
+ * {@inheritdoc}
8130
+ */
8131
+ public function executeCommand(CommandInterface $command)
8132
+ {
8133
+ $response = $this->connection->executeCommand($command);
8134
+
8135
+ if ($response instanceof ResponseInterface) {
8136
+ if ($response instanceof ErrorResponseInterface) {
8137
+ $response = $this->onErrorResponse($command, $response);
8138
+ }
8139
+
8140
+ return $response;
8141
+ }
8142
+
8143
+ return $command->parseResponse($response);
8144
+ }
8145
+
8146
+ /**
8147
+ * Handles -ERR responses returned by Redis.
8148
+ *
8149
+ * @param CommandInterface $command Redis command that generated the error.
8150
+ * @param ErrorResponseInterface $response Instance of the error response.
8151
+ *
8152
+ * @return mixed
8153
+ *
8154
+ * @throws ServerException
8155
+ */
8156
+ protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
8157
+ {
8158
+ if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
8159
+ $eval = $this->createCommand('EVAL');
8160
+ $eval->setRawArguments($command->getEvalArguments());
8161
+
8162
+ $response = $this->executeCommand($eval);
8163
+
8164
+ if (!$response instanceof ResponseInterface) {
8165
+ $response = $command->parseResponse($response);
8166
+ }
8167
+
8168
+ return $response;
8169
+ }
8170
+
8171
+ if ($this->options->exceptions) {
8172
+ throw new ServerException($response->getMessage());
8173
+ }
8174
+
8175
+ return $response;
8176
+ }
8177
+
8178
+ /**
8179
+ * Executes the specified initializer method on `$this` by adjusting the
8180
+ * actual invokation depending on the arity (0, 1 or 2 arguments). This is
8181
+ * simply an utility method to create Redis contexts instances since they
8182
+ * follow a common initialization path.
8183
+ *
8184
+ * @param string $initializer Method name.
8185
+ * @param array $argv Arguments for the method.
8186
+ *
8187
+ * @return mixed
8188
+ */
8189
+ private function sharedContextFactory($initializer, $argv = null)
8190
+ {
8191
+ switch (count($argv)) {
8192
+ case 0:
8193
+ return $this->$initializer();
8194
+
8195
+ case 1:
8196
+ return is_array($argv[0])
8197
+ ? $this->$initializer($argv[0])
8198
+ : $this->$initializer(null, $argv[0]);
8199
+
8200
+ case 2:
8201
+ list($arg0, $arg1) = $argv;
8202
+
8203
+ return $this->$initializer($arg0, $arg1);
8204
+
8205
+ default:
8206
+ return $this->$initializer($this, $argv);
8207
+ }
8208
+ }
8209
+
8210
+ /**
8211
+ * Creates a new pipeline context and returns it, or returns the results of
8212
+ * a pipeline executed inside the optionally provided callable object.
8213
+ *
8214
+ * @param mixed ... Array of options, a callable for execution, or both.
8215
+ *
8216
+ * @return Pipeline|array
8217
+ */
8218
+ public function pipeline(/* arguments */)
8219
+ {
8220
+ return $this->sharedContextFactory('createPipeline', func_get_args());
8221
+ }
8222
+
8223
+ /**
8224
+ * Actual pipeline context initializer method.
8225
+ *
8226
+ * @param array $options Options for the context.
8227
+ * @param mixed $callable Optional callable used to execute the context.
8228
+ *
8229
+ * @return Pipeline|array
8230
+ */
8231
+ protected function createPipeline(array $options = null, $callable = null)
8232
+ {
8233
+ if (isset($options['atomic']) && $options['atomic']) {
8234
+ $class = 'Predis\Pipeline\Atomic';
8235
+ } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
8236
+ $class = 'Predis\Pipeline\FireAndForget';
8237
+ } else {
8238
+ $class = 'Predis\Pipeline\Pipeline';
8239
+ }
8240
+
8241
+ /*
8242
+ * @var ClientContextInterface
8243
+ */
8244
+ $pipeline = new $class($this);
8245
+
8246
+ if (isset($callable)) {
8247
+ return $pipeline->execute($callable);
8248
+ }
8249
+
8250
+ return $pipeline;
8251
+ }
8252
+
8253
+ /**
8254
+ * Creates a new transaction context and returns it, or returns the results
8255
+ * of a transaction executed inside the optionally provided callable object.
8256
+ *
8257
+ * @param mixed ... Array of options, a callable for execution, or both.
8258
+ *
8259
+ * @return MultiExecTransaction|array
8260
+ */
8261
+ public function transaction(/* arguments */)
8262
+ {
8263
+ return $this->sharedContextFactory('createTransaction', func_get_args());
8264
+ }
8265
+
8266
+ /**
8267
+ * Actual transaction context initializer method.
8268
+ *
8269
+ * @param array $options Options for the context.
8270
+ * @param mixed $callable Optional callable used to execute the context.
8271
+ *
8272
+ * @return MultiExecTransaction|array
8273
+ */
8274
+ protected function createTransaction(array $options = null, $callable = null)
8275
+ {
8276
+ $transaction = new MultiExecTransaction($this, $options);
8277
+
8278
+ if (isset($callable)) {
8279
+ return $transaction->execute($callable);
8280
+ }
8281
+
8282
+ return $transaction;
8283
+ }
8284
+
8285
+ /**
8286
+ * Creates a new publis/subscribe context and returns it, or starts its loop
8287
+ * inside the optionally provided callable object.
8288
+ *
8289
+ * @param mixed ... Array of options, a callable for execution, or both.
8290
+ *
8291
+ * @return PubSubConsumer|null
8292
+ */
8293
+ public function pubSubLoop(/* arguments */)
8294
+ {
8295
+ return $this->sharedContextFactory('createPubSub', func_get_args());
8296
+ }
8297
+
8298
+ /**
8299
+ * Actual publish/subscribe context initializer method.
8300
+ *
8301
+ * @param array $options Options for the context.
8302
+ * @param mixed $callable Optional callable used to execute the context.
8303
+ *
8304
+ * @return PubSubConsumer|null
8305
+ */
8306
+ protected function createPubSub(array $options = null, $callable = null)
8307
+ {
8308
+ $pubsub = new PubSubConsumer($this, $options);
8309
+
8310
+ if (!isset($callable)) {
8311
+ return $pubsub;
8312
+ }
8313
+
8314
+ foreach ($pubsub as $message) {
8315
+ if (call_user_func($callable, $pubsub, $message) === false) {
8316
+ $pubsub->stop();
8317
+ }
8318
+ }
8319
+ }
8320
+
8321
+ /**
8322
+ * Creates a new monitor consumer and returns it.
8323
+ *
8324
+ * @return MonitorConsumer
8325
+ */
8326
+ public function monitor()
8327
+ {
8328
+ return new MonitorConsumer($this);
8329
+ }
8330
+ }
8331
+
8332
+ /**
8333
+ * Implements a lightweight PSR-0 compliant autoloader for Predis.
8334
+ *
8335
+ * @author Eric Naeseth <eric@thumbtack.com>
8336
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8337
+ */
8338
+ class Autoloader
8339
+ {
8340
+ private $directory;
8341
+ private $prefix;
8342
+ private $prefixLength;
8343
+
8344
+ /**
8345
+ * @param string $baseDirectory Base directory where the source files are located.
8346
+ */
8347
+ public function __construct($baseDirectory = __DIR__)
8348
+ {
8349
+ $this->directory = $baseDirectory;
8350
+ $this->prefix = __NAMESPACE__ . '\\';
8351
+ $this->prefixLength = strlen($this->prefix);
8352
+ }
8353
+
8354
+ /**
8355
+ * Registers the autoloader class with the PHP SPL autoloader.
8356
+ *
8357
+ * @param bool $prepend Prepend the autoloader on the stack instead of appending it.
8358
+ */
8359
+ public static function register($prepend = false)
8360
+ {
8361
+ spl_autoload_register(array(new self, 'autoload'), true, $prepend);
8362
+ }
8363
+
8364
+ /**
8365
+ * Loads a class from a file using its fully qualified name.
8366
+ *
8367
+ * @param string $className Fully qualified name of a class.
8368
+ */
8369
+ public function autoload($className)
8370
+ {
8371
+ if (0 === strpos($className, $this->prefix)) {
8372
+ $parts = explode('\\', substr($className, $this->prefixLength));
8373
+ $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
8374
+
8375
+ if (is_file($filepath)) {
8376
+ require($filepath);
8377
+ }
8378
+ }
8379
+ }
8380
+ }
8381
+
8382
+ /* --------------------------------------------------------------------------- */
8383
+
8384
+ namespace Predis\Configuration;
8385
+
8386
+ use InvalidArgumentException;
8387
+ use Predis\Connection\Aggregate\ClusterInterface;
8388
+ use Predis\Connection\Aggregate\PredisCluster;
8389
+ use Predis\Connection\Aggregate\RedisCluster;
8390
+ use Predis\Connection\Factory;
8391
+ use Predis\Connection\FactoryInterface;
8392
+ use Predis\Command\Processor\KeyPrefixProcessor;
8393
+ use Predis\Command\Processor\ProcessorInterface;
8394
+ use Predis\Profile\Factory as Predis_Factory;
8395
+ use Predis\Profile\ProfileInterface;
8396
+ use Predis\Profile\RedisProfile;
8397
+ use Predis\Connection\Aggregate\MasterSlaveReplication;
8398
+ use Predis\Connection\Aggregate\ReplicationInterface;
8399
+
8400
+ /**
8401
+ * Defines an handler used by Predis\Configuration\Options to filter, validate
8402
+ * or return default values for a given option.
8403
+ *
8404
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8405
+ */
8406
+ interface OptionInterface
8407
+ {
8408
+ /**
8409
+ * Filters and validates the passed value.
8410
+ *
8411
+ * @param OptionsInterface $options Options container.
8412
+ * @param mixed $value Input value.
8413
+ *
8414
+ * @return mixed
8415
+ */
8416
+ public function filter(OptionsInterface $options, $value);
8417
+
8418
+ /**
8419
+ * Returns the default value for the option.
8420
+ *
8421
+ * @param OptionsInterface $options Options container.
8422
+ *
8423
+ * @return mixed
8424
+ */
8425
+ public function getDefault(OptionsInterface $options);
8426
+ }
8427
+
8428
+ /**
8429
+ * Interface defining a container for client options.
8430
+ *
8431
+ * @property-read mixed aggregate Custom connection aggregator.
8432
+ * @property-read mixed cluster Aggregate connection for clustering.
8433
+ * @property-read mixed connections Connection factory.
8434
+ * @property-read mixed exceptions Toggles exceptions in client for -ERR responses.
8435
+ * @property-read mixed prefix Key prefixing strategy using the given prefix.
8436
+ * @property-read mixed profile Server profile.
8437
+ * @property-read mixed replication Aggregate connection for replication.
8438
+ *
8439
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8440
+ */
8441
+ interface OptionsInterface
8442
+ {
8443
+ /**
8444
+ * Returns the default value for the given option.
8445
+ *
8446
+ * @param string $option Name of the option.
8447
+ *
8448
+ * @return mixed|null
8449
+ */
8450
+ public function getDefault($option);
8451
+
8452
+ /**
8453
+ * Checks if the given option has been set by the user upon initialization.
8454
+ *
8455
+ * @param string $option Name of the option.
8456
+ *
8457
+ * @return bool
8458
+ */
8459
+ public function defined($option);
8460
+
8461
+ /**
8462
+ * Checks if the given option has been set and does not evaluate to NULL.
8463
+ *
8464
+ * @param string $option Name of the option.
8465
+ *
8466
+ * @return bool
8467
+ */
8468
+ public function __isset($option);
8469
+
8470
+ /**
8471
+ * Returns the value of the given option.
8472
+ *
8473
+ * @param string $option Name of the option.
8474
+ *
8475
+ * @return mixed|null
8476
+ */
8477
+ public function __get($option);
8478
+ }
8479
+
8480
+ /**
8481
+ * Configures a command processor that apply the specified prefix string to a
8482
+ * series of Redis commands considered prefixable.
8483
+ *
8484
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8485
+ */
8486
+ class PrefixOption implements OptionInterface
8487
+ {
8488
+ /**
8489
+ * {@inheritdoc}
8490
+ */
8491
+ public function filter(OptionsInterface $options, $value)
8492
+ {
8493
+ if ($value instanceof ProcessorInterface) {
8494
+ return $value;
8495
+ }
8496
+
8497
+ return new KeyPrefixProcessor($value);
8498
+ }
8499
+
8500
+ /**
8501
+ * {@inheritdoc}
8502
+ */
8503
+ public function getDefault(OptionsInterface $options)
8504
+ {
8505
+ // NOOP
8506
+ }
8507
+ }
8508
+
8509
+ /**
8510
+ * Configures the server profile to be used by the client to create command
8511
+ * instances depending on the specified version of the Redis server.
8512
+ *
8513
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8514
+ */
8515
+ class ProfileOption implements OptionInterface
8516
+ {
8517
+ /**
8518
+ * Sets the commands processors that need to be applied to the profile.
8519
+ *
8520
+ * @param OptionsInterface $options Client options.
8521
+ * @param ProfileInterface $profile Server profile.
8522
+ */
8523
+ protected function setProcessors(OptionsInterface $options, ProfileInterface $profile)
8524
+ {
8525
+ if (isset($options->prefix) && $profile instanceof RedisProfile) {
8526
+ // NOTE: directly using __get('prefix') is actually a workaround for
8527
+ // HHVM 2.3.0. It's correct and respects the options interface, it's
8528
+ // just ugly. We will remove this hack when HHVM will fix re-entrant
8529
+ // calls to __get() once and for all.
8530
+
8531
+ $profile->setProcessor($options->__get('prefix'));
8532
+ }
8533
+ }
8534
+
8535
+ /**
8536
+ * {@inheritdoc}
8537
+ */
8538
+ public function filter(OptionsInterface $options, $value)
8539
+ {
8540
+ if (is_string($value)) {
8541
+ $value = Predis_Factory::get($value);
8542
+ $this->setProcessors($options, $value);
8543
+ } elseif (!$value instanceof ProfileInterface) {
8544
+ throw new InvalidArgumentException('Invalid value for the profile option.');
8545
+ }
8546
+
8547
+ return $value;
8548
+ }
8549
+
8550
+ /**
8551
+ * {@inheritdoc}
8552
+ */
8553
+ public function getDefault(OptionsInterface $options)
8554
+ {
8555
+ $profile = Predis_Factory::getDefault();
8556
+ $this->setProcessors($options, $profile);
8557
+
8558
+ return $profile;
8559
+ }
8560
+ }
8561
+
8562
+ /**
8563
+ * Configures an aggregate connection used for master/slave replication among
8564
+ * multiple Redis nodes.
8565
+ *
8566
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8567
+ */
8568
+ class ReplicationOption implements OptionInterface
8569
+ {
8570
+ /**
8571
+ * {@inheritdoc}
8572
+ *
8573
+ * @todo There's more code than needed due to a bug in filter_var() as
8574
+ * discussed here https://bugs.php.net/bug.php?id=49510 and different
8575
+ * behaviours when encountering NULL values on PHP 5.3.
8576
+ */
8577
+ public function filter(OptionsInterface $options, $value)
8578
+ {
8579
+ if ($value instanceof ReplicationInterface) {
8580
+ return $value;
8581
+ }
8582
+
8583
+ if (is_bool($value) || $value === null) {
8584
+ return $value ? $this->getDefault($options) : null;
8585
+ }
8586
+
8587
+ if (
8588
+ !is_object($value) &&
8589
+ null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)
8590
+ ) {
8591
+ return $asbool ? $this->getDefault($options) : null;
8592
+ }
8593
+
8594
+ throw new InvalidArgumentException(
8595
+ "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected."
8596
+ );
8597
+ }
8598
+
8599
+ /**
8600
+ * {@inheritdoc}
8601
+ */
8602
+ public function getDefault(OptionsInterface $options)
8603
+ {
8604
+ return new MasterSlaveReplication();
8605
+ }
8606
+ }
8607
+
8608
+ /**
8609
+ * Manages Predis options with filtering, conversion and lazy initialization of
8610
+ * values using a mini-DI container approach.
8611
+ *
8612
+ * {@inheritdoc}
8613
+ *
8614
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8615
+ */
8616
+ class Options implements OptionsInterface
8617
+ {
8618
+ protected $input;
8619
+ protected $options;
8620
+ protected $handlers;
8621
+
8622
+ /**
8623
+ * @param array $options Array of options with their values
8624
+ */
8625
+ public function __construct(array $options = array())
8626
+ {
8627
+ $this->input = $options;
8628
+ $this->options = array();
8629
+ $this->handlers = $this->getHandlers();
8630
+ }
8631
+
8632
+ /**
8633
+ * Ensures that the default options are initialized.
8634
+ *
8635
+ * @return array
8636
+ */
8637
+ protected function getHandlers()
8638
+ {
8639
+ return array(
8640
+ 'cluster' => 'Predis\Configuration\ClusterOption',
8641
+ 'connections' => 'Predis\Configuration\ConnectionFactoryOption',
8642
+ 'exceptions' => 'Predis\Configuration\ExceptionsOption',
8643
+ 'prefix' => 'Predis\Configuration\PrefixOption',
8644
+ 'profile' => 'Predis\Configuration\ProfileOption',
8645
+ 'replication' => 'Predis\Configuration\ReplicationOption',
8646
+ );
8647
+ }
8648
+
8649
+ /**
8650
+ * {@inheritdoc}
8651
+ */
8652
+ public function getDefault($option)
8653
+ {
8654
+ if (isset($this->handlers[$option])) {
8655
+ $handler = $this->handlers[$option];
8656
+ $handler = new $handler();
8657
+
8658
+ return $handler->getDefault($this);
8659
+ }
8660
+ }
8661
+
8662
+ /**
8663
+ * {@inheritdoc}
8664
+ */
8665
+ public function defined($option)
8666
+ {
8667
+ return (
8668
+ array_key_exists($option, $this->options) ||
8669
+ array_key_exists($option, $this->input)
8670
+ );
8671
+ }
8672
+
8673
+ /**
8674
+ * {@inheritdoc}
8675
+ */
8676
+ public function __isset($option)
8677
+ {
8678
+ return (
8679
+ array_key_exists($option, $this->options) ||
8680
+ array_key_exists($option, $this->input)
8681
+ ) && $this->__get($option) !== null;
8682
+ }
8683
+
8684
+ /**
8685
+ * {@inheritdoc}
8686
+ */
8687
+ public function __get($option)
8688
+ {
8689
+ if (isset($this->options[$option]) || array_key_exists($option, $this->options)) {
8690
+ return $this->options[$option];
8691
+ }
8692
+
8693
+ if (isset($this->input[$option]) || array_key_exists($option, $this->input)) {
8694
+ $value = $this->input[$option];
8695
+ unset($this->input[$option]);
8696
+
8697
+ if (method_exists($value, '__invoke')) {
8698
+ $value = $value($this, $option);
8699
+ }
8700
+
8701
+ if (isset($this->handlers[$option])) {
8702
+ $handler = $this->handlers[$option];
8703
+ $handler = new $handler();
8704
+ $value = $handler->filter($this, $value);
8705
+ }
8706
+
8707
+ return $this->options[$option] = $value;
8708
+ }
8709
+
8710
+ if (isset($this->handlers[$option])) {
8711
+ return $this->options[$option] = $this->getDefault($option);
8712
+ }
8713
+
8714
+ return null;
8715
+ }
8716
+ }
8717
+
8718
+ /**
8719
+ * Configures a connection factory used by the client to create new connection
8720
+ * instances for single Redis nodes.
8721
+ *
8722
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8723
+ */
8724
+ class ConnectionFactoryOption implements OptionInterface
8725
+ {
8726
+ /**
8727
+ * {@inheritdoc}
8728
+ */
8729
+ public function filter(OptionsInterface $options, $value)
8730
+ {
8731
+ if ($value instanceof FactoryInterface) {
8732
+ return $value;
8733
+ } elseif (is_array($value)) {
8734
+ $factory = $this->getDefault($options);
8735
+
8736
+ foreach ($value as $scheme => $initializer) {
8737
+ $factory->define($scheme, $initializer);
8738
+ }
8739
+
8740
+ return $factory;
8741
+ } else {
8742
+ throw new InvalidArgumentException(
8743
+ 'Invalid value provided for the connections option.'
8744
+ );
8745
+ }
8746
+ }
8747
+
8748
+ /**
8749
+ * {@inheritdoc}
8750
+ */
8751
+ public function getDefault(OptionsInterface $options)
8752
+ {
8753
+ return new Factory();
8754
+ }
8755
+ }
8756
+
8757
+ /**
8758
+ * Configures whether consumers (such as the client) should throw exceptions on
8759
+ * Redis errors (-ERR responses) or just return instances of error responses.
8760
+ *
8761
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8762
+ */
8763
+ class ExceptionsOption implements OptionInterface
8764
+ {
8765
+ /**
8766
+ * {@inheritdoc}
8767
+ */
8768
+ public function filter(OptionsInterface $options, $value)
8769
+ {
8770
+ return filter_var($value, FILTER_VALIDATE_BOOLEAN);
8771
+ }
8772
+
8773
+ /**
8774
+ * {@inheritdoc}
8775
+ */
8776
+ public function getDefault(OptionsInterface $options)
8777
+ {
8778
+ return true;
8779
+ }
8780
+ }
8781
+
8782
+ /**
8783
+ * Configures an aggregate connection used for clustering
8784
+ * multiple Redis nodes using various implementations with
8785
+ * different algorithms or strategies.
8786
+ *
8787
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8788
+ */
8789
+ class ClusterOption implements OptionInterface
8790
+ {
8791
+ /**
8792
+ * Creates a new cluster connection from on a known descriptive name.
8793
+ *
8794
+ * @param OptionsInterface $options Instance of the client options.
8795
+ * @param string $id Descriptive identifier of the cluster type (`predis`, `redis-cluster`)
8796
+ *
8797
+ * @return ClusterInterface|null
8798
+ */
8799
+ protected function createByDescription(OptionsInterface $options, $id)
8800
+ {
8801
+ switch ($id) {
8802
+ case 'predis':
8803
+ case 'predis-cluster':
8804
+ return new PredisCluster();
8805
+
8806
+ case 'redis':
8807
+ case 'redis-cluster':
8808
+ return new RedisCluster($options->connections);
8809
+
8810
+ default:
8811
+ return;
8812
+ }
8813
+ }
8814
+
8815
+ /**
8816
+ * {@inheritdoc}
8817
+ */
8818
+ public function filter(OptionsInterface $options, $value)
8819
+ {
8820
+ if (is_string($value)) {
8821
+ $value = $this->createByDescription($options, $value);
8822
+ }
8823
+
8824
+ if (!$value instanceof ClusterInterface) {
8825
+ throw new InvalidArgumentException(
8826
+ "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected."
8827
+ );
8828
+ }
8829
+
8830
+ return $value;
8831
+ }
8832
+
8833
+ /**
8834
+ * {@inheritdoc}
8835
+ */
8836
+ public function getDefault(OptionsInterface $options)
8837
+ {
8838
+ return new PredisCluster();
8839
+ }
8840
+ }
8841
+
8842
+ /* --------------------------------------------------------------------------- */
8843
+
8844
+ namespace Predis\Response;
8845
+
8846
+ use Predis\PredisException;
8847
+
8848
+ /**
8849
+ * Represents a complex response object from Redis.
8850
+ *
8851
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8852
+ */
8853
+ interface ResponseInterface
8854
+ {
8855
+ }
8856
+
8857
+ /**
8858
+ * Represents an error returned by Redis (responses identified by "-" in the
8859
+ * Redis protocol) during the execution of an operation on the server.
8860
+ *
8861
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8862
+ */
8863
+ interface ErrorInterface extends ResponseInterface
8864
+ {
8865
+ /**
8866
+ * Returns the error message
8867
+ *
8868
+ * @return string
8869
+ */
8870
+ public function getMessage();
8871
+
8872
+ /**
8873
+ * Returns the error type (e.g. ERR, ASK, MOVED)
8874
+ *
8875
+ * @return string
8876
+ */
8877
+ public function getErrorType();
8878
+ }
8879
+
8880
+ /**
8881
+ * Represents a status response returned by Redis.
8882
+ *
8883
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8884
+ */
8885
+ class Status implements ResponseInterface
8886
+ {
8887
+ private static $OK;
8888
+ private static $QUEUED;
8889
+
8890
+ private $payload;
8891
+
8892
+ /**
8893
+ * @param string $payload Payload of the status response as returned by Redis.
8894
+ */
8895
+ public function __construct($payload)
8896
+ {
8897
+ $this->payload = $payload;
8898
+ }
8899
+
8900
+ /**
8901
+ * Converts the response object to its string representation.
8902
+ *
8903
+ * @return string
8904
+ */
8905
+ public function __toString()
8906
+ {
8907
+ return $this->payload;
8908
+ }
8909
+
8910
+ /**
8911
+ * Returns the payload of status response.
8912
+ *
8913
+ * @return string
8914
+ */
8915
+ public function getPayload()
8916
+ {
8917
+ return $this->payload;
8918
+ }
8919
+
8920
+ /**
8921
+ * Returns an instance of a status response object.
8922
+ *
8923
+ * Common status responses such as OK or QUEUED are cached in order to lower
8924
+ * the global memory usage especially when using pipelines.
8925
+ *
8926
+ * @param string $payload Status response payload.
8927
+ *
8928
+ * @return string
8929
+ */
8930
+ public static function get($payload)
8931
+ {
8932
+ switch ($payload) {
8933
+ case 'OK':
8934
+ case 'QUEUED':
8935
+ if (isset(self::$$payload)) {
8936
+ return self::$$payload;
8937
+ }
8938
+
8939
+ return self::$$payload = new self($payload);
8940
+
8941
+ default:
8942
+ return new self($payload);
8943
+ }
8944
+ }
8945
+ }
8946
+
8947
+ /**
8948
+ * Represents an error returned by Redis (-ERR responses) during the execution
8949
+ * of a command on the server.
8950
+ *
8951
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8952
+ */
8953
+ class Error implements ErrorInterface
8954
+ {
8955
+ private $message;
8956
+
8957
+ /**
8958
+ * @param string $message Error message returned by Redis
8959
+ */
8960
+ public function __construct($message)
8961
+ {
8962
+ $this->message = $message;
8963
+ }
8964
+
8965
+ /**
8966
+ * {@inheritdoc}
8967
+ */
8968
+ public function getMessage()
8969
+ {
8970
+ return $this->message;
8971
+ }
8972
+
8973
+ /**
8974
+ * {@inheritdoc}
8975
+ */
8976
+ public function getErrorType()
8977
+ {
8978
+ list($errorType, ) = explode(' ', $this->getMessage(), 2);
8979
+
8980
+ return $errorType;
8981
+ }
8982
+
8983
+ /**
8984
+ * Converts the object to its string representation.
8985
+ *
8986
+ * @return string
8987
+ */
8988
+ public function __toString()
8989
+ {
8990
+ return $this->getMessage();
8991
+ }
8992
+ }
8993
+
8994
+ /**
8995
+ * Exception class that identifies server-side Redis errors.
8996
+ *
8997
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8998
+ */
8999
+ class ServerException extends PredisException implements ErrorInterface
9000
+ {
9001
+ /**
9002
+ * Gets the type of the error returned by Redis.
9003
+ *
9004
+ * @return string
9005
+ */
9006
+ public function getErrorType()
9007
+ {
9008
+ list($errorType, ) = explode(' ', $this->getMessage(), 2);
9009
+
9010
+ return $errorType;
9011
+ }
9012
+
9013
+ /**
9014
+ * Converts the exception to an instance of Predis\Response\Error.
9015
+ *
9016
+ * @return Error
9017
+ */
9018
+ public function toErrorResponse()
9019
+ {
9020
+ return new Error($this->getMessage());
9021
+ }
9022
+ }
9023
+
9024
+ /* --------------------------------------------------------------------------- */
9025
+
9026
+ namespace Predis\Protocol\Text\Handler;
9027
+
9028
+ use Predis\CommunicationException;
9029
+ use Predis\Connection\CompositeConnectionInterface;
9030
+ use Predis\Protocol\ProtocolException;
9031
+ use Predis\Response\Error;
9032
+ use Predis\Response\Status;
9033
+ use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
9034
+
9035
+ /**
9036
+ * Defines a pluggable handler used to parse a particular type of response.
9037
+ *
9038
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9039
+ */
9040
+ interface ResponseHandlerInterface
9041
+ {
9042
+ /**
9043
+ * Deserializes a response returned by Redis and reads more data from the
9044
+ * connection if needed.
9045
+ *
9046
+ * @param CompositeConnectionInterface $connection Redis connection.
9047
+ * @param string $payload String payload.
9048
+ *
9049
+ * @return mixed
9050
+ */
9051
+ public function handle(CompositeConnectionInterface $connection, $payload);
9052
+ }
9053
+
9054
+ /**
9055
+ * Handler for the status response type in the standard Redis wire protocol. It
9056
+ * translates certain classes of status response to PHP objects or just returns
9057
+ * the payload as a string.
9058
+ *
9059
+ * @link http://redis.io/topics/protocol
9060
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9061
+ */
9062
+ class StatusResponse implements ResponseHandlerInterface
9063
+ {
9064
+ /**
9065
+ * {@inheritdoc}
9066
+ */
9067
+ public function handle(CompositeConnectionInterface $connection, $payload)
9068
+ {
9069
+ return Status::get($payload);
9070
+ }
9071
+ }
9072
+
9073
+ /**
9074
+ * Handler for the multibulk response type in the standard Redis wire protocol.
9075
+ * It returns multibulk responses as iterators that can stream bulk elements.
9076
+ *
9077
+ * Streamable multibulk responses are not globally supported by the abstractions
9078
+ * built-in into Predis, such as transactions or pipelines. Use them with care!
9079
+ *
9080
+ * @link http://redis.io/topics/protocol
9081
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9082
+ */
9083
+ class StreamableMultiBulkResponse implements ResponseHandlerInterface
9084
+ {
9085
+ /**
9086
+ * {@inheritdoc}
9087
+ */
9088
+ public function handle(CompositeConnectionInterface $connection, $payload)
9089
+ {
9090
+ $length = (int) $payload;
9091
+
9092
+ if ("$length" != $payload) {
9093
+ CommunicationException::handle(new ProtocolException(
9094
+ $connection, "Cannot parse '$payload' as a valid length for a multi-bulk response."
9095
+ ));
9096
+ }
9097
+
9098
+ return new MultiBulkIterator($connection, $length);
9099
+ }
9100
+ }
9101
+
9102
+ /**
9103
+ * Handler for the multibulk response type in the standard Redis wire protocol.
9104
+ * It returns multibulk responses as PHP arrays.
9105
+ *
9106
+ * @link http://redis.io/topics/protocol
9107
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9108
+ */
9109
+ class MultiBulkResponse implements ResponseHandlerInterface
9110
+ {
9111
+ /**
9112
+ * {@inheritdoc}
9113
+ */
9114
+ public function handle(CompositeConnectionInterface $connection, $payload)
9115
+ {
9116
+ $length = (int) $payload;
9117
+
9118
+ if ("$length" !== $payload) {
9119
+ CommunicationException::handle(new ProtocolException(
9120
+ $connection, "Cannot parse '$payload' as a valid length of a multi-bulk response."
9121
+ ));
9122
+ }
9123
+
9124
+ if ($length === -1) {
9125
+ return null;
9126
+ }
9127
+
9128
+ $list = array();
9129
+
9130
+ if ($length > 0) {
9131
+ $handlersCache = array();
9132
+ $reader = $connection->getProtocol()->getResponseReader();
9133
+
9134
+ for ($i = 0; $i < $length; $i++) {
9135
+ $header = $connection->readLine();
9136
+ $prefix = $header[0];
9137
+
9138
+ if (isset($handlersCache[$prefix])) {
9139
+ $handler = $handlersCache[$prefix];
9140
+ } else {
9141
+ $handler = $reader->getHandler($prefix);
9142
+ $handlersCache[$prefix] = $handler;
9143
+ }
9144
+
9145
+ $list[$i] = $handler->handle($connection, substr($header, 1));
9146
+ }
9147
+ }
9148
+
9149
+ return $list;
9150
+ }
9151
+ }
9152
+
9153
+ /**
9154
+ * Handler for the error response type in the standard Redis wire protocol.
9155
+ * It translates the payload to a complex response object for Predis.
9156
+ *
9157
+ * @link http://redis.io/topics/protocol
9158
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9159
+ */
9160
+ class ErrorResponse implements ResponseHandlerInterface
9161
+ {
9162
+ /**
9163
+ * {@inheritdoc}
9164
+ */
9165
+ public function handle(CompositeConnectionInterface $connection, $payload)
9166
+ {
9167
+ return new Error($payload);
9168
+ }
9169
+ }
9170
+
9171
+ /**
9172
+ * Handler for the integer response type in the standard Redis wire protocol.
9173
+ * It translates the payload an integer or NULL.
9174
+ *
9175
+ * @link http://redis.io/topics/protocol
9176
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9177
+ */
9178
+ class IntegerResponse implements ResponseHandlerInterface
9179
+ {
9180
+ /**
9181
+ * {@inheritdoc}
9182
+ */
9183
+ public function handle(CompositeConnectionInterface $connection, $payload)
9184
+ {
9185
+ if (is_numeric($payload)) {
9186
+ return (int) $payload;
9187
+ }
9188
+
9189
+ if ($payload !== 'nil') {
9190
+ CommunicationException::handle(new ProtocolException(
9191
+ $connection, "Cannot parse '$payload' as a valid numeric response."
9192
+ ));
9193
+ }
9194
+
9195
+ return null;
9196
+ }
9197
+ }
9198
+
9199
+ /**
9200
+ * Handler for the bulk response type in the standard Redis wire protocol.
9201
+ * It translates the payload to a string or a NULL.
9202
+ *
9203
+ * @link http://redis.io/topics/protocol
9204
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9205
+ */
9206
+ class BulkResponse implements ResponseHandlerInterface
9207
+ {
9208
+ /**
9209
+ * {@inheritdoc}
9210
+ */
9211
+ public function handle(CompositeConnectionInterface $connection, $payload)
9212
+ {
9213
+ $length = (int) $payload;
9214
+
9215
+ if ("$length" !== $payload) {
9216
+ CommunicationException::handle(new ProtocolException(
9217
+ $connection, "Cannot parse '$payload' as a valid length for a bulk response."
9218
+ ));
9219
+ }
9220
+
9221
+ if ($length >= 0) {
9222
+ return substr($connection->readBuffer($length + 2), 0, -2);
9223
+ }
9224
+
9225
+ if ($length == -1) {
9226
+ return null;
9227
+ }
9228
+
9229
+ CommunicationException::handle(new ProtocolException(
9230
+ $connection, "Value '$payload' is not a valid length for a bulk response."
9231
+ ));
9232
+
9233
+ return;
9234
+ }
9235
+ }
9236
+
9237
+ /* --------------------------------------------------------------------------- */
9238
+
9239
+ namespace Predis\Collection\Iterator;
9240
+
9241
+ use Iterator;
9242
+ use Predis\ClientInterface;
9243
+ use Predis\NotSupportedException;
9244
+ use InvalidArgumentException;
9245
+
9246
+ /**
9247
+ * Provides the base implementation for a fully-rewindable PHP iterator that can
9248
+ * incrementally iterate over cursor-based collections stored on Redis using the
9249
+ * commands in the `SCAN` family.
9250
+ *
9251
+ * Given their incremental nature with multiple fetches, these kind of iterators
9252
+ * offer limited guarantees about the returned elements because the collection
9253
+ * can change several times during the iteration process.
9254
+ *
9255
+ * @see http://redis.io/commands/scan
9256
+ *
9257
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9258
+ */
9259
+ abstract class CursorBasedIterator implements Iterator
9260
+ {
9261
+ protected $client;
9262
+ protected $match;
9263
+ protected $count;
9264
+
9265
+ protected $valid;
9266
+ protected $fetchmore;
9267
+ protected $elements;
9268
+ protected $cursor;
9269
+ protected $position;
9270
+ protected $current;
9271
+
9272
+ /**
9273
+ * @param ClientInterface $client Client connected to Redis.
9274
+ * @param string $match Pattern to match during the server-side iteration.
9275
+ * @param int $count Hint used by Redis to compute the number of results per iteration.
9276
+ */
9277
+ public function __construct(ClientInterface $client, $match = null, $count = null)
9278
+ {
9279
+ $this->client = $client;
9280
+ $this->match = $match;
9281
+ $this->count = $count;
9282
+
9283
+ $this->reset();
9284
+ }
9285
+
9286
+ /**
9287
+ * Ensures that the client supports the specified Redis command required to
9288
+ * fetch elements from the server to perform the iteration.
9289
+ *
9290
+ * @param ClientInterface $client Client connected to Redis.
9291
+ * @param string $commandID Command ID.
9292
+ *
9293
+ * @throws NotSupportedException
9294
+ */
9295
+ protected function requiredCommand(ClientInterface $client, $commandID)
9296
+ {
9297
+ if (!$client->getProfile()->supportsCommand($commandID)) {
9298
+ throw new NotSupportedException("The current profile does not support '$commandID'.");
9299
+ }
9300
+ }
9301
+
9302
+ /**
9303
+ * Resets the inner state of the iterator.
9304
+ */
9305
+ protected function reset()
9306
+ {
9307
+ $this->valid = true;
9308
+ $this->fetchmore = true;
9309
+ $this->elements = array();
9310
+ $this->cursor = 0;
9311
+ $this->position = -1;
9312
+ $this->current = null;
9313
+ }
9314
+
9315
+ /**
9316
+ * Returns an array of options for the `SCAN` command.
9317
+ *
9318
+ * @return array
9319
+ */
9320
+ protected function getScanOptions()
9321
+ {
9322
+ $options = array();
9323
+
9324
+ if (strlen($this->match) > 0) {
9325
+ $options['MATCH'] = $this->match;
9326
+ }
9327
+
9328
+ if ($this->count > 0) {
9329
+ $options['COUNT'] = $this->count;
9330
+ }
9331
+
9332
+ return $options;
9333
+ }
9334
+
9335
+ /**
9336
+ * Fetches a new set of elements from the remote collection, effectively
9337
+ * advancing the iteration process.
9338
+ *
9339
+ * @return array
9340
+ */
9341
+ abstract protected function executeCommand();
9342
+
9343
+ /**
9344
+ * Populates the local buffer of elements fetched from the server during
9345
+ * the iteration.
9346
+ */
9347
+ protected function fetch()
9348
+ {
9349
+ list($cursor, $elements) = $this->executeCommand();
9350
+
9351
+ if (!$cursor) {
9352
+ $this->fetchmore = false;
9353
+ }
9354
+
9355
+ $this->cursor = $cursor;
9356
+ $this->elements = $elements;
9357
+ }
9358
+
9359
+ /**
9360
+ * Extracts next values for key() and current().
9361
+ */
9362
+ protected function extractNext()
9363
+ {
9364
+ $this->position++;
9365
+ $this->current = array_shift($this->elements);
9366
+ }
9367
+
9368
+ /**
9369
+ * {@inheritdoc}
9370
+ */
9371
+ public function rewind()
9372
+ {
9373
+ $this->reset();
9374
+ $this->next();
9375
+ }
9376
+
9377
+ /**
9378
+ * {@inheritdoc}
9379
+ */
9380
+ public function current()
9381
+ {
9382
+ return $this->current;
9383
+ }
9384
+
9385
+ /**
9386
+ * {@inheritdoc}
9387
+ */
9388
+ public function key()
9389
+ {
9390
+ return $this->position;
9391
+ }
9392
+
9393
+ /**
9394
+ * {@inheritdoc}
9395
+ */
9396
+ public function next()
9397
+ {
9398
+ tryFetch: {
9399
+ if (!$this->elements && $this->fetchmore) {
9400
+ $this->fetch();
9401
+ }
9402
+
9403
+ if ($this->elements) {
9404
+ $this->extractNext();
9405
+ } elseif ($this->cursor) {
9406
+ goto tryFetch;
9407
+ } else {
9408
+ $this->valid = false;
9409
+ }
9410
+ }
9411
+ }
9412
+
9413
+ /**
9414
+ * {@inheritdoc}
9415
+ */
9416
+ public function valid()
9417
+ {
9418
+ return $this->valid;
9419
+ }
9420
+ }
9421
+
9422
+ /**
9423
+ * Abstracts the iteration of members stored in a sorted set by leveraging the
9424
+ * ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9425
+ *
9426
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9427
+ * @link http://redis.io/commands/scan
9428
+ */
9429
+ class SortedSetKey extends CursorBasedIterator
9430
+ {
9431
+ protected $key;
9432
+
9433
+ /**
9434
+ * {@inheritdoc}
9435
+ */
9436
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9437
+ {
9438
+ $this->requiredCommand($client, 'ZSCAN');
9439
+
9440
+ parent::__construct($client, $match, $count);
9441
+
9442
+ $this->key = $key;
9443
+ }
9444
+
9445
+ /**
9446
+ * {@inheritdoc}
9447
+ */
9448
+ protected function executeCommand()
9449
+ {
9450
+ return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
9451
+ }
9452
+
9453
+ /**
9454
+ * {@inheritdoc}
9455
+ */
9456
+ protected function extractNext()
9457
+ {
9458
+ $this->position = key($this->elements);
9459
+ $this->current = array_shift($this->elements);
9460
+ }
9461
+ }
9462
+
9463
+ /**
9464
+ * Abstracts the iteration of members stored in a set by leveraging the SSCAN
9465
+ * command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9466
+ *
9467
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9468
+ * @link http://redis.io/commands/scan
9469
+ */
9470
+ class SetKey extends CursorBasedIterator
9471
+ {
9472
+ protected $key;
9473
+
9474
+ /**
9475
+ * {@inheritdoc}
9476
+ */
9477
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9478
+ {
9479
+ $this->requiredCommand($client, 'SSCAN');
9480
+
9481
+ parent::__construct($client, $match, $count);
9482
+
9483
+ $this->key = $key;
9484
+ }
9485
+
9486
+ /**
9487
+ * {@inheritdoc}
9488
+ */
9489
+ protected function executeCommand()
9490
+ {
9491
+ return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
9492
+ }
9493
+ }
9494
+
9495
+ /**
9496
+ * Abstracts the iteration of the keyspace on a Redis instance by leveraging the
9497
+ * SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9498
+ *
9499
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9500
+ * @link http://redis.io/commands/scan
9501
+ */
9502
+ class Keyspace extends CursorBasedIterator
9503
+ {
9504
+ /**
9505
+ * {@inheritdoc}
9506
+ */
9507
+ public function __construct(ClientInterface $client, $match = null, $count = null)
9508
+ {
9509
+ $this->requiredCommand($client, 'SCAN');
9510
+
9511
+ parent::__construct($client, $match, $count);
9512
+ }
9513
+
9514
+ /**
9515
+ * {@inheritdoc}
9516
+ */
9517
+ protected function executeCommand()
9518
+ {
9519
+ return $this->client->scan($this->cursor, $this->getScanOptions());
9520
+ }
9521
+ }
9522
+
9523
+ /**
9524
+ * Abstracts the iteration of fields and values of an hash by leveraging the
9525
+ * HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9526
+ *
9527
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9528
+ * @link http://redis.io/commands/scan
9529
+ */
9530
+ class HashKey extends CursorBasedIterator
9531
+ {
9532
+ protected $key;
9533
+
9534
+ /**
9535
+ * {@inheritdoc}
9536
+ */
9537
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9538
+ {
9539
+ $this->requiredCommand($client, 'HSCAN');
9540
+
9541
+ parent::__construct($client, $match, $count);
9542
+
9543
+ $this->key = $key;
9544
+ }
9545
+
9546
+ /**
9547
+ * {@inheritdoc}
9548
+ */
9549
+ protected function executeCommand()
9550
+ {
9551
+ return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
9552
+ }
9553
+
9554
+ /**
9555
+ * {@inheritdoc}
9556
+ */
9557
+ protected function extractNext()
9558
+ {
9559
+ $this->position = key($this->elements);
9560
+ $this->current = array_shift($this->elements);
9561
+ }
9562
+ }
9563
+
9564
+ /**
9565
+ * Abstracts the iteration of items stored in a list by leveraging the LRANGE
9566
+ * command wrapped in a fully-rewindable PHP iterator.
9567
+ *
9568
+ * This iterator tries to emulate the behaviour of cursor-based iterators based
9569
+ * on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
9570
+ * to its incremental nature with multiple fetches it can only offer limited
9571
+ * guarantees on the returned elements because the collection can change several
9572
+ * times (trimmed, deleted, overwritten) during the iteration process.
9573
+ *
9574
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9575
+ * @link http://redis.io/commands/lrange
9576
+ */
9577
+ class ListKey implements Iterator
9578
+ {
9579
+ protected $client;
9580
+ protected $count;
9581
+ protected $key;
9582
+
9583
+ protected $valid;
9584
+ protected $fetchmore;
9585
+ protected $elements;
9586
+ protected $position;
9587
+ protected $current;
9588
+
9589
+ /**
9590
+ * @param ClientInterface $client Client connected to Redis.
9591
+ * @param string $key Redis list key.
9592
+ * @param int $count Number of items retrieved on each fetch operation.
9593
+ */
9594
+ public function __construct(ClientInterface $client, $key, $count = 10)
9595
+ {
9596
+ $this->requiredCommand($client, 'LRANGE');
9597
+
9598
+ if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
9599
+ throw new InvalidArgumentException('The $count argument must be a positive integer.');
9600
+ }
9601
+
9602
+ $this->client = $client;
9603
+ $this->key = $key;
9604
+ $this->count = $count;
9605
+
9606
+ $this->reset();
9607
+ }
9608
+
9609
+ /**
9610
+ * Ensures that the client instance supports the specified Redis command
9611
+ * required to fetch elements from the server to perform the iteration.
9612
+ *
9613
+ * @param ClientInterface $client Client connected to Redis.
9614
+ * @param string $commandID Command ID.
9615
+ *
9616
+ * @throws NotSupportedException
9617
+ */
9618
+ protected function requiredCommand(ClientInterface $client, $commandID)
9619
+ {
9620
+ if (!$client->getProfile()->supportsCommand($commandID)) {
9621
+ throw new NotSupportedException("The current profile does not support '$commandID'.");
9622
+ }
9623
+ }
9624
+
9625
+ /**
9626
+ * Resets the inner state of the iterator.
9627
+ */
9628
+ protected function reset()
9629
+ {
9630
+ $this->valid = true;
9631
+ $this->fetchmore = true;
9632
+ $this->elements = array();
9633
+ $this->position = -1;
9634
+ $this->current = null;
9635
+ }
9636
+
9637
+ /**
9638
+ * Fetches a new set of elements from the remote collection, effectively
9639
+ * advancing the iteration process.
9640
+ *
9641
+ * @return array
9642
+ */
9643
+ protected function executeCommand()
9644
+ {
9645
+ return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
9646
+ }
9647
+
9648
+ /**
9649
+ * Populates the local buffer of elements fetched from the server during the
9650
+ * iteration.
9651
+ */
9652
+ protected function fetch()
9653
+ {
9654
+ $elements = $this->executeCommand();
9655
+
9656
+ if (count($elements) < $this->count) {
9657
+ $this->fetchmore = false;
9658
+ }
9659
+
9660
+ $this->elements = $elements;
9661
+ }
9662
+
9663
+ /**
9664
+ * Extracts next values for key() and current().
9665
+ */
9666
+ protected function extractNext()
9667
+ {
9668
+ $this->position++;
9669
+ $this->current = array_shift($this->elements);
9670
+ }
9671
+
9672
+ /**
9673
+ * {@inheritdoc}
9674
+ */
9675
+ public function rewind()
9676
+ {
9677
+ $this->reset();
9678
+ $this->next();
9679
+ }
9680
+
9681
+ /**
9682
+ * {@inheritdoc}
9683
+ */
9684
+ public function current()
9685
+ {
9686
+ return $this->current;
9687
+ }
9688
+
9689
+ /**
9690
+ * {@inheritdoc}
9691
+ */
9692
+ public function key()
9693
+ {
9694
+ return $this->position;
9695
+ }
9696
+
9697
+ /**
9698
+ * {@inheritdoc}
9699
+ */
9700
+ public function next()
9701
+ {
9702
+ if (!$this->elements && $this->fetchmore) {
9703
+ $this->fetch();
9704
+ }
9705
+
9706
+ if ($this->elements) {
9707
+ $this->extractNext();
9708
+ } else {
9709
+ $this->valid = false;
9710
+ }
9711
+ }
9712
+
9713
+ /**
9714
+ * {@inheritdoc}
9715
+ */
9716
+ public function valid()
9717
+ {
9718
+ return $this->valid;
9719
+ }
9720
+ }
9721
+
9722
+ /* --------------------------------------------------------------------------- */
9723
+
9724
+ namespace Predis\Cluster;
9725
+
9726
+ use InvalidArgumentException;
9727
+ use Predis\Command\CommandInterface;
9728
+ use Predis\Command\ScriptCommand;
9729
+ use Predis\Cluster\Distributor\DistributorInterface;
9730
+ use Predis\Cluster\Distributor\HashRing;
9731
+ use Predis\NotSupportedException;
9732
+ use Predis\Cluster\Hash\HashGeneratorInterface;
9733
+ use Predis\Cluster\Hash\CRC16;
9734
+
9735
+ /**
9736
+ * Interface for classes defining the strategy used to calculate an hash out of
9737
+ * keys extracted from supported commands.
9738
+ *
9739
+ * This is mostly useful to support clustering via client-side sharding.
9740
+ *
9741
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9742
+ */
9743
+ interface StrategyInterface
9744
+ {
9745
+ /**
9746
+ * Returns a slot for the given command used for clustering distribution or
9747
+ * NULL when this is not possible.
9748
+ *
9749
+ * @param CommandInterface $command Command instance.
9750
+ *
9751
+ * @return int
9752
+ */
9753
+ public function getSlot(CommandInterface $command);
9754
+
9755
+ /**
9756
+ * Returns a slot for the given key used for clustering distribution or NULL
9757
+ * when this is not possible.
9758
+ *
9759
+ * @param string $key Key string.
9760
+ *
9761
+ * @return int
9762
+ */
9763
+ public function getSlotByKey($key);
9764
+
9765
+ /**
9766
+ * Returns a distributor instance to be used by the cluster.
9767
+ *
9768
+ * @return DistributorInterface
9769
+ */
9770
+ public function getDistributor();
9771
+ }
9772
+
9773
+ /**
9774
+ * Common class implementing the logic needed to support clustering strategies.
9775
+ *
9776
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9777
+ */
9778
+ abstract class ClusterStrategy implements StrategyInterface
9779
+ {
9780
+ protected $commands;
9781
+
9782
+ /**
9783
+ *
9784
+ */
9785
+ public function __construct()
9786
+ {
9787
+ $this->commands = $this->getDefaultCommands();
9788
+ }
9789
+
9790
+ /**
9791
+ * Returns the default map of supported commands with their handlers.
9792
+ *
9793
+ * @return array
9794
+ */
9795
+ protected function getDefaultCommands()
9796
+ {
9797
+ $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
9798
+ $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
9799
+
9800
+ return array(
9801
+ /* commands operating on the key space */
9802
+ 'EXISTS' => $getKeyFromFirstArgument,
9803
+ 'DEL' => $getKeyFromAllArguments,
9804
+ 'TYPE' => $getKeyFromFirstArgument,
9805
+ 'EXPIRE' => $getKeyFromFirstArgument,
9806
+ 'EXPIREAT' => $getKeyFromFirstArgument,
9807
+ 'PERSIST' => $getKeyFromFirstArgument,
9808
+ 'PEXPIRE' => $getKeyFromFirstArgument,
9809
+ 'PEXPIREAT' => $getKeyFromFirstArgument,
9810
+ 'TTL' => $getKeyFromFirstArgument,
9811
+ 'PTTL' => $getKeyFromFirstArgument,
9812
+ 'SORT' => $getKeyFromFirstArgument, // TODO
9813
+ 'DUMP' => $getKeyFromFirstArgument,
9814
+ 'RESTORE' => $getKeyFromFirstArgument,
9815
+
9816
+ /* commands operating on string values */
9817
+ 'APPEND' => $getKeyFromFirstArgument,
9818
+ 'DECR' => $getKeyFromFirstArgument,
9819
+ 'DECRBY' => $getKeyFromFirstArgument,
9820
+ 'GET' => $getKeyFromFirstArgument,
9821
+ 'GETBIT' => $getKeyFromFirstArgument,
9822
+ 'MGET' => $getKeyFromAllArguments,
9823
+ 'SET' => $getKeyFromFirstArgument,
9824
+ 'GETRANGE' => $getKeyFromFirstArgument,
9825
+ 'GETSET' => $getKeyFromFirstArgument,
9826
+ 'INCR' => $getKeyFromFirstArgument,
9827
+ 'INCRBY' => $getKeyFromFirstArgument,
9828
+ 'INCRBYFLOAT' => $getKeyFromFirstArgument,
9829
+ 'SETBIT' => $getKeyFromFirstArgument,
9830
+ 'SETEX' => $getKeyFromFirstArgument,
9831
+ 'MSET' => array($this, 'getKeyFromInterleavedArguments'),
9832
+ 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
9833
+ 'SETNX' => $getKeyFromFirstArgument,
9834
+ 'SETRANGE' => $getKeyFromFirstArgument,
9835
+ 'STRLEN' => $getKeyFromFirstArgument,
9836
+ 'SUBSTR' => $getKeyFromFirstArgument,
9837
+ 'BITOP' => array($this, 'getKeyFromBitOp'),
9838
+ 'BITCOUNT' => $getKeyFromFirstArgument,
9839
+
9840
+ /* commands operating on lists */
9841
+ 'LINSERT' => $getKeyFromFirstArgument,
9842
+ 'LINDEX' => $getKeyFromFirstArgument,
9843
+ 'LLEN' => $getKeyFromFirstArgument,
9844
+ 'LPOP' => $getKeyFromFirstArgument,
9845
+ 'RPOP' => $getKeyFromFirstArgument,
9846
+ 'RPOPLPUSH' => $getKeyFromAllArguments,
9847
+ 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
9848
+ 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
9849
+ 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
9850
+ 'LPUSH' => $getKeyFromFirstArgument,
9851
+ 'LPUSHX' => $getKeyFromFirstArgument,
9852
+ 'RPUSH' => $getKeyFromFirstArgument,
9853
+ 'RPUSHX' => $getKeyFromFirstArgument,
9854
+ 'LRANGE' => $getKeyFromFirstArgument,
9855
+ 'LREM' => $getKeyFromFirstArgument,
9856
+ 'LSET' => $getKeyFromFirstArgument,
9857
+ 'LTRIM' => $getKeyFromFirstArgument,
9858
+
9859
+ /* commands operating on sets */
9860
+ 'SADD' => $getKeyFromFirstArgument,
9861
+ 'SCARD' => $getKeyFromFirstArgument,
9862
+ 'SDIFF' => $getKeyFromAllArguments,
9863
+ 'SDIFFSTORE' => $getKeyFromAllArguments,
9864
+ 'SINTER' => $getKeyFromAllArguments,
9865
+ 'SINTERSTORE' => $getKeyFromAllArguments,
9866
+ 'SUNION' => $getKeyFromAllArguments,
9867
+ 'SUNIONSTORE' => $getKeyFromAllArguments,
9868
+ 'SISMEMBER' => $getKeyFromFirstArgument,
9869
+ 'SMEMBERS' => $getKeyFromFirstArgument,
9870
+ 'SSCAN' => $getKeyFromFirstArgument,
9871
+ 'SPOP' => $getKeyFromFirstArgument,
9872
+ 'SRANDMEMBER' => $getKeyFromFirstArgument,
9873
+ 'SREM' => $getKeyFromFirstArgument,
9874
+
9875
+ /* commands operating on sorted sets */
9876
+ 'ZADD' => $getKeyFromFirstArgument,
9877
+ 'ZCARD' => $getKeyFromFirstArgument,
9878
+ 'ZCOUNT' => $getKeyFromFirstArgument,
9879
+ 'ZINCRBY' => $getKeyFromFirstArgument,
9880
+ 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
9881
+ 'ZRANGE' => $getKeyFromFirstArgument,
9882
+ 'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
9883
+ 'ZRANK' => $getKeyFromFirstArgument,
9884
+ 'ZREM' => $getKeyFromFirstArgument,
9885
+ 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
9886
+ 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
9887
+ 'ZREVRANGE' => $getKeyFromFirstArgument,
9888
+ 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
9889
+ 'ZREVRANK' => $getKeyFromFirstArgument,
9890
+ 'ZSCORE' => $getKeyFromFirstArgument,
9891
+ 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
9892
+ 'ZSCAN' => $getKeyFromFirstArgument,
9893
+ 'ZLEXCOUNT' => $getKeyFromFirstArgument,
9894
+ 'ZRANGEBYLEX' => $getKeyFromFirstArgument,
9895
+ 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
9896
+
9897
+ /* commands operating on hashes */
9898
+ 'HDEL' => $getKeyFromFirstArgument,
9899
+ 'HEXISTS' => $getKeyFromFirstArgument,
9900
+ 'HGET' => $getKeyFromFirstArgument,
9901
+ 'HGETALL' => $getKeyFromFirstArgument,
9902
+ 'HMGET' => $getKeyFromFirstArgument,
9903
+ 'HMSET' => $getKeyFromFirstArgument,
9904
+ 'HINCRBY' => $getKeyFromFirstArgument,
9905
+ 'HINCRBYFLOAT' => $getKeyFromFirstArgument,
9906
+ 'HKEYS' => $getKeyFromFirstArgument,
9907
+ 'HLEN' => $getKeyFromFirstArgument,
9908
+ 'HSET' => $getKeyFromFirstArgument,
9909
+ 'HSETNX' => $getKeyFromFirstArgument,
9910
+ 'HVALS' => $getKeyFromFirstArgument,
9911
+ 'HSCAN' => $getKeyFromFirstArgument,
9912
+
9913
+ /* commands operating on HyperLogLog */
9914
+ 'PFADD' => $getKeyFromFirstArgument,
9915
+ 'PFCOUNT' => $getKeyFromAllArguments,
9916
+ 'PFMERGE' => $getKeyFromAllArguments,
9917
+
9918
+ /* scripting */
9919
+ 'EVAL' => array($this, 'getKeyFromScriptingCommands'),
9920
+ 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
9921
+ );
9922
+ }
9923
+
9924
+ /**
9925
+ * Returns the list of IDs for the supported commands.
9926
+ *
9927
+ * @return array
9928
+ */
9929
+ public function getSupportedCommands()
9930
+ {
9931
+ return array_keys($this->commands);
9932
+ }
9933
+
9934
+ /**
9935
+ * Sets an handler for the specified command ID.
9936
+ *
9937
+ * The signature of the callback must have a single parameter of type
9938
+ * Predis\Command\CommandInterface.
9939
+ *
9940
+ * When the callback argument is omitted or NULL, the previously associated
9941
+ * handler for the specified command ID is removed.
9942
+ *
9943
+ * @param string $commandID Command ID.
9944
+ * @param mixed $callback A valid callable object, or NULL to unset the handler.
9945
+ */
9946
+ public function setCommandHandler($commandID, $callback = null)
9947
+ {
9948
+ $commandID = strtoupper($commandID);
9949
+
9950
+ if (!isset($callback)) {
9951
+ unset($this->commands[$commandID]);
9952
+
9953
+ return;
9954
+ }
9955
+
9956
+ if (!is_callable($callback)) {
9957
+ throw new InvalidArgumentException(
9958
+ "The argument must be a callable object or NULL."
9959
+ );
9960
+ }
9961
+
9962
+ $this->commands[$commandID] = $callback;
9963
+ }
9964
+
9965
+ /**
9966
+ * Extracts the key from the first argument of a command instance.
9967
+ *
9968
+ * @param CommandInterface $command Command instance.
9969
+ *
9970
+ * @return string
9971
+ */
9972
+ protected function getKeyFromFirstArgument(CommandInterface $command)
9973
+ {
9974
+ return $command->getArgument(0);
9975
+ }
9976
+
9977
+ /**
9978
+ * Extracts the key from a command with multiple keys only when all keys in
9979
+ * the arguments array produce the same hash.
9980
+ *
9981
+ * @param CommandInterface $command Command instance.
9982
+ *
9983
+ * @return string|null
9984
+ */
9985
+ protected function getKeyFromAllArguments(CommandInterface $command)
9986
+ {
9987
+ $arguments = $command->getArguments();
9988
+
9989
+ if ($this->checkSameSlotForKeys($arguments)) {
9990
+ return $arguments[0];
9991
+ }
9992
+ }
9993
+
9994
+ /**
9995
+ * Extracts the key from a command with multiple keys only when all keys in
9996
+ * the arguments array produce the same hash.
9997
+ *
9998
+ * @param CommandInterface $command Command instance.
9999
+ *
10000
+ * @return string|null
10001
+ */
10002
+ protected function getKeyFromInterleavedArguments(CommandInterface $command)
10003
+ {
10004
+ $arguments = $command->getArguments();
10005
+ $keys = array();
10006
+
10007
+ for ($i = 0; $i < count($arguments); $i += 2) {
10008
+ $keys[] = $arguments[$i];
10009
+ }
10010
+
10011
+ if ($this->checkSameSlotForKeys($keys)) {
10012
+ return $arguments[0];
10013
+ }
10014
+ }
10015
+
10016
+ /**
10017
+ * Extracts the key from BLPOP and BRPOP commands.
10018
+ *
10019
+ * @param CommandInterface $command Command instance.
10020
+ *
10021
+ * @return string|null
10022
+ */
10023
+ protected function getKeyFromBlockingListCommands(CommandInterface $command)
10024
+ {
10025
+ $arguments = $command->getArguments();
10026
+
10027
+ if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
10028
+ return $arguments[0];
10029
+ }
10030
+ }
10031
+
10032
+ /**
10033
+ * Extracts the key from BITOP command.
10034
+ *
10035
+ * @param CommandInterface $command Command instance.
10036
+ *
10037
+ * @return string|null
10038
+ */
10039
+ protected function getKeyFromBitOp(CommandInterface $command)
10040
+ {
10041
+ $arguments = $command->getArguments();
10042
+
10043
+ if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) {
10044
+ return $arguments[1];
10045
+ }
10046
+ }
10047
+
10048
+ /**
10049
+ * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
10050
+ *
10051
+ * @param CommandInterface $command Command instance.
10052
+ *
10053
+ * @return string|null
10054
+ */
10055
+ protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
10056
+ {
10057
+ $arguments = $command->getArguments();
10058
+ $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
10059
+
10060
+ if ($this->checkSameSlotForKeys($keys)) {
10061
+ return $arguments[0];
10062
+ }
10063
+ }
10064
+
10065
+ /**
10066
+ * Extracts the key from EVAL and EVALSHA commands.
10067
+ *
10068
+ * @param CommandInterface $command Command instance.
10069
+ *
10070
+ * @return string|null
10071
+ */
10072
+ protected function getKeyFromScriptingCommands(CommandInterface $command)
10073
+ {
10074
+ if ($command instanceof ScriptCommand) {
10075
+ $keys = $command->getKeys();
10076
+ } else {
10077
+ $keys = array_slice($args = $command->getArguments(), 2, $args[1]);
10078
+ }
10079
+
10080
+ if ($keys && $this->checkSameSlotForKeys($keys)) {
10081
+ return $keys[0];
10082
+ }
10083
+ }
10084
+
10085
+ /**
10086
+ * {@inheritdoc}
10087
+ */
10088
+ public function getSlot(CommandInterface $command)
10089
+ {
10090
+ $slot = $command->getSlot();
10091
+
10092
+ if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) {
10093
+ $key = call_user_func($this->commands[$cmdID], $command);
10094
+
10095
+ if (isset($key)) {
10096
+ $slot = $this->getSlotByKey($key);
10097
+ $command->setSlot($slot);
10098
+ }
10099
+ }
10100
+
10101
+ return $slot;
10102
+ }
10103
+
10104
+ /**
10105
+ * {@inheritdoc}
10106
+ */
10107
+ abstract public function getSlotByKey($key);
10108
+
10109
+ /**
10110
+ * Checks if the specified array of keys will generate the same hash.
10111
+ *
10112
+ * @param array $keys Array of keys.
10113
+ *
10114
+ * @return bool
10115
+ */
10116
+ protected function checkSameSlotForKeys(array $keys)
10117
+ {
10118
+ if (!$count = count($keys)) {
10119
+ return false;
10120
+ }
10121
+
10122
+ $currentSlot = $this->getSlotByKey($keys[0]);
10123
+
10124
+ for ($i = 1; $i < $count; $i++) {
10125
+ $nextSlot = $this->getSlotByKey($keys[$i]);
10126
+
10127
+ if ($currentSlot !== $nextSlot) {
10128
+ return false;
10129
+ }
10130
+
10131
+ $currentSlot = $nextSlot;
10132
+ }
10133
+
10134
+ return true;
10135
+ }
10136
+
10137
+ /**
10138
+ * Returns only the hashable part of a key (delimited by "{...}"), or the
10139
+ * whole key if a key tag is not found in the string.
10140
+ *
10141
+ * @param string $key A key.
10142
+ *
10143
+ * @return string
10144
+ */
10145
+ protected function extractKeyTag($key)
10146
+ {
10147
+ if (false !== $start = strpos($key, '{')) {
10148
+ if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
10149
+ $key = substr($key, $start, $end - $start);
10150
+ }
10151
+ }
10152
+
10153
+ return $key;
10154
+ }
10155
+
10156
+ /**
10157
+ * {@inheritdoc}
10158
+ */
10159
+ abstract public function getDistributor();
10160
+ }
10161
+
10162
+ /**
10163
+ * Default cluster strategy used by Predis to handle client-side sharding.
10164
+ *
10165
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10166
+ */
10167
+ class PredisStrategy extends ClusterStrategy
10168
+ {
10169
+ protected $distributor;
10170
+
10171
+ /**
10172
+ * @param DistributorInterface $distributor Optional distributor instance.
10173
+ */
10174
+ public function __construct(DistributorInterface $distributor = null)
10175
+ {
10176
+ parent::__construct();
10177
+
10178
+ $this->distributor = $distributor ?: new HashRing();
10179
+ }
10180
+
10181
+ /**
10182
+ * {@inheritdoc}
10183
+ */
10184
+ public function getSlotByKey($key)
10185
+ {
10186
+ $key = $this->extractKeyTag($key);
10187
+ $hash = $this->distributor->hash($key);
10188
+ $slot = $this->distributor->getSlot($hash);
10189
+
10190
+ return $slot;
10191
+ }
10192
+
10193
+ /**
10194
+ * {@inheritdoc}
10195
+ */
10196
+ protected function checkSameSlotForKeys(array $keys)
10197
+ {
10198
+ if (!$count = count($keys)) {
10199
+ return false;
10200
+ }
10201
+
10202
+ $currentKey = $this->extractKeyTag($keys[0]);
10203
+
10204
+ for ($i = 1; $i < $count; $i++) {
10205
+ $nextKey = $this->extractKeyTag($keys[$i]);
10206
+
10207
+ if ($currentKey !== $nextKey) {
10208
+ return false;
10209
+ }
10210
+
10211
+ $currentKey = $nextKey;
10212
+ }
10213
+
10214
+ return true;
10215
+ }
10216
+
10217
+ /**
10218
+ * {@inheritdoc}
10219
+ */
10220
+ public function getDistributor()
10221
+ {
10222
+ return $this->distributor;
10223
+ }
10224
+ }
10225
+
10226
+ /**
10227
+ * Default class used by Predis to calculate hashes out of keys of
10228
+ * commands supported by redis-cluster.
10229
+ *
10230
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10231
+ */
10232
+ class RedisStrategy extends ClusterStrategy
10233
+ {
10234
+ protected $hashGenerator;
10235
+
10236
+ /**
10237
+ * @param HashGeneratorInterface $hashGenerator Hash generator instance.
10238
+ */
10239
+ public function __construct(HashGeneratorInterface $hashGenerator = null)
10240
+ {
10241
+ parent::__construct();
10242
+
10243
+ $this->hashGenerator = $hashGenerator ?: new CRC16();
10244
+ }
10245
+
10246
+ /**
10247
+ * {@inheritdoc}
10248
+ */
10249
+ public function getSlotByKey($key)
10250
+ {
10251
+ $key = $this->extractKeyTag($key);
10252
+ $slot = $this->hashGenerator->hash($key) & 0x3FFF;
10253
+
10254
+ return $slot;
10255
+ }
10256
+
10257
+ /**
10258
+ * {@inheritdoc}
10259
+ */
10260
+ public function getDistributor()
10261
+ {
10262
+ throw new NotSupportedException(
10263
+ 'This cluster strategy does not provide an external distributor'
10264
+ );
10265
+ }
10266
+ }
10267
+
10268
+ /* --------------------------------------------------------------------------- */
10269
+
10270
+ namespace Predis\Protocol;
10271
+
10272
+ use Predis\CommunicationException;
10273
+ use Predis\Command\CommandInterface;
10274
+ use Predis\Connection\CompositeConnectionInterface;
10275
+
10276
+ /**
10277
+ * Defines a pluggable protocol processor capable of serializing commands and
10278
+ * deserializing responses into PHP objects directly from a connection.
10279
+ *
10280
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10281
+ */
10282
+ interface ProtocolProcessorInterface
10283
+ {
10284
+ /**
10285
+ * Writes a request over a connection to Redis.
10286
+ *
10287
+ * @param CompositeConnectionInterface $connection Redis connection.
10288
+ * @param CommandInterface $command Command instance.
10289
+ */
10290
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command);
10291
+
10292
+ /**
10293
+ * Reads a response from a connection to Redis.
10294
+ *
10295
+ * @param CompositeConnectionInterface $connection Redis connection.
10296
+ *
10297
+ * @return mixed
10298
+ */
10299
+ public function read(CompositeConnectionInterface $connection);
10300
+ }
10301
+
10302
+ /**
10303
+ * Defines a pluggable reader capable of parsing responses returned by Redis and
10304
+ * deserializing them to PHP objects.
10305
+ *
10306
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10307
+ */
10308
+ interface ResponseReaderInterface
10309
+ {
10310
+ /**
10311
+ * Reads a response from a connection to Redis.
10312
+ *
10313
+ * @param CompositeConnectionInterface $connection Redis connection.
10314
+ *
10315
+ * @return mixed
10316
+ */
10317
+ public function read(CompositeConnectionInterface $connection);
10318
+ }
10319
+
10320
+ /**
10321
+ * Defines a pluggable serializer for Redis commands.
10322
+ *
10323
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10324
+ */
10325
+ interface RequestSerializerInterface
10326
+ {
10327
+ /**
10328
+ * Serializes a Redis command.
10329
+ *
10330
+ * @param CommandInterface $command Redis command.
10331
+ *
10332
+ * @return string
10333
+ */
10334
+ public function serialize(CommandInterface $command);
10335
+ }
10336
+
10337
+ /**
10338
+ * Exception used to indentify errors encountered while parsing the Redis wire
10339
+ * protocol.
10340
+ *
10341
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10342
+ */
10343
+ class ProtocolException extends CommunicationException
10344
+ {
10345
+ }
10346
+
10347
+ /* --------------------------------------------------------------------------- */
10348
+
10349
+ namespace Predis\Connection\Aggregate;
10350
+
10351
+ use Predis\Connection\AggregateConnectionInterface;
10352
+ use InvalidArgumentException;
10353
+ use RuntimeException;
10354
+ use Predis\Command\CommandInterface;
10355
+ use Predis\Connection\NodeConnectionInterface;
10356
+ use Predis\Replication\ReplicationStrategy;
10357
+ use ArrayIterator;
10358
+ use Countable;
10359
+ use IteratorAggregate;
10360
+ use Predis\NotSupportedException;
10361
+ use Predis\Cluster\PredisStrategy;
10362
+ use Predis\Cluster\StrategyInterface;
10363
+ use OutOfBoundsException;
10364
+ use Predis\Cluster\RedisStrategy as RedisClusterStrategy;
10365
+ use Predis\Command\RawCommand;
10366
+ use Predis\Connection\FactoryInterface;
10367
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
10368
+
10369
+ /**
10370
+ * Defines a cluster of Redis servers formed by aggregating multiple connection
10371
+ * instances to single Redis nodes.
10372
+ *
10373
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10374
+ */
10375
+ interface ClusterInterface extends AggregateConnectionInterface
10376
+ {
10377
+ }
10378
+
10379
+ /**
10380
+ * Defines a group of Redis nodes in a master / slave replication setup.
10381
+ *
10382
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10383
+ */
10384
+ interface ReplicationInterface extends AggregateConnectionInterface
10385
+ {
10386
+ /**
10387
+ * Switches the internal connection instance in use.
10388
+ *
10389
+ * @param string $connection Alias of a connection
10390
+ */
10391
+ public function switchTo($connection);
10392
+
10393
+ /**
10394
+ * Returns the connection instance currently in use by the aggregate
10395
+ * connection.
10396
+ *
10397
+ * @return NodeConnectionInterface
10398
+ */
10399
+ public function getCurrent();
10400
+
10401
+ /**
10402
+ * Returns the connection instance for the master Redis node.
10403
+ *
10404
+ * @return NodeConnectionInterface
10405
+ */
10406
+ public function getMaster();
10407
+
10408
+ /**
10409
+ * Returns a list of connection instances to slave nodes.
10410
+ *
10411
+ * @return NodeConnectionInterface
10412
+ */
10413
+ public function getSlaves();
10414
+ }
10415
+
10416
+ /**
10417
+ * Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
10418
+ *
10419
+ * This connection backend offers smart support for redis-cluster by handling
10420
+ * automatic slots map (re)generation upon -MOVED or -ASK responses returned by
10421
+ * Redis when redirecting a client to a different node.
10422
+ *
10423
+ * The cluster can be pre-initialized using only a subset of the actual nodes in
10424
+ * the cluster, Predis will do the rest by adjusting the slots map and creating
10425
+ * the missing underlying connection instances on the fly.
10426
+ *
10427
+ * It is possible to pre-associate connections to a slots range with the "slots"
10428
+ * parameter in the form "$first-$last". This can greatly reduce runtime node
10429
+ * guessing and redirections.
10430
+ *
10431
+ * It is also possible to ask for the full and updated slots map directly to one
10432
+ * of the nodes and optionally enable such a behaviour upon -MOVED redirections.
10433
+ * Asking for the cluster configuration to Redis is actually done by issuing a
10434
+ * CLUSTER SLOTS command to a random node in the pool.
10435
+ *
10436
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10437
+ */
10438
+ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable
10439
+ {
10440
+ private $useClusterSlots = true;
10441
+ private $defaultParameters = array();
10442
+ private $pool = array();
10443
+ private $slots = array();
10444
+ private $slotsMap;
10445
+ private $strategy;
10446
+ private $connections;
10447
+
10448
+ /**
10449
+ * @param FactoryInterface $connections Optional connection factory.
10450
+ * @param StrategyInterface $strategy Optional cluster strategy.
10451
+ */
10452
+ public function __construct(
10453
+ FactoryInterface $connections,
10454
+ StrategyInterface $strategy = null
10455
+ ) {
10456
+ $this->connections = $connections;
10457
+ $this->strategy = $strategy ?: new RedisClusterStrategy();
10458
+ }
10459
+
10460
+ /**
10461
+ * {@inheritdoc}
10462
+ */
10463
+ public function isConnected()
10464
+ {
10465
+ foreach ($this->pool as $connection) {
10466
+ if ($connection->isConnected()) {
10467
+ return true;
10468
+ }
10469
+ }
10470
+
10471
+ return false;
10472
+ }
10473
+
10474
+ /**
10475
+ * {@inheritdoc}
10476
+ */
10477
+ public function connect()
10478
+ {
10479
+ if ($connection = $this->getRandomConnection()) {
10480
+ $connection->connect();
10481
+ }
10482
+ }
10483
+
10484
+ /**
10485
+ * {@inheritdoc}
10486
+ */
10487
+ public function disconnect()
10488
+ {
10489
+ foreach ($this->pool as $connection) {
10490
+ $connection->disconnect();
10491
+ }
10492
+ }
10493
+
10494
+ /**
10495
+ * {@inheritdoc}
10496
+ */
10497
+ public function add(NodeConnectionInterface $connection)
10498
+ {
10499
+ $this->pool[(string) $connection] = $connection;
10500
+ unset($this->slotsMap);
10501
+ }
10502
+
10503
+ /**
10504
+ * {@inheritdoc}
10505
+ */
10506
+ public function remove(NodeConnectionInterface $connection)
10507
+ {
10508
+ if (false !== $id = array_search($connection, $this->pool, true)) {
10509
+ unset(
10510
+ $this->pool[$id],
10511
+ $this->slotsMap
10512
+ );
10513
+
10514
+ return true;
10515
+ }
10516
+
10517
+ return false;
10518
+ }
10519
+
10520
+ /**
10521
+ * Removes a connection instance by using its identifier.
10522
+ *
10523
+ * @param string $connectionID Connection identifier.
10524
+ *
10525
+ * @return bool True if the connection was in the pool.
10526
+ */
10527
+ public function removeById($connectionID)
10528
+ {
10529
+ if (isset($this->pool[$connectionID])) {
10530
+ unset(
10531
+ $this->pool[$connectionID],
10532
+ $this->slotsMap
10533
+ );
10534
+
10535
+ return true;
10536
+ }
10537
+
10538
+ return false;
10539
+ }
10540
+
10541
+ /**
10542
+ * Generates the current slots map by guessing the cluster configuration out
10543
+ * of the connection parameters of the connections in the pool.
10544
+ *
10545
+ * Generation is based on the same algorithm used by Redis to generate the
10546
+ * cluster, so it is most effective when all of the connections supplied on
10547
+ * initialization have the "slots" parameter properly set accordingly to the
10548
+ * current cluster configuration.
10549
+ */
10550
+ public function buildSlotsMap()
10551
+ {
10552
+ $this->slotsMap = array();
10553
+
10554
+ foreach ($this->pool as $connectionID => $connection) {
10555
+ $parameters = $connection->getParameters();
10556
+
10557
+ if (!isset($parameters->slots)) {
10558
+ continue;
10559
+ }
10560
+
10561
+ $slots = explode('-', $parameters->slots, 2);
10562
+ $this->setSlots($slots[0], $slots[1], $connectionID);
10563
+ }
10564
+ }
10565
+
10566
+ /**
10567
+ * Generates an updated slots map fetching the cluster configuration using
10568
+ * the CLUSTER SLOTS command against the specified node or a random one from
10569
+ * the pool.
10570
+ *
10571
+ * @param NodeConnectionInterface $connection Optional connection instance.
10572
+ *
10573
+ * @return array
10574
+ */
10575
+ public function askSlotsMap(NodeConnectionInterface $connection = null)
10576
+ {
10577
+ if (!$connection && !$connection = $this->getRandomConnection()) {
10578
+ return array();
10579
+ }
10580
+
10581
+ $command = RawCommand::create('CLUSTER', 'SLOTS');
10582
+ $response = $connection->executeCommand($command);
10583
+
10584
+ foreach ($response as $slots) {
10585
+ // We only support master servers for now, so we ignore subsequent
10586
+ // elements in the $slots array identifying slaves.
10587
+ list($start, $end, $master) = $slots;
10588
+
10589
+ if ($master[0] === '') {
10590
+ $this->setSlots($start, $end, (string) $connection);
10591
+ } else {
10592
+ $this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
10593
+ }
10594
+ }
10595
+
10596
+ return $this->slotsMap;
10597
+ }
10598
+
10599
+ /**
10600
+ * Returns the current slots map for the cluster.
10601
+ *
10602
+ * @return array
10603
+ */
10604
+ public function getSlotsMap()
10605
+ {
10606
+ if (!isset($this->slotsMap)) {
10607
+ $this->slotsMap = array();
10608
+ }
10609
+
10610
+ return $this->slotsMap;
10611
+ }
10612
+
10613
+ /**
10614
+ * Pre-associates a connection to a slots range to avoid runtime guessing.
10615
+ *
10616
+ * @param int $first Initial slot of the range.
10617
+ * @param int $last Last slot of the range.
10618
+ * @param NodeConnectionInterface|string $connection ID or connection instance.
10619
+ */
10620
+ public function setSlots($first, $last, $connection)
10621
+ {
10622
+ if ($first < 0x0000 || $first > 0x3FFF ||
10623
+ $last < 0x0000 || $last > 0x3FFF ||
10624
+ $last < $first
10625
+ ) {
10626
+ throw new OutOfBoundsException(
10627
+ "Invalid slot range for $connection: [$first-$last]."
10628
+ );
10629
+ }
10630
+
10631
+ $slots = array_fill($first, $last - $first + 1, (string) $connection);
10632
+ $this->slotsMap = $this->getSlotsMap() + $slots;
10633
+ }
10634
+
10635
+ /**
10636
+ * Guesses the correct node associated to a given slot using a precalculated
10637
+ * slots map, falling back to the same logic used by Redis to initialize a
10638
+ * cluster (best-effort).
10639
+ *
10640
+ * @param int $slot Slot index.
10641
+ *
10642
+ * @return string Connection ID.
10643
+ */
10644
+ protected function guessNode($slot)
10645
+ {
10646
+ if (!isset($this->slotsMap)) {
10647
+ $this->buildSlotsMap();
10648
+ }
10649
+
10650
+ if (isset($this->slotsMap[$slot])) {
10651
+ return $this->slotsMap[$slot];
10652
+ }
10653
+
10654
+ $count = count($this->pool);
10655
+ $index = min((int) ($slot / (int) (16384 / $count)), $count - 1);
10656
+ $nodes = array_keys($this->pool);
10657
+
10658
+ return $nodes[$index];
10659
+ }
10660
+
10661
+ /**
10662
+ * Creates a new connection instance from the given connection ID.
10663
+ *
10664
+ * @param string $connectionID Identifier for the connection.
10665
+ *
10666
+ * @return NodeConnectionInterface
10667
+ */
10668
+ protected function createConnection($connectionID)
10669
+ {
10670
+ $host = explode(':', $connectionID, 2);
10671
+
10672
+ $parameters = array_merge($this->defaultParameters, array(
10673
+ 'host' => $host[0],
10674
+ 'port' => $host[1],
10675
+ ));
10676
+
10677
+ $connection = $this->connections->create($parameters);
10678
+
10679
+ return $connection;
10680
+ }
10681
+
10682
+ /**
10683
+ * {@inheritdoc}
10684
+ */
10685
+ public function getConnection(CommandInterface $command)
10686
+ {
10687
+ $slot = $this->strategy->getSlot($command);
10688
+
10689
+ if (!isset($slot)) {
10690
+ throw new NotSupportedException(
10691
+ "Cannot use '{$command->getId()}' with redis-cluster."
10692
+ );
10693
+ }
10694
+
10695
+ if (isset($this->slots[$slot])) {
10696
+ return $this->slots[$slot];
10697
+ } else {
10698
+ return $this->getConnectionBySlot($slot);
10699
+ }
10700
+ }
10701
+
10702
+ /**
10703
+ * Returns the connection currently associated to a given slot.
10704
+ *
10705
+ * @param int $slot Slot index.
10706
+ *
10707
+ * @return NodeConnectionInterface
10708
+ */
10709
+ public function getConnectionBySlot($slot)
10710
+ {
10711
+ if ($slot < 0x0000 || $slot > 0x3FFF) {
10712
+ throw new OutOfBoundsException("Invalid slot [$slot].");
10713
+ }
10714
+
10715
+ if (isset($this->slots[$slot])) {
10716
+ return $this->slots[$slot];
10717
+ }
10718
+
10719
+ $connectionID = $this->guessNode($slot);
10720
+
10721
+ if (!$connection = $this->getConnectionById($connectionID)) {
10722
+ $connection = $this->createConnection($connectionID);
10723
+ $this->pool[$connectionID] = $connection;
10724
+ }
10725
+
10726
+ return $this->slots[$slot] = $connection;
10727
+ }
10728
+
10729
+ /**
10730
+ * {@inheritdoc}
10731
+ */
10732
+ public function getConnectionById($connectionID)
10733
+ {
10734
+ if (isset($this->pool[$connectionID])) {
10735
+ return $this->pool[$connectionID];
10736
+ }
10737
+ }
10738
+
10739
+ /**
10740
+ * Returns a random connection from the pool.
10741
+ *
10742
+ * @return NodeConnectionInterface|null
10743
+ */
10744
+ protected function getRandomConnection()
10745
+ {
10746
+ if ($this->pool) {
10747
+ return $this->pool[array_rand($this->pool)];
10748
+ }
10749
+ }
10750
+
10751
+ /**
10752
+ * Permanently associates the connection instance to a new slot.
10753
+ * The connection is added to the connections pool if not yet included.
10754
+ *
10755
+ * @param NodeConnectionInterface $connection Connection instance.
10756
+ * @param int $slot Target slot index.
10757
+ */
10758
+ protected function move(NodeConnectionInterface $connection, $slot)
10759
+ {
10760
+ $this->pool[(string) $connection] = $connection;
10761
+ $this->slots[(int) $slot] = $connection;
10762
+ }
10763
+
10764
+ /**
10765
+ * Handles -ERR responses returned by Redis.
10766
+ *
10767
+ * @param CommandInterface $command Command that generated the -ERR response.
10768
+ * @param ErrorResponseInterface $error Redis error response object.
10769
+ *
10770
+ * @return mixed
10771
+ */
10772
+ protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error)
10773
+ {
10774
+ $details = explode(' ', $error->getMessage(), 2);
10775
+
10776
+ switch ($details[0]) {
10777
+ case 'MOVED':
10778
+ return $this->onMovedResponse($command, $details[1]);
10779
+
10780
+ case 'ASK':
10781
+ return $this->onAskResponse($command, $details[1]);
10782
+
10783
+ default:
10784
+ return $error;
10785
+ }
10786
+ }
10787
+
10788
+ /**
10789
+ * Handles -MOVED responses by executing again the command against the node
10790
+ * indicated by the Redis response.
10791
+ *
10792
+ * @param CommandInterface $command Command that generated the -MOVED response.
10793
+ * @param string $details Parameters of the -MOVED response.
10794
+ *
10795
+ * @return mixed
10796
+ */
10797
+ protected function onMovedResponse(CommandInterface $command, $details)
10798
+ {
10799
+ list($slot, $connectionID) = explode(' ', $details, 2);
10800
+
10801
+ if (!$connection = $this->getConnectionById($connectionID)) {
10802
+ $connection = $this->createConnection($connectionID);
10803
+ }
10804
+
10805
+ if ($this->useClusterSlots) {
10806
+ $this->askSlotsMap($connection);
10807
+ }
10808
+
10809
+ $this->move($connection, $slot);
10810
+ $response = $this->executeCommand($command);
10811
+
10812
+ return $response;
10813
+ }
10814
+
10815
+ /**
10816
+ * Handles -ASK responses by executing again the command against the node
10817
+ * indicated by the Redis response.
10818
+ *
10819
+ * @param CommandInterface $command Command that generated the -ASK response.
10820
+ * @param string $details Parameters of the -ASK response.
10821
+ * @return mixed
10822
+ */
10823
+ protected function onAskResponse(CommandInterface $command, $details)
10824
+ {
10825
+ list($slot, $connectionID) = explode(' ', $details, 2);
10826
+
10827
+ if (!$connection = $this->getConnectionById($connectionID)) {
10828
+ $connection = $this->createConnection($connectionID);
10829
+ }
10830
+
10831
+ $connection->executeCommand(RawCommand::create('ASKING'));
10832
+ $response = $connection->executeCommand($command);
10833
+
10834
+ return $response;
10835
+ }
10836
+
10837
+ /**
10838
+ * {@inheritdoc}
10839
+ */
10840
+ public function writeRequest(CommandInterface $command)
10841
+ {
10842
+ $this->getConnection($command)->writeRequest($command);
10843
+ }
10844
+
10845
+ /**
10846
+ * {@inheritdoc}
10847
+ */
10848
+ public function readResponse(CommandInterface $command)
10849
+ {
10850
+ return $this->getConnection($command)->readResponse($command);
10851
+ }
10852
+
10853
+ /**
10854
+ * {@inheritdoc}
10855
+ */
10856
+ public function executeCommand(CommandInterface $command)
10857
+ {
10858
+ $connection = $this->getConnection($command);
10859
+ $response = $connection->executeCommand($command);
10860
+
10861
+ if ($response instanceof ErrorResponseInterface) {
10862
+ return $this->onErrorResponse($command, $response);
10863
+ }
10864
+
10865
+ return $response;
10866
+ }
10867
+
10868
+ /**
10869
+ * {@inheritdoc}
10870
+ */
10871
+ public function count()
10872
+ {
10873
+ return count($this->pool);
10874
+ }
10875
+
10876
+ /**
10877
+ * {@inheritdoc}
10878
+ */
10879
+ public function getIterator()
10880
+ {
10881
+ return new ArrayIterator(array_values($this->pool));
10882
+ }
10883
+
10884
+ /**
10885
+ * Returns the underlying command hash strategy used to hash commands by
10886
+ * using keys found in their arguments.
10887
+ *
10888
+ * @return StrategyInterface
10889
+ */
10890
+ public function getClusterStrategy()
10891
+ {
10892
+ return $this->strategy;
10893
+ }
10894
+
10895
+ /**
10896
+ * Returns the underlying connection factory used to create new connection
10897
+ * instances to Redis nodes indicated by redis-cluster.
10898
+ *
10899
+ * @return FactoryInterface
10900
+ */
10901
+ public function getConnectionFactory()
10902
+ {
10903
+ return $this->connections;
10904
+ }
10905
+
10906
+ /**
10907
+ * Enables automatic fetching of the current slots map from one of the nodes
10908
+ * using the CLUSTER SLOTS command. This option is disabled by default but
10909
+ * asking the current slots map to Redis upon -MOVED responses may reduce
10910
+ * overhead by eliminating the trial-and-error nature of the node guessing
10911
+ * procedure, mostly when targeting many keys that would end up in a lot of
10912
+ * redirections.
10913
+ *
10914
+ * The slots map can still be manually fetched using the askSlotsMap()
10915
+ * method whether or not this option is enabled.
10916
+ *
10917
+ * @param bool $value Enable or disable the use of CLUSTER SLOTS.
10918
+ */
10919
+ public function useClusterSlots($value)
10920
+ {
10921
+ $this->useClusterSlots = (bool) $value;
10922
+ }
10923
+
10924
+ /**
10925
+ * Sets a default array of connection parameters to be applied when creating
10926
+ * new connection instances on the fly when they are not part of the initial
10927
+ * pool supplied upon cluster initialization.
10928
+ *
10929
+ * These parameters are not applied to connections added to the pool using
10930
+ * the add() method.
10931
+ *
10932
+ * @param array $parameters Array of connection parameters.
10933
+ */
10934
+ public function setDefaultParameters(array $parameters)
10935
+ {
10936
+ $this->defaultParameters = array_merge(
10937
+ $this->defaultParameters,
10938
+ $parameters ?: array()
10939
+ );
10940
+ }
10941
+ }
10942
+
10943
+ /**
10944
+ * Abstraction for a cluster of aggregate connections to various Redis servers
10945
+ * implementing client-side sharding based on pluggable distribution strategies.
10946
+ *
10947
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10948
+ * @todo Add the ability to remove connections from pool.
10949
+ */
10950
+ class PredisCluster implements ClusterInterface, IteratorAggregate, Countable
10951
+ {
10952
+ private $pool;
10953
+ private $strategy;
10954
+ private $distributor;
10955
+
10956
+ /**
10957
+ * @param StrategyInterface $strategy Optional cluster strategy.
10958
+ */
10959
+ public function __construct(StrategyInterface $strategy = null)
10960
+ {
10961
+ $this->pool = array();
10962
+ $this->strategy = $strategy ?: new PredisStrategy();
10963
+ $this->distributor = $this->strategy->getDistributor();
10964
+ }
10965
+
10966
+ /**
10967
+ * {@inheritdoc}
10968
+ */
10969
+ public function isConnected()
10970
+ {
10971
+ foreach ($this->pool as $connection) {
10972
+ if ($connection->isConnected()) {
10973
+ return true;
10974
+ }
10975
+ }
10976
+
10977
+ return false;
10978
+ }
10979
+
10980
+ /**
10981
+ * {@inheritdoc}
10982
+ */
10983
+ public function connect()
10984
+ {
10985
+ foreach ($this->pool as $connection) {
10986
+ $connection->connect();
10987
+ }
10988
+ }
10989
+
10990
+ /**
10991
+ * {@inheritdoc}
10992
+ */
10993
+ public function disconnect()
10994
+ {
10995
+ foreach ($this->pool as $connection) {
10996
+ $connection->disconnect();
10997
+ }
10998
+ }
10999
+
11000
+ /**
11001
+ * {@inheritdoc}
11002
+ */
11003
+ public function add(NodeConnectionInterface $connection)
11004
+ {
11005
+ $parameters = $connection->getParameters();
11006
+
11007
+ if (isset($parameters->alias)) {
11008
+ $this->pool[$parameters->alias] = $connection;
11009
+ } else {
11010
+ $this->pool[] = $connection;
11011
+ }
11012
+
11013
+ $weight = isset($parameters->weight) ? $parameters->weight : null;
11014
+ $this->distributor->add($connection, $weight);
11015
+ }
11016
+
11017
+ /**
11018
+ * {@inheritdoc}
11019
+ */
11020
+ public function remove(NodeConnectionInterface $connection)
11021
+ {
11022
+ if (($id = array_search($connection, $this->pool, true)) !== false) {
11023
+ unset($this->pool[$id]);
11024
+ $this->distributor->remove($connection);
11025
+
11026
+ return true;
11027
+ }
11028
+
11029
+ return false;
11030
+ }
11031
+
11032
+ /**
11033
+ * Removes a connection instance using its alias or index.
11034
+ *
11035
+ * @param string $connectionID Alias or index of a connection.
11036
+ *
11037
+ * @return bool Returns true if the connection was in the pool.
11038
+ */
11039
+ public function removeById($connectionID)
11040
+ {
11041
+ if ($connection = $this->getConnectionById($connectionID)) {
11042
+ return $this->remove($connection);
11043
+ }
11044
+
11045
+ return false;
11046
+ }
11047
+
11048
+ /**
11049
+ * {@inheritdoc}
11050
+ */
11051
+ public function getConnection(CommandInterface $command)
11052
+ {
11053
+ $slot = $this->strategy->getSlot($command);
11054
+
11055
+ if (!isset($slot)) {
11056
+ throw new NotSupportedException(
11057
+ "Cannot use '{$command->getId()}' over clusters of connections."
11058
+ );
11059
+ }
11060
+
11061
+ $node = $this->distributor->getBySlot($slot);
11062
+
11063
+ return $node;
11064
+ }
11065
+
11066
+ /**
11067
+ * {@inheritdoc}
11068
+ */
11069
+ public function getConnectionById($connectionID)
11070
+ {
11071
+ return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null;
11072
+ }
11073
+
11074
+ /**
11075
+ * Retrieves a connection instance from the cluster using a key.
11076
+ *
11077
+ * @param string $key Key string.
11078
+ *
11079
+ * @return NodeConnectionInterface
11080
+ */
11081
+ public function getConnectionByKey($key)
11082
+ {
11083
+ $hash = $this->strategy->getSlotByKey($key);
11084
+ $node = $this->distributor->getBySlot($hash);
11085
+
11086
+ return $node;
11087
+ }
11088
+
11089
+ /**
11090
+ * Returns the underlying command hash strategy used to hash commands by
11091
+ * using keys found in their arguments.
11092
+ *
11093
+ * @return StrategyInterface
11094
+ */
11095
+ public function getClusterStrategy()
11096
+ {
11097
+ return $this->strategy;
11098
+ }
11099
+
11100
+ /**
11101
+ * {@inheritdoc}
11102
+ */
11103
+ public function count()
11104
+ {
11105
+ return count($this->pool);
11106
+ }
11107
+
11108
+ /**
11109
+ * {@inheritdoc}
11110
+ */
11111
+ public function getIterator()
11112
+ {
11113
+ return new ArrayIterator($this->pool);
11114
+ }
11115
+
11116
+ /**
11117
+ * {@inheritdoc}
11118
+ */
11119
+ public function writeRequest(CommandInterface $command)
11120
+ {
11121
+ $this->getConnection($command)->writeRequest($command);
11122
+ }
11123
+
11124
+ /**
11125
+ * {@inheritdoc}
11126
+ */
11127
+ public function readResponse(CommandInterface $command)
11128
+ {
11129
+ return $this->getConnection($command)->readResponse($command);
11130
+ }
11131
+
11132
+ /**
11133
+ * {@inheritdoc}
11134
+ */
11135
+ public function executeCommand(CommandInterface $command)
11136
+ {
11137
+ return $this->getConnection($command)->executeCommand($command);
11138
+ }
11139
+
11140
+ /**
11141
+ * Executes the specified Redis command on all the nodes of a cluster.
11142
+ *
11143
+ * @param CommandInterface $command A Redis command.
11144
+ *
11145
+ * @return array
11146
+ */
11147
+ public function executeCommandOnNodes(CommandInterface $command)
11148
+ {
11149
+ $responses = array();
11150
+
11151
+ foreach ($this->pool as $connection) {
11152
+ $responses[] = $connection->executeCommand($command);
11153
+ }
11154
+
11155
+ return $responses;
11156
+ }
11157
+ }
11158
+
11159
+ /**
11160
+ * Aggregate connection handling replication of Redis nodes configured in a
11161
+ * single master / multiple slaves setup.
11162
+ *
11163
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11164
+ */
11165
+ class MasterSlaveReplication implements ReplicationInterface
11166
+ {
11167
+ protected $strategy;
11168
+ protected $master;
11169
+ protected $slaves;
11170
+ protected $current;
11171
+
11172
+ /**
11173
+ * {@inheritdoc}
11174
+ */
11175
+ public function __construct(ReplicationStrategy $strategy = null)
11176
+ {
11177
+ $this->slaves = array();
11178
+ $this->strategy = $strategy ?: new ReplicationStrategy();
11179
+ }
11180
+
11181
+ /**
11182
+ * Checks if one master and at least one slave have been defined.
11183
+ */
11184
+ protected function check()
11185
+ {
11186
+ if (!isset($this->master) || !$this->slaves) {
11187
+ throw new RuntimeException('Replication needs one master and at least one slave.');
11188
+ }
11189
+ }
11190
+
11191
+ /**
11192
+ * Resets the connection state.
11193
+ */
11194
+ protected function reset()
11195
+ {
11196
+ $this->current = null;
11197
+ }
11198
+
11199
+ /**
11200
+ * {@inheritdoc}
11201
+ */
11202
+ public function add(NodeConnectionInterface $connection)
11203
+ {
11204
+ $alias = $connection->getParameters()->alias;
11205
+
11206
+ if ($alias === 'master') {
11207
+ $this->master = $connection;
11208
+ } else {
11209
+ $this->slaves[$alias ?: count($this->slaves)] = $connection;
11210
+ }
11211
+
11212
+ $this->reset();
11213
+ }
11214
+
11215
+ /**
11216
+ * {@inheritdoc}
11217
+ */
11218
+ public function remove(NodeConnectionInterface $connection)
11219
+ {
11220
+ if ($connection->getParameters()->alias === 'master') {
11221
+ $this->master = null;
11222
+ $this->reset();
11223
+
11224
+ return true;
11225
+ } else {
11226
+ if (($id = array_search($connection, $this->slaves, true)) !== false) {
11227
+ unset($this->slaves[$id]);
11228
+ $this->reset();
11229
+
11230
+ return true;
11231
+ }
11232
+ }
11233
+
11234
+ return false;
11235
+ }
11236
+
11237
+ /**
11238
+ * {@inheritdoc}
11239
+ */
11240
+ public function getConnection(CommandInterface $command)
11241
+ {
11242
+ if ($this->current === null) {
11243
+ $this->check();
11244
+ $this->current = $this->strategy->isReadOperation($command)
11245
+ ? $this->pickSlave()
11246
+ : $this->master;
11247
+
11248
+ return $this->current;
11249
+ }
11250
+
11251
+ if ($this->current === $this->master) {
11252
+ return $this->current;
11253
+ }
11254
+
11255
+ if (!$this->strategy->isReadOperation($command)) {
11256
+ $this->current = $this->master;
11257
+ }
11258
+
11259
+ return $this->current;
11260
+ }
11261
+
11262
+ /**
11263
+ * {@inheritdoc}
11264
+ */
11265
+ public function getConnectionById($connectionId)
11266
+ {
11267
+ if ($connectionId === 'master') {
11268
+ return $this->master;
11269
+ }
11270
+
11271
+ if (isset($this->slaves[$connectionId])) {
11272
+ return $this->slaves[$connectionId];
11273
+ }
11274
+
11275
+ return null;
11276
+ }
11277
+
11278
+ /**
11279
+ * {@inheritdoc}
11280
+ */
11281
+ public function switchTo($connection)
11282
+ {
11283
+ $this->check();
11284
+
11285
+ if (!$connection instanceof NodeConnectionInterface) {
11286
+ $connection = $this->getConnectionById($connection);
11287
+ }
11288
+ if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
11289
+ throw new InvalidArgumentException('Invalid connection or connection not found.');
11290
+ }
11291
+
11292
+ $this->current = $connection;
11293
+ }
11294
+
11295
+ /**
11296
+ * {@inheritdoc}
11297
+ */
11298
+ public function getCurrent()
11299
+ {
11300
+ return $this->current;
11301
+ }
11302
+
11303
+ /**
11304
+ * {@inheritdoc}
11305
+ */
11306
+ public function getMaster()
11307
+ {
11308
+ return $this->master;
11309
+ }
11310
+
11311
+ /**
11312
+ * {@inheritdoc}
11313
+ */
11314
+ public function getSlaves()
11315
+ {
11316
+ return array_values($this->slaves);
11317
+ }
11318
+
11319
+ /**
11320
+ * Returns the underlying replication strategy.
11321
+ *
11322
+ * @return ReplicationStrategy
11323
+ */
11324
+ public function getReplicationStrategy()
11325
+ {
11326
+ return $this->strategy;
11327
+ }
11328
+
11329
+ /**
11330
+ * Returns a random slave.
11331
+ *
11332
+ * @return NodeConnectionInterface
11333
+ */
11334
+ protected function pickSlave()
11335
+ {
11336
+ return $this->slaves[array_rand($this->slaves)];
11337
+ }
11338
+
11339
+ /**
11340
+ * {@inheritdoc}
11341
+ */
11342
+ public function isConnected()
11343
+ {
11344
+ return $this->current ? $this->current->isConnected() : false;
11345
+ }
11346
+
11347
+ /**
11348
+ * {@inheritdoc}
11349
+ */
11350
+ public function connect()
11351
+ {
11352
+ if ($this->current === null) {
11353
+ $this->check();
11354
+ $this->current = $this->pickSlave();
11355
+ }
11356
+
11357
+ $this->current->connect();
11358
+ }
11359
+
11360
+ /**
11361
+ * {@inheritdoc}
11362
+ */
11363
+ public function disconnect()
11364
+ {
11365
+ if ($this->master) {
11366
+ $this->master->disconnect();
11367
+ }
11368
+
11369
+ foreach ($this->slaves as $connection) {
11370
+ $connection->disconnect();
11371
+ }
11372
+ }
11373
+
11374
+ /**
11375
+ * {@inheritdoc}
11376
+ */
11377
+ public function writeRequest(CommandInterface $command)
11378
+ {
11379
+ $this->getConnection($command)->writeRequest($command);
11380
+ }
11381
+
11382
+ /**
11383
+ * {@inheritdoc}
11384
+ */
11385
+ public function readResponse(CommandInterface $command)
11386
+ {
11387
+ return $this->getConnection($command)->readResponse($command);
11388
+ }
11389
+
11390
+ /**
11391
+ * {@inheritdoc}
11392
+ */
11393
+ public function executeCommand(CommandInterface $command)
11394
+ {
11395
+ return $this->getConnection($command)->executeCommand($command);
11396
+ }
11397
+
11398
+ /**
11399
+ * {@inheritdoc}
11400
+ */
11401
+ public function __sleep()
11402
+ {
11403
+ return array('master', 'slaves', 'strategy');
11404
+ }
11405
+ }
11406
+
11407
+ /* --------------------------------------------------------------------------- */
11408
+
11409
+ namespace Predis\Pipeline;
11410
+
11411
+ use SplQueue;
11412
+ use Predis\ClientException;
11413
+ use Predis\ClientInterface;
11414
+ use Predis\Connection\ConnectionInterface;
11415
+ use Predis\Connection\NodeConnectionInterface;
11416
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
11417
+ use Predis\Response\ResponseInterface;
11418
+ use Predis\Response\ServerException;
11419
+ use Predis\NotSupportedException;
11420
+ use Predis\CommunicationException;
11421
+ use Predis\Connection\Aggregate\ClusterInterface;
11422
+ use Exception;
11423
+ use InvalidArgumentException;
11424
+ use Predis\ClientContextInterface;
11425
+ use Predis\Command\CommandInterface;
11426
+ use Predis\Connection\Aggregate\ReplicationInterface;
11427
+
11428
+ /**
11429
+ * Implementation of a command pipeline in which write and read operations of
11430
+ * Redis commands are pipelined to alleviate the effects of network round-trips.
11431
+ *
11432
+ * {@inheritdoc}
11433
+ *
11434
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11435
+ */
11436
+ class Pipeline implements ClientContextInterface
11437
+ {
11438
+ private $client;
11439
+ private $pipeline;
11440
+
11441
+ private $responses = array();
11442
+ private $running = false;
11443
+
11444
+ /**
11445
+ * @param ClientInterface $client Client instance used by the context.
11446
+ */
11447
+ public function __construct(ClientInterface $client)
11448
+ {
11449
+ $this->client = $client;
11450
+ $this->pipeline = new SplQueue();
11451
+ }
11452
+
11453
+ /**
11454
+ * Queues a command into the pipeline buffer.
11455
+ *
11456
+ * @param string $method Command ID.
11457
+ * @param array $arguments Arguments for the command.
11458
+ *
11459
+ * @return $this
11460
+ */
11461
+ public function __call($method, $arguments)
11462
+ {
11463
+ $command = $this->client->createCommand($method, $arguments);
11464
+ $this->recordCommand($command);
11465
+
11466
+ return $this;
11467
+ }
11468
+
11469
+ /**
11470
+ * Queues a command instance into the pipeline buffer.
11471
+ *
11472
+ * @param CommandInterface $command Command to be queued in the buffer.
11473
+ */
11474
+ protected function recordCommand(CommandInterface $command)
11475
+ {
11476
+ $this->pipeline->enqueue($command);
11477
+ }
11478
+
11479
+ /**
11480
+ * Queues a command instance into the pipeline buffer.
11481
+ *
11482
+ * @param CommandInterface $command Command instance to be queued in the buffer.
11483
+ *
11484
+ * @return $this
11485
+ */
11486
+ public function executeCommand(CommandInterface $command)
11487
+ {
11488
+ $this->recordCommand($command);
11489
+
11490
+ return $this;
11491
+ }
11492
+
11493
+ /**
11494
+ * Throws an exception on -ERR responses returned by Redis.
11495
+ *
11496
+ * @param ConnectionInterface $connection Redis connection that returned the error.
11497
+ * @param ErrorResponseInterface $response Instance of the error response.
11498
+ *
11499
+ * @throws ServerException
11500
+ */
11501
+ protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response)
11502
+ {
11503
+ $connection->disconnect();
11504
+ $message = $response->getMessage();
11505
+
11506
+ throw new ServerException($message);
11507
+ }
11508
+
11509
+ /**
11510
+ * Returns the underlying connection to be used by the pipeline.
11511
+ *
11512
+ * @return ConnectionInterface
11513
+ */
11514
+ protected function getConnection()
11515
+ {
11516
+ $connection = $this->getClient()->getConnection();
11517
+
11518
+ if ($connection instanceof ReplicationInterface) {
11519
+ $connection->switchTo('master');
11520
+ }
11521
+
11522
+ return $connection;
11523
+ }
11524
+
11525
+ /**
11526
+ * Implements the logic to flush the queued commands and read the responses
11527
+ * from the current connection.
11528
+ *
11529
+ * @param ConnectionInterface $connection Current connection instance.
11530
+ * @param SplQueue $commands Queued commands.
11531
+ *
11532
+ * @return array
11533
+ */
11534
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11535
+ {
11536
+ foreach ($commands as $command) {
11537
+ $connection->writeRequest($command);
11538
+ }
11539
+
11540
+ $responses = array();
11541
+ $exceptions = $this->throwServerExceptions();
11542
+
11543
+ while (!$commands->isEmpty()) {
11544
+ $command = $commands->dequeue();
11545
+ $response = $connection->readResponse($command);
11546
+
11547
+ if (!$response instanceof ResponseInterface) {
11548
+ $responses[] = $command->parseResponse($response);
11549
+ } elseif ($response instanceof ErrorResponseInterface && $exceptions) {
11550
+ $this->exception($connection, $response);
11551
+ } else {
11552
+ $responses[] = $response;
11553
+ }
11554
+ }
11555
+
11556
+ return $responses;
11557
+ }
11558
+
11559
+ /**
11560
+ * Flushes the buffer holding all of the commands queued so far.
11561
+ *
11562
+ * @param bool $send Specifies if the commands in the buffer should be sent to Redis.
11563
+ *
11564
+ * @return $this
11565
+ */
11566
+ public function flushPipeline($send = true)
11567
+ {
11568
+ if ($send && !$this->pipeline->isEmpty()) {
11569
+ $responses = $this->executePipeline($this->getConnection(), $this->pipeline);
11570
+ $this->responses = array_merge($this->responses, $responses);
11571
+ } else {
11572
+ $this->pipeline = new SplQueue();
11573
+ }
11574
+
11575
+ return $this;
11576
+ }
11577
+
11578
+ /**
11579
+ * Marks the running status of the pipeline.
11580
+ *
11581
+ * @param bool $bool Sets the running status of the pipeline.
11582
+ *
11583
+ * @throws ClientException
11584
+ */
11585
+ private function setRunning($bool)
11586
+ {
11587
+ if ($bool && $this->running) {
11588
+ throw new ClientException('The current pipeline context is already being executed.');
11589
+ }
11590
+
11591
+ $this->running = $bool;
11592
+ }
11593
+
11594
+ /**
11595
+ * Handles the actual execution of the whole pipeline.
11596
+ *
11597
+ * @param mixed $callable Optional callback for execution.
11598
+ *
11599
+ * @return array
11600
+ *
11601
+ * @throws Exception
11602
+ * @throws InvalidArgumentException
11603
+ */
11604
+ public function execute($callable = null)
11605
+ {
11606
+ if ($callable && !is_callable($callable)) {
11607
+ throw new InvalidArgumentException('The argument must be a callable object.');
11608
+ }
11609
+
11610
+ $exception = null;
11611
+ $this->setRunning(true);
11612
+
11613
+ try {
11614
+ if ($callable) {
11615
+ call_user_func($callable, $this);
11616
+ }
11617
+
11618
+ $this->flushPipeline();
11619
+ } catch (Exception $exception) {
11620
+ // NOOP
11621
+ }
11622
+
11623
+ $this->setRunning(false);
11624
+
11625
+ if ($exception) {
11626
+ throw $exception;
11627
+ }
11628
+
11629
+ return $this->responses;
11630
+ }
11631
+
11632
+ /**
11633
+ * Returns if the pipeline should throw exceptions on server errors.
11634
+ *
11635
+ * @return bool
11636
+ */
11637
+ protected function throwServerExceptions()
11638
+ {
11639
+ return (bool) $this->client->getOptions()->exceptions;
11640
+ }
11641
+
11642
+ /**
11643
+ * Returns the underlying client instance used by the pipeline object.
11644
+ *
11645
+ * @return ClientInterface
11646
+ */
11647
+ public function getClient()
11648
+ {
11649
+ return $this->client;
11650
+ }
11651
+ }
11652
+
11653
+ /**
11654
+ * Command pipeline that writes commands to the servers but discards responses.
11655
+ *
11656
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11657
+ */
11658
+ class FireAndForget extends Pipeline
11659
+ {
11660
+ /**
11661
+ * {@inheritdoc}
11662
+ */
11663
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11664
+ {
11665
+ while (!$commands->isEmpty()) {
11666
+ $connection->writeRequest($commands->dequeue());
11667
+ }
11668
+
11669
+ $connection->disconnect();
11670
+
11671
+ return array();
11672
+ }
11673
+ }
11674
+
11675
+ /**
11676
+ * Command pipeline that does not throw exceptions on connection errors, but
11677
+ * returns the exception instances as the rest of the response elements.
11678
+ *
11679
+ * @todo Awful naming!
11680
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11681
+ */
11682
+ class ConnectionErrorProof extends Pipeline
11683
+ {
11684
+ /**
11685
+ * {@inheritdoc}
11686
+ */
11687
+ protected function getConnection()
11688
+ {
11689
+ return $this->getClient()->getConnection();
11690
+ }
11691
+
11692
+ /**
11693
+ * {@inheritdoc}
11694
+ */
11695
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11696
+ {
11697
+ if ($connection instanceof NodeConnectionInterface) {
11698
+ return $this->executeSingleNode($connection, $commands);
11699
+ } elseif ($connection instanceof ClusterInterface) {
11700
+ return $this->executeCluster($connection, $commands);
11701
+ } else {
11702
+ $class = get_class($connection);
11703
+
11704
+ throw new NotSupportedException("The connection class '$class' is not supported.");
11705
+ }
11706
+ }
11707
+
11708
+ /**
11709
+ * {@inheritdoc}
11710
+ */
11711
+ protected function executeSingleNode(NodeConnectionInterface $connection, SplQueue $commands)
11712
+ {
11713
+ $responses = array();
11714
+ $sizeOfPipe = count($commands);
11715
+
11716
+ foreach ($commands as $command) {
11717
+ try {
11718
+ $connection->writeRequest($command);
11719
+ } catch (CommunicationException $exception) {
11720
+ return array_fill(0, $sizeOfPipe, $exception);
11721
+ }
11722
+ }
11723
+
11724
+ for ($i = 0; $i < $sizeOfPipe; $i++) {
11725
+ $command = $commands->dequeue();
11726
+
11727
+ try {
11728
+ $responses[$i] = $connection->readResponse($command);
11729
+ } catch (CommunicationException $exception) {
11730
+ $add = count($commands) - count($responses);
11731
+ $responses = array_merge($responses, array_fill(0, $add, $exception));
11732
+
11733
+ break;
11734
+ }
11735
+ }
11736
+
11737
+ return $responses;
11738
+ }
11739
+
11740
+ /**
11741
+ * {@inheritdoc}
11742
+ */
11743
+ protected function executeCluster(ClusterInterface $connection, SplQueue $commands)
11744
+ {
11745
+ $responses = array();
11746
+ $sizeOfPipe = count($commands);
11747
+ $exceptions = array();
11748
+
11749
+ foreach ($commands as $command) {
11750
+ $cmdConnection = $connection->getConnection($command);
11751
+
11752
+ if (isset($exceptions[spl_object_hash($cmdConnection)])) {
11753
+ continue;
11754
+ }
11755
+
11756
+ try {
11757
+ $cmdConnection->writeRequest($command);
11758
+ } catch (CommunicationException $exception) {
11759
+ $exceptions[spl_object_hash($cmdConnection)] = $exception;
11760
+ }
11761
+ }
11762
+
11763
+ for ($i = 0; $i < $sizeOfPipe; $i++) {
11764
+ $command = $commands->dequeue();
11765
+
11766
+ $cmdConnection = $connection->getConnection($command);
11767
+ $connectionHash = spl_object_hash($cmdConnection);
11768
+
11769
+ if (isset($exceptions[$connectionHash])) {
11770
+ $responses[$i] = $exceptions[$connectionHash];
11771
+ continue;
11772
+ }
11773
+
11774
+ try {
11775
+ $responses[$i] = $cmdConnection->readResponse($command);
11776
+ } catch (CommunicationException $exception) {
11777
+ $responses[$i] = $exception;
11778
+ $exceptions[$connectionHash] = $exception;
11779
+ }
11780
+ }
11781
+
11782
+ return $responses;
11783
+ }
11784
+ }
11785
+
11786
+ /**
11787
+ * Command pipeline wrapped into a MULTI / EXEC transaction.
11788
+ *
11789
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11790
+ */
11791
+ class Atomic extends Pipeline
11792
+ {
11793
+ /**
11794
+ * {@inheritdoc}
11795
+ */
11796
+ public function __construct(ClientInterface $client)
11797
+ {
11798
+ if (!$client->getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) {
11799
+ throw new ClientException(
11800
+ "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'."
11801
+ );
11802
+ }
11803
+
11804
+ parent::__construct($client);
11805
+ }
11806
+
11807
+ /**
11808
+ * {@inheritdoc}
11809
+ */
11810
+ protected function getConnection()
11811
+ {
11812
+ $connection = $this->getClient()->getConnection();
11813
+
11814
+ if (!$connection instanceof NodeConnectionInterface) {
11815
+ $class = __CLASS__;
11816
+
11817
+ throw new ClientException("The class '$class' does not support aggregate connections.");
11818
+ }
11819
+
11820
+ return $connection;
11821
+ }
11822
+
11823
+ /**
11824
+ * {@inheritdoc}
11825
+ */
11826
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11827
+ {
11828
+ $profile = $this->getClient()->getProfile();
11829
+ $connection->executeCommand($profile->createCommand('multi'));
11830
+
11831
+ foreach ($commands as $command) {
11832
+ $connection->writeRequest($command);
11833
+ }
11834
+
11835
+ foreach ($commands as $command) {
11836
+ $response = $connection->readResponse($command);
11837
+
11838
+ if ($response instanceof ErrorResponseInterface) {
11839
+ $connection->executeCommand($profile->createCommand('discard'));
11840
+ throw new ServerException($response->getMessage());
11841
+ }
11842
+ }
11843
+
11844
+ $executed = $connection->executeCommand($profile->createCommand('exec'));
11845
+
11846
+ if (!isset($executed)) {
11847
+ // TODO: should be throwing a more appropriate exception.
11848
+ throw new ClientException(
11849
+ 'The underlying transaction has been aborted by the server.'
11850
+ );
11851
+ }
11852
+
11853
+ if (count($executed) !== count($commands)) {
11854
+ $expected = count($commands);
11855
+ $received = count($executed);
11856
+
11857
+ throw new ClientException(
11858
+ "Invalid number of responses [expected $expected, received $received]."
11859
+ );
11860
+ }
11861
+
11862
+ $responses = array();
11863
+ $sizeOfPipe = count($commands);
11864
+ $exceptions = $this->throwServerExceptions();
11865
+
11866
+ for ($i = 0; $i < $sizeOfPipe; $i++) {
11867
+ $command = $commands->dequeue();
11868
+ $response = $executed[$i];
11869
+
11870
+ if (!$response instanceof ResponseInterface) {
11871
+ $responses[] = $command->parseResponse($response);
11872
+ } elseif ($response instanceof ErrorResponseInterface && $exceptions) {
11873
+ $this->exception($connection, $response);
11874
+ } else {
11875
+ $responses[] = $response;
11876
+ }
11877
+
11878
+ unset($executed[$i]);
11879
+ }
11880
+
11881
+ return $responses;
11882
+ }
11883
+ }
11884
+
11885
+ /* --------------------------------------------------------------------------- */
11886
+
11887
+ namespace Predis\Cluster\Distributor;
11888
+
11889
+ use Predis\Cluster\Hash\HashGeneratorInterface;
11890
+ use Exception;
11891
+
11892
+ /**
11893
+ * A distributor implements the logic to automatically distribute keys among
11894
+ * several nodes for client-side sharding.
11895
+ *
11896
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11897
+ */
11898
+ interface DistributorInterface
11899
+ {
11900
+ /**
11901
+ * Adds a node to the distributor with an optional weight.
11902
+ *
11903
+ * @param mixed $node Node object.
11904
+ * @param int $weight Weight for the node.
11905
+ */
11906
+ public function add($node, $weight = null);
11907
+
11908
+ /**
11909
+ * Removes a node from the distributor.
11910
+ *
11911
+ * @param mixed $node Node object.
11912
+ */
11913
+ public function remove($node);
11914
+
11915
+ /**
11916
+ * Returns the corresponding slot of a node from the distributor using the
11917
+ * computed hash of a key.
11918
+ *
11919
+ * @param mixed $hash
11920
+ *
11921
+ * @return mixed
11922
+ */
11923
+ public function getSlot($hash);
11924
+
11925
+ /**
11926
+ * Returns a node from the distributor using its assigned slot ID.
11927
+ *
11928
+ * @param mixed $slot
11929
+ *
11930
+ * @return mixed|null
11931
+ */
11932
+ public function getBySlot($slot);
11933
+
11934
+ /**
11935
+ * Returns a node from the distributor using the computed hash of a key.
11936
+ *
11937
+ * @param mixed $hash
11938
+ *
11939
+ * @return mixed
11940
+ */
11941
+ public function getByHash($hash);
11942
+
11943
+ /**
11944
+ * Returns a node from the distributor mapping to the specified value.
11945
+ *
11946
+ * @param string $value
11947
+ *
11948
+ * @return mixed
11949
+ */
11950
+ public function get($value);
11951
+
11952
+ /**
11953
+ * Returns the underlying hash generator instance.
11954
+ *
11955
+ * @return HashGeneratorInterface
11956
+ */
11957
+ public function getHashGenerator();
11958
+ }
11959
+
11960
+ /**
11961
+ * This class implements an hashring-based distributor that uses the same
11962
+ * algorithm of memcache to distribute keys in a cluster using client-side
11963
+ * sharding.
11964
+ *
11965
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11966
+ * @author Lorenzo Castelli <lcastelli@gmail.com>
11967
+ */
11968
+ class HashRing implements DistributorInterface, HashGeneratorInterface
11969
+ {
11970
+ const DEFAULT_REPLICAS = 128;
11971
+ const DEFAULT_WEIGHT = 100;
11972
+
11973
+ private $ring;
11974
+ private $ringKeys;
11975
+ private $ringKeysCount;
11976
+ private $replicas;
11977
+ private $nodeHashCallback;
11978
+ private $nodes = array();
11979
+
11980
+ /**
11981
+ * @param int $replicas Number of replicas in the ring.
11982
+ * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
11983
+ */
11984
+ public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
11985
+ {
11986
+ $this->replicas = $replicas;
11987
+ $this->nodeHashCallback = $nodeHashCallback;
11988
+ }
11989
+
11990
+ /**
11991
+ * Adds a node to the ring with an optional weight.
11992
+ *
11993
+ * @param mixed $node Node object.
11994
+ * @param int $weight Weight for the node.
11995
+ */
11996
+ public function add($node, $weight = null)
11997
+ {
11998
+ // In case of collisions in the hashes of the nodes, the node added
11999
+ // last wins, thus the order in which nodes are added is significant.
12000
+ $this->nodes[] = array(
12001
+ 'object' => $node,
12002
+ 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT
12003
+ );
12004
+
12005
+ $this->reset();
12006
+ }
12007
+
12008
+ /**
12009
+ * {@inheritdoc}
12010
+ */
12011
+ public function remove($node)
12012
+ {
12013
+ // A node is removed by resetting the ring so that it's recreated from
12014
+ // scratch, in order to reassign possible hashes with collisions to the
12015
+ // right node according to the order in which they were added in the
12016
+ // first place.
12017
+ for ($i = 0; $i < count($this->nodes); ++$i) {
12018
+ if ($this->nodes[$i]['object'] === $node) {
12019
+ array_splice($this->nodes, $i, 1);
12020
+ $this->reset();
12021
+
12022
+ break;
12023
+ }
12024
+ }
12025
+ }
12026
+
12027
+ /**
12028
+ * Resets the distributor.
12029
+ */
12030
+ private function reset()
12031
+ {
12032
+ unset(
12033
+ $this->ring,
12034
+ $this->ringKeys,
12035
+ $this->ringKeysCount
12036
+ );
12037
+ }
12038
+
12039
+ /**
12040
+ * Returns the initialization status of the distributor.
12041
+ *
12042
+ * @return bool
12043
+ */
12044
+ private function isInitialized()
12045
+ {
12046
+ return isset($this->ringKeys);
12047
+ }
12048
+
12049
+ /**
12050
+ * Calculates the total weight of all the nodes in the distributor.
12051
+ *
12052
+ * @return int
12053
+ */
12054
+ private function computeTotalWeight()
12055
+ {
12056
+ $totalWeight = 0;
12057
+
12058
+ foreach ($this->nodes as $node) {
12059
+ $totalWeight += $node['weight'];
12060
+ }
12061
+
12062
+ return $totalWeight;
12063
+ }
12064
+
12065
+ /**
12066
+ * Initializes the distributor.
12067
+ */
12068
+ private function initialize()
12069
+ {
12070
+ if ($this->isInitialized()) {
12071
+ return;
12072
+ }
12073
+
12074
+ if (!$this->nodes) {
12075
+ throw new EmptyRingException('Cannot initialize an empty hashring.');
12076
+ }
12077
+
12078
+ $this->ring = array();
12079
+ $totalWeight = $this->computeTotalWeight();
12080
+ $nodesCount = count($this->nodes);
12081
+
12082
+ foreach ($this->nodes as $node) {
12083
+ $weightRatio = $node['weight'] / $totalWeight;
12084
+ $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
12085
+ }
12086
+
12087
+ ksort($this->ring, SORT_NUMERIC);
12088
+ $this->ringKeys = array_keys($this->ring);
12089
+ $this->ringKeysCount = count($this->ringKeys);
12090
+ }
12091
+
12092
+ /**
12093
+ * Implements the logic needed to add a node to the hashring.
12094
+ *
12095
+ * @param array $ring Source hashring.
12096
+ * @param mixed $node Node object to be added.
12097
+ * @param int $totalNodes Total number of nodes.
12098
+ * @param int $replicas Number of replicas in the ring.
12099
+ * @param float $weightRatio Weight ratio for the node.
12100
+ */
12101
+ protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
12102
+ {
12103
+ $nodeObject = $node['object'];
12104
+ $nodeHash = $this->getNodeHash($nodeObject);
12105
+ $replicas = (int) round($weightRatio * $totalNodes * $replicas);
12106
+
12107
+ for ($i = 0; $i < $replicas; $i++) {
12108
+ $key = crc32("$nodeHash:$i");
12109
+ $ring[$key] = $nodeObject;
12110
+ }
12111
+ }
12112
+
12113
+ /**
12114
+ * {@inheritdoc}
12115
+ */
12116
+ protected function getNodeHash($nodeObject)
12117
+ {
12118
+ if (!isset($this->nodeHashCallback)) {
12119
+ return (string) $nodeObject;
12120
+ }
12121
+
12122
+ return call_user_func($this->nodeHashCallback, $nodeObject);
12123
+ }
12124
+
12125
+ /**
12126
+ * {@inheritdoc}
12127
+ */
12128
+ public function hash($value)
12129
+ {
12130
+ return crc32($value);
12131
+ }
12132
+
12133
+ /**
12134
+ * {@inheritdoc}
12135
+ */
12136
+ public function getByHash($hash)
12137
+ {
12138
+ return $this->ring[$this->getSlot($hash)];
12139
+ }
12140
+
12141
+ /**
12142
+ * {@inheritdoc}
12143
+ */
12144
+ public function getBySlot($slot)
12145
+ {
12146
+ $this->initialize();
12147
+
12148
+ if (isset($this->ring[$slot])) {
12149
+ return $this->ring[$slot];
12150
+ }
12151
+ }
12152
+
12153
+ /**
12154
+ * {@inheritdoc}
12155
+ */
12156
+ public function getSlot($hash)
12157
+ {
12158
+ $this->initialize();
12159
+
12160
+ $ringKeys = $this->ringKeys;
12161
+ $upper = $this->ringKeysCount - 1;
12162
+ $lower = 0;
12163
+
12164
+ while ($lower <= $upper) {
12165
+ $index = ($lower + $upper) >> 1;
12166
+ $item = $ringKeys[$index];
12167
+
12168
+ if ($item > $hash) {
12169
+ $upper = $index - 1;
12170
+ } elseif ($item < $hash) {
12171
+ $lower = $index + 1;
12172
+ } else {
12173
+ return $item;
12174
+ }
12175
+ }
12176
+
12177
+ return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
12178
+ }
12179
+
12180
+ /**
12181
+ * {@inheritdoc}
12182
+ */
12183
+ public function get($value)
12184
+ {
12185
+ $hash = $this->hash($value);
12186
+ $node = $this->getByHash($hash);
12187
+
12188
+ return $node;
12189
+ }
12190
+
12191
+ /**
12192
+ * Implements a strategy to deal with wrap-around errors during binary searches.
12193
+ *
12194
+ * @param int $upper
12195
+ * @param int $lower
12196
+ * @param int $ringKeysCount
12197
+ *
12198
+ * @return int
12199
+ */
12200
+ protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
12201
+ {
12202
+ // Binary search for the last item in ringkeys with a value less or
12203
+ // equal to the key. If no such item exists, return the last item.
12204
+ return $upper >= 0 ? $upper : $ringKeysCount - 1;
12205
+ }
12206
+
12207
+ /**
12208
+ * {@inheritdoc}
12209
+ */
12210
+ public function getHashGenerator()
12211
+ {
12212
+ return $this;
12213
+ }
12214
+ }
12215
+
12216
+ /**
12217
+ * This class implements an hashring-based distributor that uses the same
12218
+ * algorithm of libketama to distribute keys in a cluster using client-side
12219
+ * sharding.
12220
+ *
12221
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12222
+ * @author Lorenzo Castelli <lcastelli@gmail.com>
12223
+ */
12224
+ class KetamaRing extends HashRing
12225
+ {
12226
+ const DEFAULT_REPLICAS = 160;
12227
+
12228
+ /**
12229
+ * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
12230
+ */
12231
+ public function __construct($nodeHashCallback = null)
12232
+ {
12233
+ parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
12234
+ }
12235
+
12236
+ /**
12237
+ * {@inheritdoc}
12238
+ */
12239
+ protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
12240
+ {
12241
+ $nodeObject = $node['object'];
12242
+ $nodeHash = $this->getNodeHash($nodeObject);
12243
+ $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
12244
+
12245
+ for ($i = 0; $i < $replicas; $i++) {
12246
+ $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
12247
+
12248
+ foreach ($unpackedDigest as $key) {
12249
+ $ring[$key] = $nodeObject;
12250
+ }
12251
+ }
12252
+ }
12253
+
12254
+ /**
12255
+ * {@inheritdoc}
12256
+ */
12257
+ public function hash($value)
12258
+ {
12259
+ $hash = unpack('V', md5($value, true));
12260
+
12261
+ return $hash[1];
12262
+ }
12263
+
12264
+ /**
12265
+ * {@inheritdoc}
12266
+ */
12267
+ protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
12268
+ {
12269
+ // Binary search for the first item in ringkeys with a value greater
12270
+ // or equal to the key. If no such item exists, return the first item.
12271
+ return $lower < $ringKeysCount ? $lower : 0;
12272
+ }
12273
+ }
12274
+
12275
+ /**
12276
+ * Exception class that identifies empty rings.
12277
+ *
12278
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12279
+ */
12280
+ class EmptyRingException extends Exception
12281
+ {
12282
+ }
12283
+
12284
+ /* --------------------------------------------------------------------------- */
12285
+
12286
+ namespace Predis\Response\Iterator;
12287
+
12288
+ use Predis\Connection\NodeConnectionInterface;
12289
+ use Iterator;
12290
+ use Countable;
12291
+ use Predis\Response\ResponseInterface;
12292
+ use OuterIterator;
12293
+ use InvalidArgumentException;
12294
+ use UnexpectedValueException;
12295
+
12296
+ /**
12297
+ * Iterator that abstracts the access to multibulk responses allowing them to be
12298
+ * consumed in a streamable fashion without keeping the whole payload in memory.
12299
+ *
12300
+ * This iterator does not support rewinding which means that the iteration, once
12301
+ * consumed, cannot be restarted.
12302
+ *
12303
+ * Always make sure that the whole iteration is consumed (or dropped) to prevent
12304
+ * protocol desynchronization issues.
12305
+ *
12306
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12307
+ */
12308
+ abstract class MultiBulkIterator implements Iterator, Countable, ResponseInterface
12309
+ {
12310
+ protected $current;
12311
+ protected $position;
12312
+ protected $size;
12313
+
12314
+ /**
12315
+ * {@inheritdoc}
12316
+ */
12317
+ public function rewind()
12318
+ {
12319
+ // NOOP
12320
+ }
12321
+
12322
+ /**
12323
+ * {@inheritdoc}
12324
+ */
12325
+ public function current()
12326
+ {
12327
+ return $this->current;
12328
+ }
12329
+
12330
+ /**
12331
+ * {@inheritdoc}
12332
+ */
12333
+ public function key()
12334
+ {
12335
+ return $this->position;
12336
+ }
12337
+
12338
+ /**
12339
+ * {@inheritdoc}
12340
+ */
12341
+ public function next()
12342
+ {
12343
+ if (++$this->position < $this->size) {
12344
+ $this->current = $this->getValue();
12345
+ }
12346
+ }
12347
+
12348
+ /**
12349
+ * {@inheritdoc}
12350
+ */
12351
+ public function valid()
12352
+ {
12353
+ return $this->position < $this->size;
12354
+ }
12355
+
12356
+ /**
12357
+ * Returns the number of items comprising the whole multibulk response.
12358
+ *
12359
+ * This method should be used instead of iterator_count() to get the size of
12360
+ * the current multibulk response since the former consumes the iteration to
12361
+ * count the number of elements, but our iterators do not support rewinding.
12362
+ *
12363
+ * @return int
12364
+ */
12365
+ public function count()
12366
+ {
12367
+ return $this->size;
12368
+ }
12369
+
12370
+ /**
12371
+ * Returns the current position of the iterator.
12372
+ *
12373
+ * @return int
12374
+ */
12375
+ public function getPosition()
12376
+ {
12377
+ return $this->position;
12378
+ }
12379
+
12380
+ /**
12381
+ * {@inheritdoc}
12382
+ */
12383
+ abstract protected function getValue();
12384
+ }
12385
+
12386
+ /**
12387
+ * Streamable multibulk response.
12388
+ *
12389
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12390
+ */
12391
+ class MultiBulk extends MultiBulkIterator
12392
+ {
12393
+ private $connection;
12394
+
12395
+ /**
12396
+ * @param NodeConnectionInterface $connection Connection to Redis.
12397
+ * @param int $size Number of elements of the multibulk response.
12398
+ */
12399
+ public function __construct(NodeConnectionInterface $connection, $size)
12400
+ {
12401
+ $this->connection = $connection;
12402
+ $this->size = $size;
12403
+ $this->position = 0;
12404
+ $this->current = $size > 0 ? $this->getValue() : null;
12405
+ }
12406
+
12407
+ /**
12408
+ * Handles the synchronization of the client with the Redis protocol when
12409
+ * the garbage collector kicks in (e.g. when the iterator goes out of the
12410
+ * scope of a foreach or it is unset).
12411
+ */
12412
+ public function __destruct()
12413
+ {
12414
+ $this->drop(true);
12415
+ }
12416
+
12417
+ /**
12418
+ * Drop queued elements that have not been read from the connection either
12419
+ * by consuming the rest of the multibulk response or quickly by closing the
12420
+ * underlying connection.
12421
+ *
12422
+ * @param bool $disconnect Consume the iterator or drop the connection.
12423
+ */
12424
+ public function drop($disconnect = false)
12425
+ {
12426
+ if ($disconnect) {
12427
+ if ($this->valid()) {
12428
+ $this->position = $this->size;
12429
+ $this->connection->disconnect();
12430
+ }
12431
+ } else {
12432
+ while ($this->valid()) {
12433
+ $this->next();
12434
+ }
12435
+ }
12436
+ }
12437
+
12438
+ /**
12439
+ * Reads the next item of the multibulk response from the connection.
12440
+ *
12441
+ * @return mixed
12442
+ */
12443
+ protected function getValue()
12444
+ {
12445
+ return $this->connection->read();
12446
+ }
12447
+ }
12448
+
12449
+ /**
12450
+ * Outer iterator consuming streamable multibulk responses by yielding tuples of
12451
+ * keys and values.
12452
+ *
12453
+ * This wrapper is useful for responses to commands such as `HGETALL` that can
12454
+ * be iterater as $key => $value pairs.
12455
+ *
12456
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12457
+ */
12458
+ class MultiBulkTuple extends MultiBulk implements OuterIterator
12459
+ {
12460
+ private $iterator;
12461
+
12462
+ /**
12463
+ * @param MultiBulk $iterator Inner multibulk response iterator.
12464
+ */
12465
+ public function __construct(MultiBulk $iterator)
12466
+ {
12467
+ $this->checkPreconditions($iterator);
12468
+
12469
+ $this->size = count($iterator) / 2;
12470
+ $this->iterator = $iterator;
12471
+ $this->position = $iterator->getPosition();
12472
+ $this->current = $this->size > 0 ? $this->getValue() : null;
12473
+ }
12474
+
12475
+ /**
12476
+ * Checks for valid preconditions.
12477
+ *
12478
+ * @param MultiBulk $iterator Inner multibulk response iterator.
12479
+ */
12480
+ protected function checkPreconditions(MultiBulk $iterator)
12481
+ {
12482
+ if ($iterator->getPosition() !== 0) {
12483
+ throw new InvalidArgumentException(
12484
+ 'Cannot initialize a tuple iterator using an already initiated iterator.'
12485
+ );
12486
+ }
12487
+
12488
+ if (($size = count($iterator)) % 2 !== 0) {
12489
+ throw new UnexpectedValueException("Invalid response size for a tuple iterator.");
12490
+ }
12491
+ }
12492
+
12493
+ /**
12494
+ * {@inheritdoc}
12495
+ */
12496
+ public function getInnerIterator()
12497
+ {
12498
+ return $this->iterator;
12499
+ }
12500
+
12501
+ /**
12502
+ * {@inheritdoc}
12503
+ */
12504
+ public function __destruct()
12505
+ {
12506
+ $this->iterator->drop(true);
12507
+ }
12508
+
12509
+ /**
12510
+ * {@inheritdoc}
12511
+ */
12512
+ protected function getValue()
12513
+ {
12514
+ $k = $this->iterator->current();
12515
+ $this->iterator->next();
12516
+
12517
+ $v = $this->iterator->current();
12518
+ $this->iterator->next();
12519
+
12520
+ return array($k, $v);
12521
+ }
12522
+ }
12523
+
12524
+ /* --------------------------------------------------------------------------- */
12525
+
12526
+ namespace Predis\Cluster\Hash;
12527
+
12528
+ /**
12529
+ * An hash generator implements the logic used to calculate the hash of a key to
12530
+ * distribute operations among Redis nodes.
12531
+ *
12532
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12533
+ */
12534
+ interface HashGeneratorInterface
12535
+ {
12536
+ /**
12537
+ * Generates an hash from a string to be used for distribution.
12538
+ *
12539
+ * @param string $value String value.
12540
+ *
12541
+ * @return int
12542
+ */
12543
+ public function hash($value);
12544
+ }
12545
+
12546
+ /**
12547
+ * Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
12548
+ *
12549
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12550
+ */
12551
+ class CRC16 implements HashGeneratorInterface
12552
+ {
12553
+ private static $CCITT_16 = array(
12554
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
12555
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
12556
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
12557
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
12558
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
12559
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
12560
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
12561
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
12562
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
12563
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
12564
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
12565
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
12566
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
12567
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
12568
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
12569
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
12570
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
12571
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
12572
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
12573
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
12574
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
12575
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
12576
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
12577
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
12578
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
12579
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
12580
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
12581
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
12582
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
12583
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
12584
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
12585
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
12586
+ );
12587
+
12588
+ /**
12589
+ * {@inheritdoc}
12590
+ */
12591
+ public function hash($value)
12592
+ {
12593
+ // CRC-CCITT-16 algorithm
12594
+ $crc = 0;
12595
+ $CCITT_16 = self::$CCITT_16;
12596
+ $strlen = strlen($value);
12597
+
12598
+ for ($i = 0; $i < $strlen; $i++) {
12599
+ $crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
12600
+ }
12601
+
12602
+ return $crc;
12603
+ }
12604
+ }
12605
+
12606
+ /* --------------------------------------------------------------------------- */
12607
+
12608
+ namespace Predis\Command\Processor;
12609
+
12610
+ use InvalidArgumentException;
12611
+ use Predis\Command\CommandInterface;
12612
+ use Predis\Command\PrefixableCommandInterface;
12613
+ use ArrayAccess;
12614
+ use ArrayIterator;
12615
+
12616
+ /**
12617
+ * A command processor processes Redis commands before they are sent to Redis.
12618
+ *
12619
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12620
+ */
12621
+ interface ProcessorInterface
12622
+ {
12623
+ /**
12624
+ * Processes the given Redis command.
12625
+ *
12626
+ * @param CommandInterface $command Command instance.
12627
+ */
12628
+ public function process(CommandInterface $command);
12629
+ }
12630
+
12631
+ /**
12632
+ * Default implementation of a command processors chain.
12633
+ *
12634
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12635
+ */
12636
+ class ProcessorChain implements ArrayAccess, ProcessorInterface
12637
+ {
12638
+ private $processors = array();
12639
+
12640
+ /**
12641
+ * @param array $processors List of instances of ProcessorInterface.
12642
+ */
12643
+ public function __construct($processors = array())
12644
+ {
12645
+ foreach ($processors as $processor) {
12646
+ $this->add($processor);
12647
+ }
12648
+ }
12649
+
12650
+ /**
12651
+ * {@inheritdoc}
12652
+ */
12653
+ public function add(ProcessorInterface $processor)
12654
+ {
12655
+ $this->processors[] = $processor;
12656
+ }
12657
+
12658
+ /**
12659
+ * {@inheritdoc}
12660
+ */
12661
+ public function remove(ProcessorInterface $processor)
12662
+ {
12663
+ if (false !== $index = array_search($processor, $this->processors, true)) {
12664
+ unset($this[$index]);
12665
+ }
12666
+ }
12667
+
12668
+ /**
12669
+ * {@inheritdoc}
12670
+ */
12671
+ public function process(CommandInterface $command)
12672
+ {
12673
+ for ($i = 0; $i < $count = count($this->processors); $i++) {
12674
+ $this->processors[$i]->process($command);
12675
+ }
12676
+ }
12677
+
12678
+ /**
12679
+ * {@inheritdoc}
12680
+ */
12681
+ public function getProcessors()
12682
+ {
12683
+ return $this->processors;
12684
+ }
12685
+
12686
+ /**
12687
+ * Returns an iterator over the list of command processor in the chain.
12688
+ *
12689
+ * @return ArrayIterator
12690
+ */
12691
+ public function getIterator()
12692
+ {
12693
+ return new ArrayIterator($this->processors);
12694
+ }
12695
+
12696
+ /**
12697
+ * Returns the number of command processors in the chain.
12698
+ *
12699
+ * @return int
12700
+ */
12701
+ public function count()
12702
+ {
12703
+ return count($this->processors);
12704
+ }
12705
+
12706
+ /**
12707
+ * {@inheritdoc}
12708
+ */
12709
+ public function offsetExists($index)
12710
+ {
12711
+ return isset($this->processors[$index]);
12712
+ }
12713
+
12714
+ /**
12715
+ * {@inheritdoc}
12716
+ */
12717
+ public function offsetGet($index)
12718
+ {
12719
+ return $this->processors[$index];
12720
+ }
12721
+
12722
+ /**
12723
+ * {@inheritdoc}
12724
+ */
12725
+ public function offsetSet($index, $processor)
12726
+ {
12727
+ if (!$processor instanceof ProcessorInterface) {
12728
+ throw new InvalidArgumentException(
12729
+ "A processor chain accepts only instances of ".
12730
+ "'Predis\Command\Processor\ProcessorInterface'."
12731
+ );
12732
+ }
12733
+
12734
+ $this->processors[$index] = $processor;
12735
+ }
12736
+
12737
+ /**
12738
+ * {@inheritdoc}
12739
+ */
12740
+ public function offsetUnset($index)
12741
+ {
12742
+ unset($this->processors[$index]);
12743
+ $this->processors = array_values($this->processors);
12744
+ }
12745
+ }
12746
+
12747
+ /**
12748
+ * Command processor capable of prefixing keys stored in the arguments of Redis
12749
+ * commands supported.
12750
+ *
12751
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12752
+ */
12753
+ class KeyPrefixProcessor implements ProcessorInterface
12754
+ {
12755
+ private $prefix;
12756
+ private $commands;
12757
+
12758
+ /**
12759
+ * @param string $prefix Prefix for the keys.
12760
+ */
12761
+ public function __construct($prefix)
12762
+ {
12763
+ $this->prefix = $prefix;
12764
+ $this->commands = array(
12765
+ /* ---------------- Redis 1.2 ---------------- */
12766
+ 'EXISTS' => 'self::first',
12767
+ 'DEL' => 'self::all',
12768
+ 'TYPE' => 'self::first',
12769
+ 'KEYS' => 'self::first',
12770
+ 'RENAME' => 'self::all',
12771
+ 'RENAMENX' => 'self::all',
12772
+ 'EXPIRE' => 'self::first',
12773
+ 'EXPIREAT' => 'self::first',
12774
+ 'TTL' => 'self::first',
12775
+ 'MOVE' => 'self::first',
12776
+ 'SORT' => 'self::sort',
12777
+ 'DUMP' => 'self::first',
12778
+ 'RESTORE' => 'self::first',
12779
+ 'SET' => 'self::first',
12780
+ 'SETNX' => 'self::first',
12781
+ 'MSET' => 'self::interleaved',
12782
+ 'MSETNX' => 'self::interleaved',
12783
+ 'GET' => 'self::first',
12784
+ 'MGET' => 'self::all',
12785
+ 'GETSET' => 'self::first',
12786
+ 'INCR' => 'self::first',
12787
+ 'INCRBY' => 'self::first',
12788
+ 'DECR' => 'self::first',
12789
+ 'DECRBY' => 'self::first',
12790
+ 'RPUSH' => 'self::first',
12791
+ 'LPUSH' => 'self::first',
12792
+ 'LLEN' => 'self::first',
12793
+ 'LRANGE' => 'self::first',
12794
+ 'LTRIM' => 'self::first',
12795
+ 'LINDEX' => 'self::first',
12796
+ 'LSET' => 'self::first',
12797
+ 'LREM' => 'self::first',
12798
+ 'LPOP' => 'self::first',
12799
+ 'RPOP' => 'self::first',
12800
+ 'RPOPLPUSH' => 'self::all',
12801
+ 'SADD' => 'self::first',
12802
+ 'SREM' => 'self::first',
12803
+ 'SPOP' => 'self::first',
12804
+ 'SMOVE' => 'self::skipLast',
12805
+ 'SCARD' => 'self::first',
12806
+ 'SISMEMBER' => 'self::first',
12807
+ 'SINTER' => 'self::all',
12808
+ 'SINTERSTORE' => 'self::all',
12809
+ 'SUNION' => 'self::all',
12810
+ 'SUNIONSTORE' => 'self::all',
12811
+ 'SDIFF' => 'self::all',
12812
+ 'SDIFFSTORE' => 'self::all',
12813
+ 'SMEMBERS' => 'self::first',
12814
+ 'SRANDMEMBER' => 'self::first',
12815
+ 'ZADD' => 'self::first',
12816
+ 'ZINCRBY' => 'self::first',
12817
+ 'ZREM' => 'self::first',
12818
+ 'ZRANGE' => 'self::first',
12819
+ 'ZREVRANGE' => 'self::first',
12820
+ 'ZRANGEBYSCORE' => 'self::first',
12821
+ 'ZCARD' => 'self::first',
12822
+ 'ZSCORE' => 'self::first',
12823
+ 'ZREMRANGEBYSCORE' => 'self::first',
12824
+ /* ---------------- Redis 2.0 ---------------- */
12825
+ 'SETEX' => 'self::first',
12826
+ 'APPEND' => 'self::first',
12827
+ 'SUBSTR' => 'self::first',
12828
+ 'BLPOP' => 'self::skipLast',
12829
+ 'BRPOP' => 'self::skipLast',
12830
+ 'ZUNIONSTORE' => 'self::zsetStore',
12831
+ 'ZINTERSTORE' => 'self::zsetStore',
12832
+ 'ZCOUNT' => 'self::first',
12833
+ 'ZRANK' => 'self::first',
12834
+ 'ZREVRANK' => 'self::first',
12835
+ 'ZREMRANGEBYRANK' => 'self::first',
12836
+ 'HSET' => 'self::first',
12837
+ 'HSETNX' => 'self::first',
12838
+ 'HMSET' => 'self::first',
12839
+ 'HINCRBY' => 'self::first',
12840
+ 'HGET' => 'self::first',
12841
+ 'HMGET' => 'self::first',
12842
+ 'HDEL' => 'self::first',
12843
+ 'HEXISTS' => 'self::first',
12844
+ 'HLEN' => 'self::first',
12845
+ 'HKEYS' => 'self::first',
12846
+ 'HVALS' => 'self::first',
12847
+ 'HGETALL' => 'self::first',
12848
+ 'SUBSCRIBE' => 'self::all',
12849
+ 'UNSUBSCRIBE' => 'self::all',
12850
+ 'PSUBSCRIBE' => 'self::all',
12851
+ 'PUNSUBSCRIBE' => 'self::all',
12852
+ 'PUBLISH' => 'self::first',
12853
+ /* ---------------- Redis 2.2 ---------------- */
12854
+ 'PERSIST' => 'self::first',
12855
+ 'STRLEN' => 'self::first',
12856
+ 'SETRANGE' => 'self::first',
12857
+ 'GETRANGE' => 'self::first',
12858
+ 'SETBIT' => 'self::first',
12859
+ 'GETBIT' => 'self::first',
12860
+ 'RPUSHX' => 'self::first',
12861
+ 'LPUSHX' => 'self::first',
12862
+ 'LINSERT' => 'self::first',
12863
+ 'BRPOPLPUSH' => 'self::skipLast',
12864
+ 'ZREVRANGEBYSCORE' => 'self::first',
12865
+ 'WATCH' => 'self::all',
12866
+ /* ---------------- Redis 2.6 ---------------- */
12867
+ 'PTTL' => 'self::first',
12868
+ 'PEXPIRE' => 'self::first',
12869
+ 'PEXPIREAT' => 'self::first',
12870
+ 'PSETEX' => 'self::first',
12871
+ 'INCRBYFLOAT' => 'self::first',
12872
+ 'BITOP' => 'self::skipFirst',
12873
+ 'BITCOUNT' => 'self::first',
12874
+ 'HINCRBYFLOAT' => 'self::first',
12875
+ 'EVAL' => 'self::evalKeys',
12876
+ 'EVALSHA' => 'self::evalKeys',
12877
+ /* ---------------- Redis 2.8 ---------------- */
12878
+ 'SSCAN' => 'self::first',
12879
+ 'ZSCAN' => 'self::first',
12880
+ 'HSCAN' => 'self::first',
12881
+ 'PFADD' => 'self::first',
12882
+ 'PFCOUNT' => 'self::all',
12883
+ 'PFMERGE' => 'self::all',
12884
+ 'ZLEXCOUNT' => 'self::first',
12885
+ 'ZRANGEBYLEX' => 'self::first',
12886
+ 'ZREMRANGEBYLEX' => 'self::first',
12887
+ );
12888
+ }
12889
+
12890
+ /**
12891
+ * Sets a prefix that is applied to all the keys.
12892
+ *
12893
+ * @param string $prefix Prefix for the keys.
12894
+ */
12895
+ public function setPrefix($prefix)
12896
+ {
12897
+ $this->prefix = $prefix;
12898
+ }
12899
+
12900
+ /**
12901
+ * Gets the current prefix.
12902
+ *
12903
+ * @return string
12904
+ */
12905
+ public function getPrefix()
12906
+ {
12907
+ return $this->prefix;
12908
+ }
12909
+
12910
+ /**
12911
+ * {@inheritdoc}
12912
+ */
12913
+ public function process(CommandInterface $command)
12914
+ {
12915
+ if ($command instanceof PrefixableCommandInterface) {
12916
+ $command->prefixKeys($this->prefix);
12917
+ } elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) {
12918
+ call_user_func($this->commands[$commandID], $command, $this->prefix);
12919
+ }
12920
+ }
12921
+
12922
+ /**
12923
+ * Sets an handler for the specified command ID.
12924
+ *
12925
+ * The callback signature must have 2 parameters of the following types:
12926
+ *
12927
+ * - Predis\Command\CommandInterface (command instance)
12928
+ * - String (prefix)
12929
+ *
12930
+ * When the callback argument is omitted or NULL, the previously
12931
+ * associated handler for the specified command ID is removed.
12932
+ *
12933
+ * @param string $commandID The ID of the command to be handled.
12934
+ * @param mixed $callback A valid callable object or NULL.
12935
+ */
12936
+ public function setCommandHandler($commandID, $callback = null)
12937
+ {
12938
+ $commandID = strtoupper($commandID);
12939
+
12940
+ if (!isset($callback)) {
12941
+ unset($this->commands[$commandID]);
12942
+
12943
+ return;
12944
+ }
12945
+
12946
+ if (!is_callable($callback)) {
12947
+ throw new InvalidArgumentException(
12948
+ "Callback must be a valid callable object or NULL"
12949
+ );
12950
+ }
12951
+
12952
+ $this->commands[$commandID] = $callback;
12953
+ }
12954
+
12955
+ /**
12956
+ * {@inheritdoc}
12957
+ */
12958
+ public function __toString()
12959
+ {
12960
+ return $this->getPrefix();
12961
+ }
12962
+
12963
+ /**
12964
+ * Applies the specified prefix only the first argument.
12965
+ *
12966
+ * @param CommandInterface $command Command instance.
12967
+ * @param string $prefix Prefix string.
12968
+ */
12969
+ public static function first(CommandInterface $command, $prefix)
12970
+ {
12971
+ if ($arguments = $command->getArguments()) {
12972
+ $arguments[0] = "$prefix{$arguments[0]}";
12973
+ $command->setRawArguments($arguments);
12974
+ }
12975
+ }
12976
+
12977
+ /**
12978
+ * Applies the specified prefix to all the arguments.
12979
+ *
12980
+ * @param CommandInterface $command Command instance.
12981
+ * @param string $prefix Prefix string.
12982
+ */
12983
+ public static function all(CommandInterface $command, $prefix)
12984
+ {
12985
+ if ($arguments = $command->getArguments()) {
12986
+ foreach ($arguments as &$key) {
12987
+ $key = "$prefix$key";
12988
+ }
12989
+
12990
+ $command->setRawArguments($arguments);
12991
+ }
12992
+ }
12993
+
12994
+ /**
12995
+ * Applies the specified prefix only to even arguments in the list.
12996
+ *
12997
+ * @param CommandInterface $command Command instance.
12998
+ * @param string $prefix Prefix string.
12999
+ */
13000
+ public static function interleaved(CommandInterface $command, $prefix)
13001
+ {
13002
+ if ($arguments = $command->getArguments()) {
13003
+ $length = count($arguments);
13004
+
13005
+ for ($i = 0; $i < $length; $i += 2) {
13006
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13007
+ }
13008
+
13009
+ $command->setRawArguments($arguments);
13010
+ }
13011
+ }
13012
+
13013
+ /**
13014
+ * Applies the specified prefix to all the arguments but the first one.
13015
+ *
13016
+ * @param CommandInterface $command Command instance.
13017
+ * @param string $prefix Prefix string.
13018
+ */
13019
+ public static function skipFirst(CommandInterface $command, $prefix)
13020
+ {
13021
+ if ($arguments = $command->getArguments()) {
13022
+ $length = count($arguments);
13023
+
13024
+ for ($i = 1; $i < $length; $i++) {
13025
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13026
+ }
13027
+
13028
+ $command->setRawArguments($arguments);
13029
+ }
13030
+ }
13031
+
13032
+ /**
13033
+ * Applies the specified prefix to all the arguments but the last one.
13034
+ *
13035
+ * @param CommandInterface $command Command instance.
13036
+ * @param string $prefix Prefix string.
13037
+ */
13038
+ public static function skipLast(CommandInterface $command, $prefix)
13039
+ {
13040
+ if ($arguments = $command->getArguments()) {
13041
+ $length = count($arguments);
13042
+
13043
+ for ($i = 0; $i < $length - 1; $i++) {
13044
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13045
+ }
13046
+
13047
+ $command->setRawArguments($arguments);
13048
+ }
13049
+ }
13050
+
13051
+ /**
13052
+ * Applies the specified prefix to the keys of a SORT command.
13053
+ *
13054
+ * @param CommandInterface $command Command instance.
13055
+ * @param string $prefix Prefix string.
13056
+ */
13057
+ public static function sort(CommandInterface $command, $prefix)
13058
+ {
13059
+ if ($arguments = $command->getArguments()) {
13060
+ $arguments[0] = "$prefix{$arguments[0]}";
13061
+
13062
+ if (($count = count($arguments)) > 1) {
13063
+ for ($i = 1; $i < $count; $i++) {
13064
+ switch ($arguments[$i]) {
13065
+ case 'BY':
13066
+ case 'STORE':
13067
+ $arguments[$i] = "$prefix{$arguments[++$i]}";
13068
+ break;
13069
+
13070
+ case 'GET':
13071
+ $value = $arguments[++$i];
13072
+ if ($value !== '#') {
13073
+ $arguments[$i] = "$prefix$value";
13074
+ }
13075
+ break;
13076
+
13077
+ case 'LIMIT';
13078
+ $i += 2;
13079
+ break;
13080
+ }
13081
+ }
13082
+ }
13083
+
13084
+ $command->setRawArguments($arguments);
13085
+ }
13086
+ }
13087
+
13088
+ /**
13089
+ * Applies the specified prefix to the keys of an EVAL-based command.
13090
+ *
13091
+ * @param CommandInterface $command Command instance.
13092
+ * @param string $prefix Prefix string.
13093
+ */
13094
+ public static function evalKeys(CommandInterface $command, $prefix)
13095
+ {
13096
+ if ($arguments = $command->getArguments()) {
13097
+ for ($i = 2; $i < $arguments[1] + 2; $i++) {
13098
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13099
+ }
13100
+
13101
+ $command->setRawArguments($arguments);
13102
+ }
13103
+ }
13104
+
13105
+ /**
13106
+ * Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE.
13107
+ *
13108
+ * @param CommandInterface $command Command instance.
13109
+ * @param string $prefix Prefix string.
13110
+ */
13111
+ public static function zsetStore(CommandInterface $command, $prefix)
13112
+ {
13113
+ if ($arguments = $command->getArguments()) {
13114
+ $arguments[0] = "$prefix{$arguments[0]}";
13115
+ $length = ((int) $arguments[1]) + 2;
13116
+
13117
+ for ($i = 2; $i < $length; $i++) {
13118
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13119
+ }
13120
+
13121
+ $command->setRawArguments($arguments);
13122
+ }
13123
+ }
13124
+ }
13125
+
13126
+ /* --------------------------------------------------------------------------- */
13127
+
13128
+ namespace Predis\Protocol\Text;
13129
+
13130
+ use Predis\Command\CommandInterface;
13131
+ use Predis\Connection\CompositeConnectionInterface;
13132
+ use Predis\Protocol\ProtocolProcessorInterface;
13133
+ use Predis\Protocol\RequestSerializerInterface;
13134
+ use Predis\Protocol\ResponseReaderInterface;
13135
+ use Predis\CommunicationException;
13136
+ use Predis\Protocol\ProtocolException;
13137
+ use Predis\Response\Status as StatusResponse;
13138
+ use Predis\Response\Error as ErrorResponse;
13139
+ use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
13140
+
13141
+ /**
13142
+ * Response reader for the standard Redis wire protocol.
13143
+ *
13144
+ * @link http://redis.io/topics/protocol
13145
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13146
+ */
13147
+ class ResponseReader implements ResponseReaderInterface
13148
+ {
13149
+ protected $handlers;
13150
+
13151
+ /**
13152
+ *
13153
+ */
13154
+ public function __construct()
13155
+ {
13156
+ $this->handlers = $this->getDefaultHandlers();
13157
+ }
13158
+
13159
+ /**
13160
+ * Returns the default handlers for the supported type of responses.
13161
+ *
13162
+ * @return array
13163
+ */
13164
+ protected function getDefaultHandlers()
13165
+ {
13166
+ return array(
13167
+ '+' => new Handler\StatusResponse(),
13168
+ '-' => new Handler\ErrorResponse(),
13169
+ ':' => new Handler\IntegerResponse(),
13170
+ '$' => new Handler\BulkResponse(),
13171
+ '*' => new Handler\MultiBulkResponse(),
13172
+ );
13173
+ }
13174
+
13175
+ /**
13176
+ * Sets the handler for the specified prefix identifying the response type.
13177
+ *
13178
+ * @param string $prefix Identifier of the type of response.
13179
+ * @param Handler\ResponseHandlerInterface $handler Response handler.
13180
+ */
13181
+ public function setHandler($prefix, Handler\ResponseHandlerInterface $handler)
13182
+ {
13183
+ $this->handlers[$prefix] = $handler;
13184
+ }
13185
+
13186
+ /**
13187
+ * Returns the response handler associated to a certain type of response.
13188
+ *
13189
+ * @param string $prefix Identifier of the type of response.
13190
+ *
13191
+ * @return Handler\ResponseHandlerInterface
13192
+ */
13193
+ public function getHandler($prefix)
13194
+ {
13195
+ if (isset($this->handlers[$prefix])) {
13196
+ return $this->handlers[$prefix];
13197
+ }
13198
+
13199
+ return null;
13200
+ }
13201
+
13202
+ /**
13203
+ * {@inheritdoc}
13204
+ */
13205
+ public function read(CompositeConnectionInterface $connection)
13206
+ {
13207
+ $header = $connection->readLine();
13208
+
13209
+ if ($header === '') {
13210
+ $this->onProtocolError($connection, 'Unexpected empty reponse header.');
13211
+ }
13212
+
13213
+ $prefix = $header[0];
13214
+
13215
+ if (!isset($this->handlers[$prefix])) {
13216
+ $this->onProtocolError($connection, "Unknown response prefix: '$prefix'.");
13217
+ }
13218
+
13219
+ $payload = $this->handlers[$prefix]->handle($connection, substr($header, 1));
13220
+
13221
+ return $payload;
13222
+ }
13223
+
13224
+ /**
13225
+ * Handles protocol errors generated while reading responses from a
13226
+ * connection.
13227
+ *
13228
+ * @param CompositeConnectionInterface $connection Redis connection that generated the error.
13229
+ * @param string $message Error message.
13230
+ */
13231
+ protected function onProtocolError(CompositeConnectionInterface $connection, $message)
13232
+ {
13233
+ CommunicationException::handle(
13234
+ new ProtocolException($connection, $message)
13235
+ );
13236
+ }
13237
+ }
13238
+
13239
+ /**
13240
+ * Request serializer for the standard Redis wire protocol.
13241
+ *
13242
+ * @link http://redis.io/topics/protocol
13243
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13244
+ */
13245
+ class RequestSerializer implements RequestSerializerInterface
13246
+ {
13247
+ /**
13248
+ * {@inheritdoc}
13249
+ */
13250
+ public function serialize(CommandInterface $command)
13251
+ {
13252
+ $commandID = $command->getId();
13253
+ $arguments = $command->getArguments();
13254
+
13255
+ $cmdlen = strlen($commandID);
13256
+ $reqlen = count($arguments) + 1;
13257
+
13258
+ $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
13259
+
13260
+ for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
13261
+ $argument = $arguments[$i];
13262
+ $arglen = strlen($argument);
13263
+ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
13264
+ }
13265
+
13266
+ return $buffer;
13267
+ }
13268
+ }
13269
+
13270
+ /**
13271
+ * Protocol processor for the standard Redis wire protocol.
13272
+ *
13273
+ * @link http://redis.io/topics/protocol
13274
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13275
+ */
13276
+ class ProtocolProcessor implements ProtocolProcessorInterface
13277
+ {
13278
+ protected $mbiterable;
13279
+ protected $serializer;
13280
+
13281
+ /**
13282
+ *
13283
+ */
13284
+ public function __construct()
13285
+ {
13286
+ $this->mbiterable = false;
13287
+ $this->serializer = new RequestSerializer();
13288
+ }
13289
+
13290
+ /**
13291
+ * {@inheritdoc}
13292
+ */
13293
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command)
13294
+ {
13295
+ $request = $this->serializer->serialize($command);
13296
+ $connection->writeBuffer($request);
13297
+ }
13298
+
13299
+ /**
13300
+ * {@inheritdoc}
13301
+ */
13302
+ public function read(CompositeConnectionInterface $connection)
13303
+ {
13304
+ $chunk = $connection->readLine();
13305
+ $prefix = $chunk[0];
13306
+ $payload = substr($chunk, 1);
13307
+
13308
+ switch ($prefix) {
13309
+ case '+':
13310
+ return new StatusResponse($payload);
13311
+
13312
+ case '$':
13313
+ $size = (int) $payload;
13314
+ if ($size === -1) {
13315
+ return null;
13316
+ }
13317
+
13318
+ return substr($connection->readBuffer($size + 2), 0, -2);
13319
+
13320
+ case '*':
13321
+ $count = (int) $payload;
13322
+
13323
+ if ($count === -1) {
13324
+ return null;
13325
+ }
13326
+ if ($this->mbiterable) {
13327
+ return new MultiBulkIterator($connection, $count);
13328
+ }
13329
+
13330
+ $multibulk = array();
13331
+
13332
+ for ($i = 0; $i < $count; $i++) {
13333
+ $multibulk[$i] = $this->read($connection);
13334
+ }
13335
+
13336
+ return $multibulk;
13337
+
13338
+ case ':':
13339
+ return (int) $payload;
13340
+
13341
+ case '-':
13342
+ return new ErrorResponse($payload);
13343
+
13344
+ default:
13345
+ CommunicationException::handle(new ProtocolException(
13346
+ $connection, "Unknown response prefix: '$prefix'."
13347
+ ));
13348
+
13349
+ return;
13350
+ }
13351
+ }
13352
+
13353
+ /**
13354
+ * Enables or disables returning multibulk responses as specialized PHP
13355
+ * iterators used to stream bulk elements of a multibulk response instead
13356
+ * returning a plain array.
13357
+ *
13358
+ * Streamable multibulk responses are not globally supported by the
13359
+ * abstractions built-in into Predis, such as transactions or pipelines.
13360
+ * Use them with care!
13361
+ *
13362
+ * @param bool $value Enable or disable streamable multibulk responses.
13363
+ */
13364
+ public function useIterableMultibulk($value)
13365
+ {
13366
+ $this->mbiterable = (bool) $value;
13367
+ }
13368
+ }
13369
+
13370
+ /**
13371
+ * Composite protocol processor for the standard Redis wire protocol using
13372
+ * pluggable handlers to serialize requests and deserialize responses.
13373
+ *
13374
+ * @link http://redis.io/topics/protocol
13375
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13376
+ */
13377
+ class CompositeProtocolProcessor implements ProtocolProcessorInterface
13378
+ {
13379
+ /*
13380
+ * @var RequestSerializerInterface
13381
+ */
13382
+ protected $serializer;
13383
+
13384
+ /*
13385
+ * @var ResponseReaderInterface
13386
+ */
13387
+ protected $reader;
13388
+
13389
+ /**
13390
+ * @param RequestSerializerInterface $serializer Request serializer.
13391
+ * @param ResponseReaderInterface $reader Response reader.
13392
+ */
13393
+ public function __construct(
13394
+ RequestSerializerInterface $serializer = null,
13395
+ ResponseReaderInterface $reader = null
13396
+ ) {
13397
+ $this->setRequestSerializer($serializer ?: new RequestSerializer());
13398
+ $this->setResponseReader($reader ?: new ResponseReader());
13399
+ }
13400
+
13401
+ /**
13402
+ * {@inheritdoc}
13403
+ */
13404
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command)
13405
+ {
13406
+ $connection->writeBuffer($this->serializer->serialize($command));
13407
+ }
13408
+
13409
+ /**
13410
+ * {@inheritdoc}
13411
+ */
13412
+ public function read(CompositeConnectionInterface $connection)
13413
+ {
13414
+ return $this->reader->read($connection);
13415
+ }
13416
+
13417
+ /**
13418
+ * Sets the request serializer used by the protocol processor.
13419
+ *
13420
+ * @param RequestSerializerInterface $serializer Request serializer.
13421
+ */
13422
+ public function setRequestSerializer(RequestSerializerInterface $serializer)
13423
+ {
13424
+ $this->serializer = $serializer;
13425
+ }
13426
+
13427
+ /**
13428
+ * Returns the request serializer used by the protocol processor.
13429
+ *
13430
+ * @return RequestSerializerInterface
13431
+ */
13432
+ public function getRequestSerializer()
13433
+ {
13434
+ return $this->serializer;
13435
+ }
13436
+
13437
+ /**
13438
+ * Sets the response reader used by the protocol processor.
13439
+ *
13440
+ * @param ResponseReaderInterface $reader Response reader.
13441
+ */
13442
+ public function setResponseReader(ResponseReaderInterface $reader)
13443
+ {
13444
+ $this->reader = $reader;
13445
+ }
13446
+
13447
+ /**
13448
+ * Returns the Response reader used by the protocol processor.
13449
+ *
13450
+ * @return ResponseReaderInterface
13451
+ */
13452
+ public function getResponseReader()
13453
+ {
13454
+ return $this->reader;
13455
+ }
13456
+ }
13457
+
13458
+ /* --------------------------------------------------------------------------- */
13459
+
13460
+ namespace Predis\PubSub;
13461
+
13462
+ use Iterator;
13463
+ use Predis\ClientException;
13464
+ use Predis\ClientInterface;
13465
+ use Predis\Command\Command;
13466
+ use Predis\NotSupportedException;
13467
+ use Predis\Connection\AggregateConnectionInterface;
13468
+ use InvalidArgumentException;
13469
+
13470
+ /**
13471
+ * Base implementation of a PUB/SUB consumer abstraction based on PHP iterators.
13472
+ *
13473
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13474
+ */
13475
+ abstract class AbstractConsumer implements Iterator
13476
+ {
13477
+ const SUBSCRIBE = 'subscribe';
13478
+ const UNSUBSCRIBE = 'unsubscribe';
13479
+ const PSUBSCRIBE = 'psubscribe';
13480
+ const PUNSUBSCRIBE = 'punsubscribe';
13481
+ const MESSAGE = 'message';
13482
+ const PMESSAGE = 'pmessage';
13483
+ const PONG = 'pong';
13484
+
13485
+ const STATUS_VALID = 1; // 0b0001
13486
+ const STATUS_SUBSCRIBED = 2; // 0b0010
13487
+ const STATUS_PSUBSCRIBED = 4; // 0b0100
13488
+
13489
+ private $position = null;
13490
+ private $statusFlags = self::STATUS_VALID;
13491
+
13492
+ /**
13493
+ * Automatically stops the consumer when the garbage collector kicks in.
13494
+ */
13495
+ public function __destruct()
13496
+ {
13497
+ $this->stop(true);
13498
+ }
13499
+
13500
+ /**
13501
+ * Checks if the specified flag is valid based on the state of the consumer.
13502
+ *
13503
+ * @param int $value Flag.
13504
+ *
13505
+ * @return bool
13506
+ */
13507
+ protected function isFlagSet($value)
13508
+ {
13509
+ return ($this->statusFlags & $value) === $value;
13510
+ }
13511
+
13512
+ /**
13513
+ * Subscribes to the specified channels.
13514
+ *
13515
+ * @param mixed $channel,... One or more channel names.
13516
+ */
13517
+ public function subscribe($channel /*, ... */)
13518
+ {
13519
+ $this->writeRequest(self::SUBSCRIBE, func_get_args());
13520
+ $this->statusFlags |= self::STATUS_SUBSCRIBED;
13521
+ }
13522
+
13523
+ /**
13524
+ * Unsubscribes from the specified channels.
13525
+ *
13526
+ * @param string ... One or more channel names.
13527
+ */
13528
+ public function unsubscribe(/* ... */)
13529
+ {
13530
+ $this->writeRequest(self::UNSUBSCRIBE, func_get_args());
13531
+ }
13532
+
13533
+ /**
13534
+ * Subscribes to the specified channels using a pattern.
13535
+ *
13536
+ * @param mixed $pattern,... One or more channel name patterns.
13537
+ */
13538
+ public function psubscribe($pattern /* ... */)
13539
+ {
13540
+ $this->writeRequest(self::PSUBSCRIBE, func_get_args());
13541
+ $this->statusFlags |= self::STATUS_PSUBSCRIBED;
13542
+ }
13543
+
13544
+ /**
13545
+ * Unsubscribes from the specified channels using a pattern.
13546
+ *
13547
+ * @param string ... One or more channel name patterns.
13548
+ */
13549
+ public function punsubscribe(/* ... */)
13550
+ {
13551
+ $this->writeRequest(self::PUNSUBSCRIBE, func_get_args());
13552
+ }
13553
+
13554
+ /**
13555
+ * PING the server with an optional payload that will be echoed as a
13556
+ * PONG message in the pub/sub loop.
13557
+ *
13558
+ * @param string $payload Optional PING payload.
13559
+ */
13560
+ public function ping($payload = null)
13561
+ {
13562
+ $this->writeRequest('PING', array($payload));
13563
+ }
13564
+
13565
+ /**
13566
+ * Closes the context by unsubscribing from all the subscribed channels. The
13567
+ * context can be forcefully closed by dropping the underlying connection.
13568
+ *
13569
+ * @param bool $drop Indicates if the context should be closed by dropping the connection.
13570
+ *
13571
+ * @return bool Returns false when there are no pending messages.
13572
+ */
13573
+ public function stop($drop = false)
13574
+ {
13575
+ if (!$this->valid()) {
13576
+ return false;
13577
+ }
13578
+
13579
+ if ($drop) {
13580
+ $this->invalidate();
13581
+ $this->disconnect();
13582
+ } else {
13583
+ if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) {
13584
+ $this->unsubscribe();
13585
+ }
13586
+ if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) {
13587
+ $this->punsubscribe();
13588
+ }
13589
+ }
13590
+
13591
+ return !$drop;
13592
+ }
13593
+
13594
+ /**
13595
+ * Closes the underlying connection when forcing a disconnection.
13596
+ */
13597
+ abstract protected function disconnect();
13598
+
13599
+ /**
13600
+ * Writes a Redis command on the underlying connection.
13601
+ *
13602
+ * @param string $method Command ID.
13603
+ * @param array $arguments Arguments for the command.
13604
+ */
13605
+ abstract protected function writeRequest($method, $arguments);
13606
+
13607
+ /**
13608
+ * {@inheritdoc}
13609
+ */
13610
+ public function rewind()
13611
+ {
13612
+ // NOOP
13613
+ }
13614
+
13615
+ /**
13616
+ * Returns the last message payload retrieved from the server and generated
13617
+ * by one of the active subscriptions.
13618
+ *
13619
+ * @return array
13620
+ */
13621
+ public function current()
13622
+ {
13623
+ return $this->getValue();
13624
+ }
13625
+
13626
+ /**
13627
+ * {@inheritdoc}
13628
+ */
13629
+ public function key()
13630
+ {
13631
+ return $this->position;
13632
+ }
13633
+
13634
+ /**
13635
+ * {@inheritdoc}
13636
+ */
13637
+ public function next()
13638
+ {
13639
+ if ($this->valid()) {
13640
+ $this->position++;
13641
+ }
13642
+
13643
+ return $this->position;
13644
+ }
13645
+
13646
+ /**
13647
+ * Checks if the the consumer is still in a valid state to continue.
13648
+ *
13649
+ * @return bool
13650
+ */
13651
+ public function valid()
13652
+ {
13653
+ $isValid = $this->isFlagSet(self::STATUS_VALID);
13654
+ $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED;
13655
+ $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0;
13656
+
13657
+ return $isValid && $hasSubscriptions;
13658
+ }
13659
+
13660
+ /**
13661
+ * Resets the state of the consumer.
13662
+ */
13663
+ protected function invalidate()
13664
+ {
13665
+ $this->statusFlags = 0; // 0b0000;
13666
+ }
13667
+
13668
+ /**
13669
+ * Waits for a new message from the server generated by one of the active
13670
+ * subscriptions and returns it when available.
13671
+ *
13672
+ * @return array
13673
+ */
13674
+ abstract protected function getValue();
13675
+ }
13676
+
13677
+ /**
13678
+ * Method-dispatcher loop built around the client-side abstraction of a Redis
13679
+ * PUB / SUB context.
13680
+ *
13681
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13682
+ */
13683
+ class DispatcherLoop
13684
+ {
13685
+ private $pubsub;
13686
+
13687
+ protected $callbacks;
13688
+ protected $defaultCallback;
13689
+ protected $subscriptionCallback;
13690
+
13691
+ /**
13692
+ * @param Consumer $pubsub PubSub consumer instance used by the loop.
13693
+ */
13694
+ public function __construct(Consumer $pubsub)
13695
+ {
13696
+ $this->callbacks = array();
13697
+ $this->pubsub = $pubsub;
13698
+ }
13699
+
13700
+ /**
13701
+ * Checks if the passed argument is a valid callback.
13702
+ *
13703
+ * @param mixed $callable A callback.
13704
+ */
13705
+ protected function assertCallback($callable)
13706
+ {
13707
+ if (!is_callable($callable)) {
13708
+ throw new InvalidArgumentException('The given argument must be a callable object.');
13709
+ }
13710
+ }
13711
+
13712
+ /**
13713
+ * Returns the underlying PUB / SUB context.
13714
+ *
13715
+ * @return Consumer
13716
+ */
13717
+ public function getPubSubConsumer()
13718
+ {
13719
+ return $this->pubsub;
13720
+ }
13721
+
13722
+ /**
13723
+ * Sets a callback that gets invoked upon new subscriptions.
13724
+ *
13725
+ * @param mixed $callable A callback.
13726
+ */
13727
+ public function subscriptionCallback($callable = null)
13728
+ {
13729
+ if (isset($callable)) {
13730
+ $this->assertCallback($callable);
13731
+ }
13732
+
13733
+ $this->subscriptionCallback = $callable;
13734
+ }
13735
+
13736
+ /**
13737
+ * Sets a callback that gets invoked when a message is received on a
13738
+ * channel that does not have an associated callback.
13739
+ *
13740
+ * @param mixed $callable A callback.
13741
+ */
13742
+ public function defaultCallback($callable = null)
13743
+ {
13744
+ if (isset($callable)) {
13745
+ $this->assertCallback($callable);
13746
+ }
13747
+
13748
+ $this->subscriptionCallback = $callable;
13749
+ }
13750
+
13751
+ /**
13752
+ * Binds a callback to a channel.
13753
+ *
13754
+ * @param string $channel Channel name.
13755
+ * @param Callable $callback A callback.
13756
+ */
13757
+ public function attachCallback($channel, $callback)
13758
+ {
13759
+ $callbackName = $this->getPrefixKeys() . $channel;
13760
+
13761
+ $this->assertCallback($callback);
13762
+ $this->callbacks[$callbackName] = $callback;
13763
+ $this->pubsub->subscribe($channel);
13764
+ }
13765
+
13766
+ /**
13767
+ * Stops listening to a channel and removes the associated callback.
13768
+ *
13769
+ * @param string $channel Redis channel.
13770
+ */
13771
+ public function detachCallback($channel)
13772
+ {
13773
+ $callbackName = $this->getPrefixKeys() . $channel;
13774
+
13775
+ if (isset($this->callbacks[$callbackName])) {
13776
+ unset($this->callbacks[$callbackName]);
13777
+ $this->pubsub->unsubscribe($channel);
13778
+ }
13779
+ }
13780
+
13781
+ /**
13782
+ * Starts the dispatcher loop.
13783
+ */
13784
+ public function run()
13785
+ {
13786
+ foreach ($this->pubsub as $message) {
13787
+ $kind = $message->kind;
13788
+
13789
+ if ($kind !== Consumer::MESSAGE && $kind !== Consumer::PMESSAGE) {
13790
+ if (isset($this->subscriptionCallback)) {
13791
+ $callback = $this->subscriptionCallback;
13792
+ call_user_func($callback, $message);
13793
+ }
13794
+
13795
+ continue;
13796
+ }
13797
+
13798
+ if (isset($this->callbacks[$message->channel])) {
13799
+ $callback = $this->callbacks[$message->channel];
13800
+ call_user_func($callback, $message->payload);
13801
+ } elseif (isset($this->defaultCallback)) {
13802
+ $callback = $this->defaultCallback;
13803
+ call_user_func($callback, $message);
13804
+ }
13805
+ }
13806
+ }
13807
+
13808
+ /**
13809
+ * Terminates the dispatcher loop.
13810
+ */
13811
+ public function stop()
13812
+ {
13813
+ $this->pubsub->stop();
13814
+ }
13815
+
13816
+ /**
13817
+ * Return the prefix used for keys
13818
+ *
13819
+ * @return string
13820
+ */
13821
+ protected function getPrefixKeys()
13822
+ {
13823
+ $options = $this->pubsub->getClient()->getOptions();
13824
+
13825
+ if (isset($options->prefix)) {
13826
+ return $options->prefix->getPrefix();
13827
+ }
13828
+
13829
+ return '';
13830
+ }
13831
+ }
13832
+
13833
+ /**
13834
+ * PUB/SUB consumer abstraction.
13835
+ *
13836
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13837
+ */
13838
+ class Consumer extends AbstractConsumer
13839
+ {
13840
+ private $client;
13841
+ private $options;
13842
+
13843
+ /**
13844
+ * @param ClientInterface $client Client instance used by the consumer.
13845
+ * @param array $options Options for the consumer initialization.
13846
+ */
13847
+ public function __construct(ClientInterface $client, array $options = null)
13848
+ {
13849
+ $this->checkCapabilities($client);
13850
+
13851
+ $this->options = $options ?: array();
13852
+ $this->client = $client;
13853
+
13854
+ $this->genericSubscribeInit('subscribe');
13855
+ $this->genericSubscribeInit('psubscribe');
13856
+ }
13857
+
13858
+ /**
13859
+ * Returns the underlying client instance used by the pub/sub iterator.
13860
+ *
13861
+ * @return ClientInterface
13862
+ */
13863
+ public function getClient()
13864
+ {
13865
+ return $this->client;
13866
+ }
13867
+
13868
+ /**
13869
+ * Checks if the client instance satisfies the required conditions needed to
13870
+ * initialize a PUB/SUB consumer.
13871
+ *
13872
+ * @param ClientInterface $client Client instance used by the consumer.
13873
+ *
13874
+ * @throws NotSupportedException
13875
+ */
13876
+ private function checkCapabilities(ClientInterface $client)
13877
+ {
13878
+ if ($client->getConnection() instanceof AggregateConnectionInterface) {
13879
+ throw new NotSupportedException(
13880
+ 'Cannot initialize a PUB/SUB consumer over aggregate connections.'
13881
+ );
13882
+ }
13883
+
13884
+ $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
13885
+
13886
+ if ($client->getProfile()->supportsCommands($commands) === false) {
13887
+ throw new NotSupportedException(
13888
+ 'The current profile does not support PUB/SUB related commands.'
13889
+ );
13890
+ }
13891
+ }
13892
+
13893
+ /**
13894
+ * This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE.
13895
+ *
13896
+ * @param string $subscribeAction Type of subscription.
13897
+ */
13898
+ private function genericSubscribeInit($subscribeAction)
13899
+ {
13900
+ if (isset($this->options[$subscribeAction])) {
13901
+ $this->$subscribeAction($this->options[$subscribeAction]);
13902
+ }
13903
+ }
13904
+
13905
+ /**
13906
+ * {@inheritdoc}
13907
+ */
13908
+ protected function writeRequest($method, $arguments)
13909
+ {
13910
+ $this->client->getConnection()->writeRequest(
13911
+ $this->client->createCommand($method,
13912
+ Command::normalizeArguments($arguments)
13913
+ )
13914
+ );
13915
+ }
13916
+
13917
+ /**
13918
+ * {@inheritdoc}
13919
+ */
13920
+ protected function disconnect()
13921
+ {
13922
+ $this->client->disconnect();
13923
+ }
13924
+
13925
+ /**
13926
+ * {@inheritdoc}
13927
+ */
13928
+ protected function getValue()
13929
+ {
13930
+ $response = $this->client->getConnection()->read();
13931
+
13932
+ switch ($response[0]) {
13933
+ case self::SUBSCRIBE:
13934
+ case self::UNSUBSCRIBE:
13935
+ case self::PSUBSCRIBE:
13936
+ case self::PUNSUBSCRIBE:
13937
+ if ($response[2] === 0) {
13938
+ $this->invalidate();
13939
+ }
13940
+ // The missing break here is intentional as we must process
13941
+ // subscriptions and unsubscriptions as standard messages.
13942
+ // no break
13943
+
13944
+ case self::MESSAGE:
13945
+ return (object) array(
13946
+ 'kind' => $response[0],
13947
+ 'channel' => $response[1],
13948
+ 'payload' => $response[2],
13949
+ );
13950
+
13951
+ case self::PMESSAGE:
13952
+ return (object) array(
13953
+ 'kind' => $response[0],
13954
+ 'pattern' => $response[1],
13955
+ 'channel' => $response[2],
13956
+ 'payload' => $response[3],
13957
+ );
13958
+
13959
+ case self::PONG:
13960
+ return (object) array(
13961
+ 'kind' => $response[0],
13962
+ 'payload' => $response[1],
13963
+ );
13964
+
13965
+ default:
13966
+ throw new ClientException(
13967
+ "Unknown message type '{$response[0]}' received in the PUB/SUB context."
13968
+ );
13969
+ }
13970
+ }
13971
+ }
13972
+
13973
+ /* --------------------------------------------------------------------------- */
13974
+
13975
+ namespace Predis\Transaction;
13976
+
13977
+ use Predis\PredisException;
13978
+ use Exception;
13979
+ use InvalidArgumentException;
13980
+ use SplQueue;
13981
+ use Predis\ClientContextInterface;
13982
+ use Predis\ClientException;
13983
+ use Predis\ClientInterface;
13984
+ use Predis\CommunicationException;
13985
+ use Predis\NotSupportedException;
13986
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
13987
+ use Predis\Response\ServerException;
13988
+ use Predis\Response\Status as StatusResponse;
13989
+ use Predis\Command\CommandInterface;
13990
+ use Predis\Connection\AggregateConnectionInterface;
13991
+ use Predis\Protocol\ProtocolException;
13992
+
13993
+ /**
13994
+ * Utility class used to track the state of a MULTI / EXEC transaction.
13995
+ *
13996
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13997
+ */
13998
+ class MultiExecState
13999
+ {
14000
+ const INITIALIZED = 1; // 0b00001
14001
+ const INSIDEBLOCK = 2; // 0b00010
14002
+ const DISCARDED = 4; // 0b00100
14003
+ const CAS = 8; // 0b01000
14004
+ const WATCH = 16; // 0b10000
14005
+
14006
+ private $flags;
14007
+
14008
+ /**
14009
+ *
14010
+ */
14011
+ public function __construct()
14012
+ {
14013
+ $this->flags = 0;
14014
+ }
14015
+
14016
+ /**
14017
+ * Sets the internal state flags.
14018
+ *
14019
+ * @param int $flags Set of flags
14020
+ */
14021
+ public function set($flags)
14022
+ {
14023
+ $this->flags = $flags;
14024
+ }
14025
+
14026
+ /**
14027
+ * Gets the internal state flags.
14028
+ *
14029
+ * @return int
14030
+ */
14031
+ public function get()
14032
+ {
14033
+ return $this->flags;
14034
+ }
14035
+
14036
+ /**
14037
+ * Sets one or more flags.
14038
+ *
14039
+ * @param int $flags Set of flags
14040
+ */
14041
+ public function flag($flags)
14042
+ {
14043
+ $this->flags |= $flags;
14044
+ }
14045
+
14046
+ /**
14047
+ * Resets one or more flags.
14048
+ *
14049
+ * @param int $flags Set of flags
14050
+ */
14051
+ public function unflag($flags)
14052
+ {
14053
+ $this->flags &= ~$flags;
14054
+ }
14055
+
14056
+ /**
14057
+ * Returns if the specified flag or set of flags is set.
14058
+ *
14059
+ * @param int $flags Flag
14060
+ *
14061
+ * @return bool
14062
+ */
14063
+ public function check($flags)
14064
+ {
14065
+ return ($this->flags & $flags) === $flags;
14066
+ }
14067
+
14068
+ /**
14069
+ * Resets the state of a transaction.
14070
+ */
14071
+ public function reset()
14072
+ {
14073
+ $this->flags = 0;
14074
+ }
14075
+
14076
+ /**
14077
+ * Returns the state of the RESET flag.
14078
+ *
14079
+ * @return bool
14080
+ */
14081
+ public function isReset()
14082
+ {
14083
+ return $this->flags === 0;
14084
+ }
14085
+
14086
+ /**
14087
+ * Returns the state of the INITIALIZED flag.
14088
+ *
14089
+ * @return bool
14090
+ */
14091
+ public function isInitialized()
14092
+ {
14093
+ return $this->check(self::INITIALIZED);
14094
+ }
14095
+
14096
+ /**
14097
+ * Returns the state of the INSIDEBLOCK flag.
14098
+ *
14099
+ * @return bool
14100
+ */
14101
+ public function isExecuting()
14102
+ {
14103
+ return $this->check(self::INSIDEBLOCK);
14104
+ }
14105
+
14106
+ /**
14107
+ * Returns the state of the CAS flag.
14108
+ *
14109
+ * @return bool
14110
+ */
14111
+ public function isCAS()
14112
+ {
14113
+ return $this->check(self::CAS);
14114
+ }
14115
+
14116
+ /**
14117
+ * Returns if WATCH is allowed in the current state.
14118
+ *
14119
+ * @return bool
14120
+ */
14121
+ public function isWatchAllowed()
14122
+ {
14123
+ return $this->check(self::INITIALIZED) && !$this->check(self::CAS);
14124
+ }
14125
+
14126
+ /**
14127
+ * Returns the state of the WATCH flag.
14128
+ *
14129
+ * @return bool
14130
+ */
14131
+ public function isWatching()
14132
+ {
14133
+ return $this->check(self::WATCH);
14134
+ }
14135
+
14136
+ /**
14137
+ * Returns the state of the DISCARDED flag.
14138
+ *
14139
+ * @return bool
14140
+ */
14141
+ public function isDiscarded()
14142
+ {
14143
+ return $this->check(self::DISCARDED);
14144
+ }
14145
+ }
14146
+
14147
+ /**
14148
+ * Client-side abstraction of a Redis transaction based on MULTI / EXEC.
14149
+ *
14150
+ * {@inheritdoc}
14151
+ *
14152
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14153
+ */
14154
+ class MultiExec implements ClientContextInterface
14155
+ {
14156
+ private $state;
14157
+
14158
+ protected $client;
14159
+ protected $commands;
14160
+ protected $exceptions = true;
14161
+ protected $attempts = 0;
14162
+ protected $watchKeys = array();
14163
+ protected $modeCAS = false;
14164
+
14165
+ /**
14166
+ * @param ClientInterface $client Client instance used by the transaction.
14167
+ * @param array $options Initialization options.
14168
+ */
14169
+ public function __construct(ClientInterface $client, array $options = null)
14170
+ {
14171
+ $this->assertClient($client);
14172
+
14173
+ $this->client = $client;
14174
+ $this->state = new MultiExecState();
14175
+
14176
+ $this->configure($client, $options ?: array());
14177
+ $this->reset();
14178
+ }
14179
+
14180
+ /**
14181
+ * Checks if the passed client instance satisfies the required conditions
14182
+ * needed to initialize the transaction object.
14183
+ *
14184
+ * @param ClientInterface $client Client instance used by the transaction object.
14185
+ *
14186
+ * @throws NotSupportedException
14187
+ */
14188
+ private function assertClient(ClientInterface $client)
14189
+ {
14190
+ if ($client->getConnection() instanceof AggregateConnectionInterface) {
14191
+ throw new NotSupportedException(
14192
+ 'Cannot initialize a MULTI/EXEC transaction over aggregate connections.'
14193
+ );
14194
+ }
14195
+
14196
+ if (!$client->getProfile()->supportsCommands(array('MULTI', 'EXEC', 'DISCARD'))) {
14197
+ throw new NotSupportedException(
14198
+ 'The current profile does not support MULTI, EXEC and DISCARD.'
14199
+ );
14200
+ }
14201
+ }
14202
+
14203
+ /**
14204
+ * Configures the transaction using the provided options.
14205
+ *
14206
+ * @param ClientInterface $client Underlying client instance.
14207
+ * @param array $options Array of options for the transaction.
14208
+ **/
14209
+ protected function configure(ClientInterface $client, array $options)
14210
+ {
14211
+ if (isset($options['exceptions'])) {
14212
+ $this->exceptions = (bool) $options['exceptions'];
14213
+ } else {
14214
+ $this->exceptions = $client->getOptions()->exceptions;
14215
+ }
14216
+
14217
+ if (isset($options['cas'])) {
14218
+ $this->modeCAS = (bool) $options['cas'];
14219
+ }
14220
+
14221
+ if (isset($options['watch']) && $keys = $options['watch']) {
14222
+ $this->watchKeys = $keys;
14223
+ }
14224
+
14225
+ if (isset($options['retry'])) {
14226
+ $this->attempts = (int) $options['retry'];
14227
+ }
14228
+ }
14229
+
14230
+ /**
14231
+ * Resets the state of the transaction.
14232
+ */
14233
+ protected function reset()
14234
+ {
14235
+ $this->state->reset();
14236
+ $this->commands = new SplQueue();
14237
+ }
14238
+
14239
+ /**
14240
+ * Initializes the transaction context.
14241
+ */
14242
+ protected function initialize()
14243
+ {
14244
+ if ($this->state->isInitialized()) {
14245
+ return;
14246
+ }
14247
+
14248
+ if ($this->modeCAS) {
14249
+ $this->state->flag(MultiExecState::CAS);
14250
+ }
14251
+
14252
+ if ($this->watchKeys) {
14253
+ $this->watch($this->watchKeys);
14254
+ }
14255
+
14256
+ $cas = $this->state->isCAS();
14257
+ $discarded = $this->state->isDiscarded();
14258
+
14259
+ if (!$cas || ($cas && $discarded)) {
14260
+ $this->call('MULTI');
14261
+
14262
+ if ($discarded) {
14263
+ $this->state->unflag(MultiExecState::CAS);
14264
+ }
14265
+ }
14266
+
14267
+ $this->state->unflag(MultiExecState::DISCARDED);
14268
+ $this->state->flag(MultiExecState::INITIALIZED);
14269
+ }
14270
+
14271
+ /**
14272
+ * Dynamically invokes a Redis command with the specified arguments.
14273
+ *
14274
+ * @param string $method Command ID.
14275
+ * @param array $arguments Arguments for the command.
14276
+ *
14277
+ * @return mixed
14278
+ */
14279
+ public function __call($method, $arguments)
14280
+ {
14281
+ return $this->executeCommand(
14282
+ $this->client->createCommand($method, $arguments)
14283
+ );
14284
+ }
14285
+
14286
+ /**
14287
+ * Executes a Redis command bypassing the transaction logic.
14288
+ *
14289
+ * @param string $commandID Command ID.
14290
+ * @param array $arguments Arguments for the command.
14291
+ *
14292
+ * @return mixed
14293
+ *
14294
+ * @throws ServerException
14295
+ */
14296
+ protected function call($commandID, array $arguments = array())
14297
+ {
14298
+ $response = $this->client->executeCommand(
14299
+ $this->client->createCommand($commandID, $arguments)
14300
+ );
14301
+
14302
+ if ($response instanceof ErrorResponseInterface) {
14303
+ throw new ServerException($response->getMessage());
14304
+ }
14305
+
14306
+ return $response;
14307
+ }
14308
+
14309
+ /**
14310
+ * Executes the specified Redis command.
14311
+ *
14312
+ * @param CommandInterface $command Command instance.
14313
+ *
14314
+ * @return $this|mixed
14315
+ *
14316
+ * @throws AbortedMultiExecException
14317
+ * @throws CommunicationException
14318
+ */
14319
+ public function executeCommand(CommandInterface $command)
14320
+ {
14321
+ $this->initialize();
14322
+
14323
+ if ($this->state->isCAS()) {
14324
+ return $this->client->executeCommand($command);
14325
+ }
14326
+
14327
+ $response = $this->client->getConnection()->executeCommand($command);
14328
+
14329
+ if ($response instanceof StatusResponse && $response == 'QUEUED') {
14330
+ $this->commands->enqueue($command);
14331
+ } elseif ($response instanceof ErrorResponseInterface) {
14332
+ throw new AbortedMultiExecException($this, $response->getMessage());
14333
+ } else {
14334
+ $this->onProtocolError('The server did not return a +QUEUED status response.');
14335
+ }
14336
+
14337
+ return $this;
14338
+ }
14339
+
14340
+ /**
14341
+ * Executes WATCH against one or more keys.
14342
+ *
14343
+ * @param string|array $keys One or more keys.
14344
+ *
14345
+ * @return mixed
14346
+ *
14347
+ * @throws NotSupportedException
14348
+ * @throws ClientException
14349
+ */
14350
+ public function watch($keys)
14351
+ {
14352
+ if (!$this->client->getProfile()->supportsCommand('WATCH')) {
14353
+ throw new NotSupportedException('WATCH is not supported by the current profile.');
14354
+ }
14355
+
14356
+ if ($this->state->isWatchAllowed()) {
14357
+ throw new ClientException('Sending WATCH after MULTI is not allowed.');
14358
+ }
14359
+
14360
+ $response = $this->call('WATCH', is_array($keys) ? $keys : array($keys));
14361
+ $this->state->flag(MultiExecState::WATCH);
14362
+
14363
+ return $response;
14364
+ }
14365
+
14366
+ /**
14367
+ * Finalizes the transaction by executing MULTI on the server.
14368
+ *
14369
+ * @return MultiExec
14370
+ */
14371
+ public function multi()
14372
+ {
14373
+ if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) {
14374
+ $this->state->unflag(MultiExecState::CAS);
14375
+ $this->call('MULTI');
14376
+ } else {
14377
+ $this->initialize();
14378
+ }
14379
+
14380
+ return $this;
14381
+ }
14382
+
14383
+ /**
14384
+ * Executes UNWATCH.
14385
+ *
14386
+ * @return MultiExec
14387
+ *
14388
+ * @throws NotSupportedException
14389
+ */
14390
+ public function unwatch()
14391
+ {
14392
+ if (!$this->client->getProfile()->supportsCommand('UNWATCH')) {
14393
+ throw new NotSupportedException(
14394
+ 'UNWATCH is not supported by the current profile.'
14395
+ );
14396
+ }
14397
+
14398
+ $this->state->unflag(MultiExecState::WATCH);
14399
+ $this->__call('UNWATCH', array());
14400
+
14401
+ return $this;
14402
+ }
14403
+
14404
+ /**
14405
+ * Resets the transaction by UNWATCH-ing the keys that are being WATCHed and
14406
+ * DISCARD-ing pending commands that have been already sent to the server.
14407
+ *
14408
+ * @return MultiExec
14409
+ */
14410
+ public function discard()
14411
+ {
14412
+ if ($this->state->isInitialized()) {
14413
+ $this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD');
14414
+
14415
+ $this->reset();
14416
+ $this->state->flag(MultiExecState::DISCARDED);
14417
+ }
14418
+
14419
+ return $this;
14420
+ }
14421
+
14422
+ /**
14423
+ * Executes the whole transaction.
14424
+ *
14425
+ * @return mixed
14426
+ */
14427
+ public function exec()
14428
+ {
14429
+ return $this->execute();
14430
+ }
14431
+
14432
+ /**
14433
+ * Checks the state of the transaction before execution.
14434
+ *
14435
+ * @param mixed $callable Callback for execution.
14436
+ *
14437
+ * @throws InvalidArgumentException
14438
+ * @throws ClientException
14439
+ */
14440
+ private function checkBeforeExecution($callable)
14441
+ {
14442
+ if ($this->state->isExecuting()) {
14443
+ throw new ClientException(
14444
+ 'Cannot invoke "execute" or "exec" inside an active transaction context.'
14445
+ );
14446
+ }
14447
+
14448
+ if ($callable) {
14449
+ if (!is_callable($callable)) {
14450
+ throw new InvalidArgumentException('The argument must be a callable object.');
14451
+ }
14452
+
14453
+ if (!$this->commands->isEmpty()) {
14454
+ $this->discard();
14455
+
14456
+ throw new ClientException(
14457
+ 'Cannot execute a transaction block after using fluent interface.'
14458
+ );
14459
+ }
14460
+ } elseif ($this->attempts) {
14461
+ $this->discard();
14462
+
14463
+ throw new ClientException(
14464
+ 'Automatic retries are supported only when a callable block is provided.'
14465
+ );
14466
+ }
14467
+ }
14468
+
14469
+ /**
14470
+ * Handles the actual execution of the whole transaction.
14471
+ *
14472
+ * @param mixed $callable Optional callback for execution.
14473
+ *
14474
+ * @return array
14475
+ *
14476
+ * @throws CommunicationException
14477
+ * @throws AbortedMultiExecException
14478
+ * @throws ServerException
14479
+ */
14480
+ public function execute($callable = null)
14481
+ {
14482
+ $this->checkBeforeExecution($callable);
14483
+
14484
+ $execResponse = null;
14485
+ $attempts = $this->attempts;
14486
+
14487
+ do {
14488
+ if ($callable) {
14489
+ $this->executeTransactionBlock($callable);
14490
+ }
14491
+
14492
+ if ($this->commands->isEmpty()) {
14493
+ if ($this->state->isWatching()) {
14494
+ $this->discard();
14495
+ }
14496
+
14497
+ return null;
14498
+ }
14499
+
14500
+ $execResponse = $this->call('EXEC');
14501
+
14502
+ if ($execResponse === null) {
14503
+ if ($attempts === 0) {
14504
+ throw new AbortedMultiExecException(
14505
+ $this, 'The current transaction has been aborted by the server.'
14506
+ );
14507
+ }
14508
+
14509
+ $this->reset();
14510
+
14511
+ continue;
14512
+ }
14513
+
14514
+ break;
14515
+ } while ($attempts-- > 0);
14516
+
14517
+ $response = array();
14518
+ $commands = $this->commands;
14519
+ $size = count($execResponse);
14520
+
14521
+ if ($size !== count($commands)) {
14522
+ $this->onProtocolError('EXEC returned an unexpected number of response items.');
14523
+ }
14524
+
14525
+ for ($i = 0; $i < $size; $i++) {
14526
+ $cmdResponse = $execResponse[$i];
14527
+
14528
+ if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) {
14529
+ throw new ServerException($cmdResponse->getMessage());
14530
+ }
14531
+
14532
+ $response[$i] = $commands->dequeue()->parseResponse($cmdResponse);
14533
+ }
14534
+
14535
+ return $response;
14536
+ }
14537
+
14538
+ /**
14539
+ * Passes the current transaction object to a callable block for execution.
14540
+ *
14541
+ * @param mixed $callable Callback.
14542
+ *
14543
+ * @throws CommunicationException
14544
+ * @throws ServerException
14545
+ */
14546
+ protected function executeTransactionBlock($callable)
14547
+ {
14548
+ $exception = null;
14549
+ $this->state->flag(MultiExecState::INSIDEBLOCK);
14550
+
14551
+ try {
14552
+ call_user_func($callable, $this);
14553
+ } catch (CommunicationException $exception) {
14554
+ // NOOP
14555
+ } catch (ServerException $exception) {
14556
+ // NOOP
14557
+ } catch (Exception $exception) {
14558
+ $this->discard();
14559
+ }
14560
+
14561
+ $this->state->unflag(MultiExecState::INSIDEBLOCK);
14562
+
14563
+ if ($exception) {
14564
+ throw $exception;
14565
+ }
14566
+ }
14567
+
14568
+ /**
14569
+ * Helper method for protocol errors encountered inside the transaction.
14570
+ *
14571
+ * @param string $message Error message.
14572
+ */
14573
+ private function onProtocolError($message)
14574
+ {
14575
+ // Since a MULTI/EXEC block cannot be initialized when using aggregate
14576
+ // connections we can safely assume that Predis\Client::getConnection()
14577
+ // will return a Predis\Connection\NodeConnectionInterface instance.
14578
+ CommunicationException::handle(new ProtocolException(
14579
+ $this->client->getConnection(), $message
14580
+ ));
14581
+ }
14582
+ }
14583
+
14584
+ /**
14585
+ * Exception class that identifies a MULTI / EXEC transaction aborted by Redis.
14586
+ *
14587
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14588
+ */
14589
+ class AbortedMultiExecException extends PredisException
14590
+ {
14591
+ private $transaction;
14592
+
14593
+ /**
14594
+ * @param MultiExec $transaction Transaction that generated the exception.
14595
+ * @param string $message Error message.
14596
+ * @param int $code Error code.
14597
+ */
14598
+ public function __construct(MultiExec $transaction, $message, $code = null)
14599
+ {
14600
+ parent::__construct($message, $code);
14601
+ $this->transaction = $transaction;
14602
+ }
14603
+
14604
+ /**
14605
+ * Returns the transaction that generated the exception.
14606
+ *
14607
+ * @return MultiExec
14608
+ */
14609
+ public function getTransaction()
14610
+ {
14611
+ return $this->transaction;
14612
+ }
14613
+ }
14614
+
14615
+ /* --------------------------------------------------------------------------- */
14616
+
14617
+ namespace Predis\Session;
14618
+
14619
+ use SessionHandlerInterface;
14620
+ use Predis\ClientInterface;
14621
+
14622
+ /**
14623
+ * Session handler class that relies on Predis\Client to store PHP's sessions
14624
+ * data into one or multiple Redis servers.
14625
+ *
14626
+ * This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3
14627
+ * provided that a polyfill for `SessionHandlerInterface` is defined by either
14628
+ * you or an external package such as `symfony/http-foundation`.
14629
+ *
14630
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14631
+ */
14632
+ class Handler implements SessionHandlerInterface
14633
+ {
14634
+ protected $client;
14635
+ protected $ttl;
14636
+
14637
+ /**
14638
+ * @param ClientInterface $client Fully initialized client instance.
14639
+ * @param array $options Session handler options.
14640
+ */
14641
+ public function __construct(ClientInterface $client, array $options = array())
14642
+ {
14643
+ $this->client = $client;
14644
+
14645
+ if (isset($options['gc_maxlifetime'])) {
14646
+ $this->ttl = (int) $options['gc_maxlifetime'];
14647
+ } else {
14648
+ $this->ttl = ini_get('session.gc_maxlifetime');
14649
+ }
14650
+ }
14651
+
14652
+ /**
14653
+ * Registers this instance as the current session handler.
14654
+ */
14655
+ public function register()
14656
+ {
14657
+ if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
14658
+ session_set_save_handler($this, true);
14659
+ } else {
14660
+ session_set_save_handler(
14661
+ array($this, 'open'),
14662
+ array($this, 'close'),
14663
+ array($this, 'read'),
14664
+ array($this, 'write'),
14665
+ array($this, 'destroy'),
14666
+ array($this, 'gc')
14667
+ );
14668
+ }
14669
+ }
14670
+
14671
+ /**
14672
+ * {@inheritdoc}
14673
+ */
14674
+ public function open($save_path, $session_id)
14675
+ {
14676
+ // NOOP
14677
+ return true;
14678
+ }
14679
+
14680
+ /**
14681
+ * {@inheritdoc}
14682
+ */
14683
+ public function close()
14684
+ {
14685
+ // NOOP
14686
+ return true;
14687
+ }
14688
+
14689
+ /**
14690
+ * {@inheritdoc}
14691
+ */
14692
+ public function gc($maxlifetime)
14693
+ {
14694
+ // NOOP
14695
+ return true;
14696
+ }
14697
+
14698
+ /**
14699
+ * {@inheritdoc}
14700
+ */
14701
+ public function read($session_id)
14702
+ {
14703
+ if ($data = $this->client->get($session_id)) {
14704
+ return $data;
14705
+ }
14706
+
14707
+ return '';
14708
+ }
14709
+ /**
14710
+ * {@inheritdoc}
14711
+ */
14712
+ public function write($session_id, $session_data)
14713
+ {
14714
+ $this->client->setex($session_id, $this->ttl, $session_data);
14715
+
14716
+ return true;
14717
+ }
14718
+
14719
+ /**
14720
+ * {@inheritdoc}
14721
+ */
14722
+ public function destroy($session_id)
14723
+ {
14724
+ $this->client->del($session_id);
14725
+
14726
+ return true;
14727
+ }
14728
+
14729
+ /**
14730
+ * Returns the underlying client instance.
14731
+ *
14732
+ * @return ClientInterface
14733
+ */
14734
+ public function getClient()
14735
+ {
14736
+ return $this->client;
14737
+ }
14738
+
14739
+ /**
14740
+ * Returns the session max lifetime value.
14741
+ *
14742
+ * @return int
14743
+ */
14744
+ public function getMaxLifeTime()
14745
+ {
14746
+ return $this->ttl;
14747
+ }
14748
+ }
14749
+
14750
+ /* --------------------------------------------------------------------------- */
14751
+
14752
+ namespace Predis\Monitor;
14753
+
14754
+ use Iterator;
14755
+ use Predis\ClientInterface;
14756
+ use Predis\NotSupportedException;
14757
+ use Predis\Connection\AggregateConnectionInterface;
14758
+
14759
+ /**
14760
+ * Redis MONITOR consumer.
14761
+ *
14762
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14763
+ */
14764
+ class Consumer implements Iterator
14765
+ {
14766
+ private $client;
14767
+ private $valid;
14768
+ private $position;
14769
+
14770
+ /**
14771
+ * @param ClientInterface $client Client instance used by the consumer.
14772
+ */
14773
+ public function __construct(ClientInterface $client)
14774
+ {
14775
+ $this->assertClient($client);
14776
+
14777
+ $this->client = $client;
14778
+
14779
+ $this->start();
14780
+ }
14781
+
14782
+ /**
14783
+ * Automatically stops the consumer when the garbage collector kicks in.
14784
+ */
14785
+ public function __destruct()
14786
+ {
14787
+ $this->stop();
14788
+ }
14789
+
14790
+ /**
14791
+ * Checks if the passed client instance satisfies the required conditions
14792
+ * needed to initialize a monitor consumer.
14793
+ *
14794
+ * @param ClientInterface $client Client instance used by the consumer.
14795
+ *
14796
+ * @throws NotSupportedException
14797
+ */
14798
+ private function assertClient(ClientInterface $client)
14799
+ {
14800
+ if ($client->getConnection() instanceof AggregateConnectionInterface) {
14801
+ throw new NotSupportedException(
14802
+ 'Cannot initialize a monitor consumer over aggregate connections.'
14803
+ );
14804
+ }
14805
+
14806
+ if ($client->getProfile()->supportsCommand('MONITOR') === false) {
14807
+ throw new NotSupportedException("The current profile does not support 'MONITOR'.");
14808
+ }
14809
+ }
14810
+
14811
+ /**
14812
+ * Initializes the consumer and sends the MONITOR command to the server.
14813
+ */
14814
+ protected function start()
14815
+ {
14816
+ $this->client->executeCommand(
14817
+ $this->client->createCommand('MONITOR')
14818
+ );
14819
+ $this->valid = true;
14820
+ }
14821
+
14822
+ /**
14823
+ * Stops the consumer. Internally this is done by disconnecting from server
14824
+ * since there is no way to terminate the stream initialized by MONITOR.
14825
+ */
14826
+ public function stop()
14827
+ {
14828
+ $this->client->disconnect();
14829
+ $this->valid = false;
14830
+ }
14831
+
14832
+ /**
14833
+ * {@inheritdoc}
14834
+ */
14835
+ public function rewind()
14836
+ {
14837
+ // NOOP
14838
+ }
14839
+
14840
+ /**
14841
+ * Returns the last message payload retrieved from the server.
14842
+ *
14843
+ * @return Object
14844
+ */
14845
+ public function current()
14846
+ {
14847
+ return $this->getValue();
14848
+ }
14849
+
14850
+ /**
14851
+ * {@inheritdoc}
14852
+ */
14853
+ public function key()
14854
+ {
14855
+ return $this->position;
14856
+ }
14857
+
14858
+ /**
14859
+ * {@inheritdoc}
14860
+ */
14861
+ public function next()
14862
+ {
14863
+ $this->position++;
14864
+ }
14865
+
14866
+ /**
14867
+ * Checks if the the consumer is still in a valid state to continue.
14868
+ *
14869
+ * @return bool
14870
+ */
14871
+ public function valid()
14872
+ {
14873
+ return $this->valid;
14874
+ }
14875
+
14876
+ /**
14877
+ * Waits for a new message from the server generated by MONITOR and returns
14878
+ * it when available.
14879
+ *
14880
+ * @return Object
14881
+ */
14882
+ private function getValue()
14883
+ {
14884
+ $database = 0;
14885
+ $client = null;
14886
+ $event = $this->client->getConnection()->read();
14887
+
14888
+ $callback = function ($matches) use (&$database, &$client) {
14889
+ if (2 === $count = count($matches)) {
14890
+ // Redis <= 2.4
14891
+ $database = (int) $matches[1];
14892
+ }
14893
+
14894
+ if (4 === $count) {
14895
+ // Redis >= 2.6
14896
+ $database = (int) $matches[2];
14897
+ $client = $matches[3];
14898
+ }
14899
+
14900
+ return ' ';
14901
+ };
14902
+
14903
+ $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1);
14904
+ @list($timestamp, $command, $arguments) = explode(' ', $event, 3);
14905
+
14906
+ return (object) array(
14907
+ 'timestamp' => (float) $timestamp,
14908
+ 'database' => $database,
14909
+ 'client' => $client,
14910
+ 'command' => substr($command, 1, -1),
14911
+ 'arguments' => $arguments,
14912
+ );
14913
+ }
14914
+ }
14915
+
14916
+ /* --------------------------------------------------------------------------- */
14917
+
14918
+ namespace Predis\Replication;
14919
+
14920
+ use Predis\NotSupportedException;
14921
+ use Predis\Command\CommandInterface;
14922
+
14923
+ /**
14924
+ * Defines a strategy for master/slave replication.
14925
+ *
14926
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14927
+ */
14928
+ class ReplicationStrategy
14929
+ {
14930
+ protected $disallowed;
14931
+ protected $readonly;
14932
+ protected $readonlySHA1;
14933
+
14934
+ /**
14935
+ *
14936
+ */
14937
+ public function __construct()
14938
+ {
14939
+ $this->disallowed = $this->getDisallowedOperations();
14940
+ $this->readonly = $this->getReadOnlyOperations();
14941
+ $this->readonlySHA1 = array();
14942
+ }
14943
+
14944
+ /**
14945
+ * Returns if the specified command will perform a read-only operation
14946
+ * on Redis or not.
14947
+ *
14948
+ * @param CommandInterface $command Command instance.
14949
+ *
14950
+ * @return bool
14951
+ *
14952
+ * @throws NotSupportedException
14953
+ */
14954
+ public function isReadOperation(CommandInterface $command)
14955
+ {
14956
+ if (isset($this->disallowed[$id = $command->getId()])) {
14957
+ throw new NotSupportedException(
14958
+ "The command '$id' is not allowed in replication mode."
14959
+ );
14960
+ }
14961
+
14962
+ if (isset($this->readonly[$id])) {
14963
+ if (true === $readonly = $this->readonly[$id]) {
14964
+ return true;
14965
+ }
14966
+
14967
+ return call_user_func($readonly, $command);
14968
+ }
14969
+
14970
+ if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
14971
+ $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
14972
+
14973
+ if (isset($this->readonlySHA1[$sha1])) {
14974
+ if (true === $readonly = $this->readonlySHA1[$sha1]) {
14975
+ return true;
14976
+ }
14977
+
14978
+ return call_user_func($readonly, $command);
14979
+ }
14980
+ }
14981
+
14982
+ return false;
14983
+ }
14984
+
14985
+ /**
14986
+ * Returns if the specified command is not allowed for execution in a master
14987
+ * / slave replication context.
14988
+ *
14989
+ * @param CommandInterface $command Command instance.
14990
+ *
14991
+ * @return bool
14992
+ */
14993
+ public function isDisallowedOperation(CommandInterface $command)
14994
+ {
14995
+ return isset($this->disallowed[$command->getId()]);
14996
+ }
14997
+
14998
+ /**
14999
+ * Checks if a SORT command is a readable operation by parsing the arguments
15000
+ * array of the specified commad instance.
15001
+ *
15002
+ * @param CommandInterface $command Command instance.
15003
+ *
15004
+ * @return bool
15005
+ */
15006
+ protected function isSortReadOnly(CommandInterface $command)
15007
+ {
15008
+ $arguments = $command->getArguments();
15009
+
15010
+ return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
15011
+ }
15012
+
15013
+ /**
15014
+ * Marks a command as a read-only operation.
15015
+ *
15016
+ * When the behavior of a command can be decided only at runtime depending
15017
+ * on its arguments, a callable object can be provided to dynamically check
15018
+ * if the specified command performs a read or a write operation.
15019
+ *
15020
+ * @param string $commandID Command ID.
15021
+ * @param mixed $readonly A boolean value or a callable object.
15022
+ */
15023
+ public function setCommandReadOnly($commandID, $readonly = true)
15024
+ {
15025
+ $commandID = strtoupper($commandID);
15026
+
15027
+ if ($readonly) {
15028
+ $this->readonly[$commandID] = $readonly;
15029
+ } else {
15030
+ unset($this->readonly[$commandID]);
15031
+ }
15032
+ }
15033
+
15034
+ /**
15035
+ * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
15036
+ * the behaviour of a script can be decided only at runtime depending on
15037
+ * its arguments, a callable object can be provided to dynamically check
15038
+ * if the passed instance of EVAL or EVALSHA performs write operations or
15039
+ * not.
15040
+ *
15041
+ * @param string $script Body of the Lua script.
15042
+ * @param mixed $readonly A boolean value or a callable object.
15043
+ */
15044
+ public function setScriptReadOnly($script, $readonly = true)
15045
+ {
15046
+ $sha1 = sha1($script);
15047
+
15048
+ if ($readonly) {
15049
+ $this->readonlySHA1[$sha1] = $readonly;
15050
+ } else {
15051
+ unset($this->readonlySHA1[$sha1]);
15052
+ }
15053
+ }
15054
+
15055
+ /**
15056
+ * Returns the default list of disallowed commands.
15057
+ *
15058
+ * @return array
15059
+ */
15060
+ protected function getDisallowedOperations()
15061
+ {
15062
+ return array(
15063
+ 'SHUTDOWN' => true,
15064
+ 'INFO' => true,
15065
+ 'DBSIZE' => true,
15066
+ 'LASTSAVE' => true,
15067
+ 'CONFIG' => true,
15068
+ 'MONITOR' => true,
15069
+ 'SLAVEOF' => true,
15070
+ 'SAVE' => true,
15071
+ 'BGSAVE' => true,
15072
+ 'BGREWRITEAOF' => true,
15073
+ 'SLOWLOG' => true,
15074
+ );
15075
+ }
15076
+
15077
+ /**
15078
+ * Returns the default list of commands performing read-only operations.
15079
+ *
15080
+ * @return array
15081
+ */
15082
+ protected function getReadOnlyOperations()
15083
+ {
15084
+ return array(
15085
+ 'EXISTS' => true,
15086
+ 'TYPE' => true,
15087
+ 'KEYS' => true,
15088
+ 'SCAN' => true,
15089
+ 'RANDOMKEY' => true,
15090
+ 'TTL' => true,
15091
+ 'GET' => true,
15092
+ 'MGET' => true,
15093
+ 'SUBSTR' => true,
15094
+ 'STRLEN' => true,
15095
+ 'GETRANGE' => true,
15096
+ 'GETBIT' => true,
15097
+ 'LLEN' => true,
15098
+ 'LRANGE' => true,
15099
+ 'LINDEX' => true,
15100
+ 'SCARD' => true,
15101
+ 'SISMEMBER' => true,
15102
+ 'SINTER' => true,
15103
+ 'SUNION' => true,
15104
+ 'SDIFF' => true,
15105
+ 'SMEMBERS' => true,
15106
+ 'SSCAN' => true,
15107
+ 'SRANDMEMBER' => true,
15108
+ 'ZRANGE' => true,
15109
+ 'ZREVRANGE' => true,
15110
+ 'ZRANGEBYSCORE' => true,
15111
+ 'ZREVRANGEBYSCORE' => true,
15112
+ 'ZCARD' => true,
15113
+ 'ZSCORE' => true,
15114
+ 'ZCOUNT' => true,
15115
+ 'ZRANK' => true,
15116
+ 'ZREVRANK' => true,
15117
+ 'ZSCAN' => true,
15118
+ 'ZLEXCOUNT' => true,
15119
+ 'ZRANGEBYLEX' => true,
15120
+ 'HGET' => true,
15121
+ 'HMGET' => true,
15122
+ 'HEXISTS' => true,
15123
+ 'HLEN' => true,
15124
+ 'HKEYS' => true,
15125
+ 'HVALS' => true,
15126
+ 'HGETALL' => true,
15127
+ 'HSCAN' => true,
15128
+ 'PING' => true,
15129
+ 'AUTH' => true,
15130
+ 'SELECT' => true,
15131
+ 'ECHO' => true,
15132
+ 'QUIT' => true,
15133
+ 'OBJECT' => true,
15134
+ 'BITCOUNT' => true,
15135
+ 'TIME' => true,
15136
+ 'PFCOUNT' => true,
15137
+ 'SORT' => array($this, 'isSortReadOnly'),
15138
+ );
15139
+ }
15140
+ }
15141
+
15142
+ /* --------------------------------------------------------------------------- */
15143
+
readme.txt ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Redis Object Cache ===
2
+ Contributors: tillkruess
3
+ Donate link: http://till.kruss.me/donations/
4
+ Tags: redis, predis, cache, object cache, wp object cache, server, performance, speed, load
5
+ Requires at least: 3.3
6
+ Tested up to: 4.0
7
+ Stable tag: 1.0
8
+ License: GPLv3
9
+ License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
+
11
+ A persistent object cache backend powered by Redis and the Predis library for PHP.
12
+
13
+
14
+ == Description ==
15
+
16
+ A persistent object cache backend powered by [Redis](http://redis.io/) and the [Predis](https://github.com/nrk/predis/) library for PHP.
17
+
18
+ The Predis library requires PHP 5.4 or greater.
19
+
20
+ To adjust the connection parameters or prefixing cache keys, see ["Other Notes"](http://wordpress.org/extend/plugins/redis-cache/other_notes/).
21
+
22
+ Forked from Eric Mann's and Erick Hitter's [Redis Object Cache](https://github.com/ericmann/Redis-Object-Cache), which requires the Redis PECL extension.
23
+
24
+
25
+ == Installation ==
26
+
27
+ For detailed installation instructions, please read the [standard installation procedure for WordPress plugins](http://codex.wordpress.org/Managing_Plugins#Installing_Plugins).
28
+
29
+ 1. Make sure Redis in installed and running.
30
+ 2. Install and activate plugin.
31
+ 3. Enable the object cache under _Tools -> Redis_.
32
+ 4. If necessary, adjust [connection parameters](http://wordpress.org/extend/plugins/redis-cache/other_notes/).
33
+
34
+
35
+ == Connection Parameters ==
36
+
37
+ By default the object cache drop-in will connect to Redis over TCP at `127.0.0.1:6379` and select database `0`.
38
+
39
+ To adjust the connection parameters, define the following constants in your `wp-config.php`.
40
+
41
+ * `WP_REDIS_SCHEME` [default: `tcp`]
42
+
43
+ Specifies the protocol used to communicate with an instance of Redis. Internally the client uses the connection class associated to the specified connection scheme. By default Predis supports `tcp` (TCP/IP), `unix` (UNIX domain sockets) or `http` (HTTP protocol through Webdis).
44
+
45
+ * `WP_REDIS_HOST` [default: `127.0.0.1`]
46
+
47
+ IP or hostname of the target server. This is ignored when connecting to Redis using UNIX domain sockets.
48
+
49
+ * `WP_REDIS_PORT` [default: `6379`]
50
+
51
+ TCP/IP port of the target server. This is ignored when connecting to Redis using UNIX domain sockets.
52
+
53
+ * `WP_REDIS_DATABASE` [default: not set]
54
+
55
+ Accepts a numeric value that is used by Predis to automatically select a logical database with the `SELECT` command.
56
+
57
+ * `WP_REDIS_PASSWORD` [default: not set]
58
+
59
+ Accepts a value used to authenticate with a Redis server protected by password with the `AUTH` command.
60
+
61
+
62
+ == Prefixing Cache Keys ==
63
+
64
+ The `WP_CACHE_KEY_SALT` constant is provided to add a prefix to all cache keys.
65
+
66
+ Users with setups where multiple installs share a common `wp-config.php` or `$table_prefix` can use this constant to guarantee uniqueness for the keys generated by this object cache.
67
+
68
+
69
+ == Changelog ==
70
+
71
+ = 1.0 =
72
+
73
+ * Initial release
redis-cache.php ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Redis Object Cache
4
+ Plugin URI: http://wordpress.org/plugins/redis-cache/
5
+ Description: A Redis backend for the WordPress Object Cache based on the Predis client library for PHP.
6
+ Version: 1.0
7
+ Author: Till Krüss
8
+ Author URI: http://till.kruss.me/
9
+ License: GPLv3
10
+ License URI: http://www.gnu.org/licenses/gpl-3.0.html
11
+ */
12
+
13
+ class RedisObjectCache {
14
+
15
+ public $admin_page = 'tools.php?page=redis-cache';
16
+ public $admin_actions = array( 'enable-cache', 'disable-cache', 'update-dropin' );
17
+
18
+ public function __construct() {
19
+
20
+ add_action( 'admin_notices', array( $this, 'show_admin_notices' ) );
21
+ add_action( 'admin_menu', array( $this, 'add_admin_menu_page' ) );
22
+ add_action( 'load-tools_page_redis-cache', array( $this, 'do_admin_actions' ) );
23
+ add_action( 'load-tools_page_redis-cache', array( $this, 'add_admin_page_notices' ) );
24
+ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'add_plugin_action_link' ) );
25
+
26
+ }
27
+
28
+ public function add_admin_menu_page() {
29
+
30
+ // add "Tools" sub-page
31
+ add_management_page(
32
+ __( 'Redis Object Cache', 'redis-cache'),
33
+ __( 'Redis', 'redis-cache'),
34
+ 'manage_options',
35
+ 'redis-cache',
36
+ array( $this, 'show_admin_page' )
37
+ );
38
+
39
+ }
40
+
41
+ public function show_admin_page() {
42
+
43
+ // request filesystem credentials?
44
+ if ( isset( $_GET[ '_wpnonce' ], $_GET[ 'action' ] ) ) {
45
+
46
+ $action = $_GET[ 'action' ];
47
+
48
+ foreach ( $this->admin_actions as $name ) {
49
+
50
+ // verify nonce
51
+ if ( $action === $name && wp_verify_nonce( $_GET[ '_wpnonce' ], $action ) ) {
52
+
53
+ $url = wp_nonce_url( admin_url( add_query_arg( 'action', $action, $this->admin_page ) ), $action );
54
+
55
+ if ( $this->initialize_filesystem( $url ) === false ) {
56
+ return; // request filesystem credentials
57
+ }
58
+
59
+ }
60
+
61
+ }
62
+
63
+ }
64
+
65
+ // show admin page
66
+ require_once plugin_dir_path( __FILE__ ) . '/includes/admin-page.php';
67
+
68
+ }
69
+
70
+ public function add_plugin_action_link( $links ) {
71
+
72
+ // add settings link to plugin actions
73
+ return array_merge(
74
+ array( '<a href="' . admin_url( $this->admin_page ) . '">Settings</a>' ),
75
+ $links
76
+ );
77
+
78
+ }
79
+
80
+ public function object_cache_dropin_exists() {
81
+ return file_exists( WP_CONTENT_DIR . '/object-cache.php' );
82
+ }
83
+
84
+ public function validate_object_cache_dropin() {
85
+ global $wp_object_cache;
86
+ return $this->object_cache_dropin_exists() && method_exists( $wp_object_cache, 'redis_status' );
87
+ }
88
+
89
+ public function get_redis_scheme() {
90
+ return defined( 'WP_REDIS_SCHEME' ) ? WP_REDIS_SCHEME : 'TCP';
91
+ }
92
+
93
+ public function get_redis_host() {
94
+ return defined( 'WP_REDIS_HOST' ) ? WP_REDIS_HOST : '127.0.0.1';
95
+ }
96
+
97
+ public function get_redis_port() {
98
+ return defined( 'WP_REDIS_PORT' ) ? WP_REDIS_PORT : '6379';
99
+ }
100
+
101
+ public function get_redis_database() {
102
+ return defined( 'WP_REDIS_DATABASE' ) ? WP_REDIS_DATABASE : '0';
103
+ }
104
+
105
+ public function get_redis_password() {
106
+ return defined( 'WP_REDIS_PASSWORD' ) ? WP_REDIS_PASSWORD : null;
107
+ }
108
+
109
+ public function get_redis_status() {
110
+
111
+ global $wp_object_cache;
112
+
113
+ if ( ! $this->object_cache_dropin_exists() ) {
114
+ return __( 'No drop-in found', 'redis-cache' );
115
+ }
116
+
117
+ if ( $this->validate_object_cache_dropin() ) {
118
+ return $wp_object_cache->redis_status() ? __( 'Connected', 'redis-cache' ) : __( 'Not connected', 'redis-cache' );
119
+ }
120
+
121
+ return __( 'Unknown', 'redis-cache' );
122
+
123
+ }
124
+
125
+ public function show_admin_notices() {
126
+
127
+ global $wp_object_cache;
128
+
129
+ if ( $this->object_cache_dropin_exists() ) {
130
+
131
+ $url = wp_nonce_url( admin_url( add_query_arg( 'action', 'update-dropin', $this->admin_page ) ), 'update-dropin' );
132
+
133
+ if ( $this->validate_object_cache_dropin() ) {
134
+
135
+ $dropin = get_plugin_data( WP_CONTENT_DIR . '/object-cache.php' );
136
+ $plugin = get_plugin_data( plugin_dir_path( __FILE__ ) . '/includes/object-cache.php' );
137
+
138
+ // outdated `object-cache.php` notice
139
+ if ( version_compare( $dropin[ 'Version' ], $plugin[ 'Version' ], '<' ) ) {
140
+ $message = sprintf( __( 'The Redis object cache drop-in is outdated. <a href="%s">Update it now</a>.', 'redis-cache' ), $url );
141
+ }
142
+
143
+ } else {
144
+
145
+ // show foreign `object-cache.php` notice
146
+ $message = sprintf( __( 'Another object cache drop-in is already active. To use Redis, <a href="%s">please replace it now</a>.', 'redis-cache' ), $url );
147
+
148
+ }
149
+
150
+ if ( isset( $message ) ) {
151
+ printf( '<div class="update-nag">%s</div>', $message );
152
+ }
153
+
154
+ }
155
+
156
+ }
157
+
158
+ public function add_admin_page_notices() {
159
+
160
+ // show PHP version warning
161
+ if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) {
162
+ add_settings_error( '', 'redis-cache', __( 'This plugin requires PHP 5.4 or greater.', 'redis-cache' ) );
163
+ }
164
+
165
+ // show action success/failure messages
166
+ if ( isset( $_GET[ 'message' ] ) ) {
167
+
168
+ switch ( $_GET[ 'message' ] ) {
169
+
170
+ case 'cache-enabled':
171
+ $message = __( 'Object Cache enabled.', 'redis-cache' );
172
+ break;
173
+ case 'enable-cache-failed':
174
+ $error = __( 'Object Cache could not be enabled.', 'redis-cache' );
175
+ break;
176
+ case 'cache-disabled':
177
+ $message = __( 'Object Cache disabled.', 'redis-cache' );
178
+ break;
179
+ case 'disable-cache-failed':
180
+ $error = __( 'Object Cache could not be disabled.', 'redis-cache' );
181
+ break;
182
+ case 'dropin-updated':
183
+ $message = __( 'Drop-in updated.', 'redis-cache' );
184
+ break;
185
+ case 'update-dropin-failed':
186
+ $error = __( 'Drop-in could not be updated.', 'redis-cache' );
187
+ break;
188
+
189
+ }
190
+
191
+ add_settings_error( '', 'redis-cache', isset( $message ) ? $message : $error, isset( $message ) ? 'updated' : 'error' );
192
+
193
+ }
194
+
195
+ }
196
+
197
+ public function do_admin_actions() {
198
+
199
+ global $wp_filesystem;
200
+
201
+ if ( isset( $_GET[ '_wpnonce' ], $_GET[ 'action' ] ) ) {
202
+
203
+ $action = $_GET[ 'action' ];
204
+
205
+ // verify nonce
206
+ foreach ( $this->admin_actions as $name ) {
207
+ if ( $action === $name && ! wp_verify_nonce( $_GET[ '_wpnonce' ], $action ) ) {
208
+ return;
209
+ }
210
+ }
211
+
212
+ if ( in_array( $action, $this->admin_actions ) ) {
213
+
214
+ $url = wp_nonce_url( admin_url( add_query_arg( 'action', $action, $this->admin_page ) ), $action );
215
+
216
+ // do we have filesystem credentials?
217
+ if ( $this->initialize_filesystem( $url, true ) ) {
218
+
219
+ switch ( $action ) {
220
+
221
+ case 'enable-cache':
222
+ $result = $wp_filesystem->copy( plugin_dir_path( __FILE__ ) . '/includes/object-cache.php', WP_CONTENT_DIR . '/object-cache.php', true );
223
+ $message = $result ? 'cache-enabled' : 'enable-cache-failed';
224
+ break;
225
+
226
+ case 'disable-cache':
227
+ $result = $wp_filesystem->delete( WP_CONTENT_DIR . '/object-cache.php' );
228
+ $message = $result ? 'cache-disabled' : 'disable-cache-failed';
229
+ break;
230
+
231
+ case 'update-dropin':
232
+ $result = $wp_filesystem->copy( plugin_dir_path( __FILE__ ) . '/includes/object-cache.php', WP_CONTENT_DIR . '/object-cache.php', true );
233
+ $message = $result ? 'dropin-updated' : 'update-dropin-failed';
234
+ break;
235
+
236
+ }
237
+
238
+ wp_safe_redirect( admin_url( add_query_arg( 'message', $message, $this->admin_page ) ) );
239
+ exit;
240
+
241
+ }
242
+
243
+ }
244
+
245
+ }
246
+
247
+ }
248
+
249
+ public function initialize_filesystem( $url, $silent = false ) {
250
+
251
+ if ( $silent ) {
252
+ ob_start();
253
+ }
254
+
255
+ if ( ( $credentials = request_filesystem_credentials( $url ) ) === false ) {
256
+
257
+ if ( $silent ) {
258
+ ob_end_clean();
259
+ }
260
+
261
+ return false;
262
+
263
+ }
264
+
265
+ if ( ! WP_Filesystem( $credentials ) ) {
266
+
267
+ request_filesystem_credentials( $url );
268
+
269
+ if ( $silent ) {
270
+ ob_end_clean();
271
+ }
272
+
273
+ return false;
274
+
275
+ }
276
+
277
+ return true;
278
+
279
+ }
280
+
281
+ }
282
+
283
+ new RedisObjectCache;