Redis Object Cache - Version 1.3.3

Version Description

  • Updated Predis to v1.1.1
    • Added redis_instance() method
    • Added incr() method alias for Batcache compatibility
    • Added WP_REDIS_GLOBAL_GROUPS and WP_REDIS_IGNORED_GROUPS constant
    • Added redis_object_cache_delete action
    • Use WP_PLUGIN_DIR with WP_CONTENT_DIR as fallback
    • Set password when using a cluster or replication
    • Show Redis client in stats()
    • Change visibility of $cache to public
    • Use old array syntax, just in case
Download this release

Release Info

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

Code changes from version 1.3.2 to 1.3.3

includes/object-cache.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Redis Object Cache
4
  Plugin URI: http://wordpress.org/plugins/redis-cache/
5
  Description: A persistent object cache backend powered by Redis. Supports HHVM's Redis extension, the PECL Redis Extension and the Predis library for PHP.
6
- Version: 1.3.2
7
  Author: Till Krüss
8
  Author URI: https://till.im/
9
  License: GPLv3
@@ -248,7 +248,7 @@ function wp_cache_add_non_persistent_groups( $groups ) {
248
  class WP_Object_Cache {
249
 
250
  /**
251
- * Holds the Redis client.
252
  *
253
  * @var mixed
254
  */
@@ -266,7 +266,7 @@ class WP_Object_Cache {
266
  *
267
  * @var array
268
  */
269
- private $cache = array();
270
 
271
  /**
272
  * Name of the used Redis client
@@ -287,7 +287,7 @@ class WP_Object_Cache {
287
  *
288
  * @var array
289
  */
290
- public $no_redis_groups = array( 'comment', 'counts' );
291
 
292
  /**
293
  * Prefix used for global groups.
@@ -333,13 +333,21 @@ class WP_Object_Cache {
333
  'port' => 6379
334
  );
335
 
336
- foreach ( [ 'scheme', 'host', 'port', 'path', 'password', 'database' ] as $setting ) {
337
  $constant = sprintf( 'WP_REDIS_%s', strtoupper( $setting ) );
338
  if ( defined( $constant ) ) {
339
  $parameters[ $setting ] = constant( $constant );
340
  }
341
  }
342
 
 
 
 
 
 
 
 
 
343
  $client = defined( 'WP_REDIS_CLIENT' ) ? WP_REDIS_CLIENT : null;
344
 
345
  if ( class_exists( 'Redis' ) && strcasecmp( 'predis', $client ) !== 0 ) {
@@ -397,7 +405,8 @@ class WP_Object_Cache {
397
 
398
  // Load bundled Predis library
399
  if ( ! class_exists( 'Predis\Client' ) ) {
400
- require_once WP_CONTENT_DIR . '/plugins/redis-cache/includes/predis.php';
 
401
  Predis\Autoloader::register();
402
  }
403
 
@@ -413,6 +422,10 @@ class WP_Object_Cache {
413
  $options[ 'replication' ] = true;
414
  }
415
 
 
 
 
 
416
  $this->redis = new Predis\Client( $parameters, $options );
417
  $this->redis->connect();
418
 
@@ -428,7 +441,7 @@ class WP_Object_Cache {
428
  } catch ( Exception $exception ) {
429
 
430
  // When Redis is unavailable, fall back to the internal back by forcing all groups to be "no redis" groups
431
- $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $this->global_groups ) );
432
 
433
  $this->redis_connected = false;
434
 
@@ -458,6 +471,15 @@ class WP_Object_Cache {
458
  return $this->redis_connected;
459
  }
460
 
 
 
 
 
 
 
 
 
 
461
  /**
462
  * Adds a value to cache.
463
  *
@@ -508,7 +530,7 @@ class WP_Object_Cache {
508
  $result = true;
509
 
510
  // save if group not excluded and redis is up
511
- if ( ! in_array( $group, $this->no_redis_groups ) && $this->redis_status() ) {
512
  $exists = $this->redis->exists( $derived_key );
513
 
514
  if ( $add === $exists ) {
@@ -552,10 +574,12 @@ class WP_Object_Cache {
552
  $result = true;
553
  }
554
 
555
- if ( $this->redis_status() && ! in_array( $group, $this->no_redis_groups ) ) {
556
  $result = $this->parse_redis_response( $this->redis->del( $derived_key ) );
557
  }
558
 
 
 
559
  return $result;
560
  }
561
 
@@ -601,7 +625,7 @@ class WP_Object_Cache {
601
  $this->cache_hits++;
602
 
603
  return is_object( $this->cache[ $derived_key ] ) ? clone $this->cache[ $derived_key ] : $this->cache[ $derived_key ];
604
- } elseif ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
605
  $found = false;
606
  $this->cache_misses++;
607
 
@@ -646,7 +670,6 @@ class WP_Object_Cache {
646
  * Mirrors the Memcached Object Cache plugin's argument and return-value formats
647
  *
648
  * @param array $groups Array of groups and keys to retrieve
649
- * @uses this::filter_redis_get_multi()
650
  * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys null.
651
  */
652
  public function get_multi( $groups ) {
@@ -658,7 +681,7 @@ class WP_Object_Cache {
658
  $cache = array();
659
 
660
  foreach ( $groups as $group => $keys ) {
661
- if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
662
  foreach ( $keys as $key ) {
663
  $cache[ $this->build_key( $key, $group ) ] = $this->get( $key, $group );
664
  }
@@ -714,7 +737,7 @@ class WP_Object_Cache {
714
  $result = true;
715
 
716
  // save if group not excluded from redis and redis is up
717
- if ( ! in_array( $group, $this->no_redis_groups ) && $this->redis_status() ) {
718
  $expiration = $this->validate_expiration($expiration);
719
  if ( $expiration ) {
720
  $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) );
@@ -748,7 +771,7 @@ class WP_Object_Cache {
748
  $offset = (int) $offset;
749
 
750
  // If group is a non-Redis group, save to internal cache, not Redis
751
- if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
752
  $value = $this->get_from_internal_cache( $derived_key, $group );
753
  $value += $offset;
754
  $this->add_to_internal_cache( $derived_key, $value );
@@ -764,6 +787,18 @@ class WP_Object_Cache {
764
  return $result;
765
  }
766
 
 
 
 
 
 
 
 
 
 
 
 
 
767
  /**
768
  * Decrement a Redis counter by the amount specified
769
  *
@@ -777,7 +812,7 @@ class WP_Object_Cache {
777
  $offset = (int) $offset;
778
 
779
  // If group is a non-Redis group, save to internal cache, not Redis
780
- if ( in_array( $group, $this->no_redis_groups ) || ! $this->redis_status() ) {
781
  $value = $this->get_from_internal_cache( $derived_key, $group );
782
  $value -= $offset;
783
  $this->add_to_internal_cache( $derived_key, $value );
@@ -801,7 +836,8 @@ class WP_Object_Cache {
801
  public function stats() { ?>
802
 
803
  <p>
804
- <strong>Cache Status:</strong> <?php echo $this->redis_status() ? 'Connected' : 'Not Connected'; ?><br />
 
805
  <strong>Cache Hits:</strong> <?php echo $this->cache_hits; ?><br />
806
  <strong>Cache Misses:</strong> <?php echo $this->cache_misses; ?>
807
  </p>
@@ -932,7 +968,7 @@ class WP_Object_Cache {
932
  if ( $this->redis_status() ) {
933
  $this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
934
  } else {
935
- $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $groups ) );
936
  }
937
  }
938
 
@@ -944,7 +980,7 @@ class WP_Object_Cache {
944
  public function add_non_persistent_groups( $groups ) {
945
  $groups = (array) $groups;
946
 
947
- $this->no_redis_groups = array_unique( array_merge( $this->no_redis_groups, $groups ) );
948
  }
949
 
950
  /**
3
  Plugin Name: Redis Object Cache
4
  Plugin URI: http://wordpress.org/plugins/redis-cache/
5
  Description: A persistent object cache backend powered by Redis. Supports HHVM's Redis extension, the PECL Redis Extension and the Predis library for PHP.
6
+ Version: 1.3.3
7
  Author: Till Krüss
8
  Author URI: https://till.im/
9
  License: GPLv3
248
  class WP_Object_Cache {
249
 
250
  /**
251
+ * The Redis client.
252
  *
253
  * @var mixed
254
  */
266
  *
267
  * @var array
268
  */
269
+ public $cache = array();
270
 
271
  /**
272
  * Name of the used Redis client
287
  *
288
  * @var array
289
  */
290
+ public $ignored_groups = array( 'comment', 'counts' );
291
 
292
  /**
293
  * Prefix used for global groups.
333
  'port' => 6379
334
  );
335
 
336
+ foreach ( array( 'scheme', 'host', 'port', 'path', 'password', 'database' ) as $setting ) {
337
  $constant = sprintf( 'WP_REDIS_%s', strtoupper( $setting ) );
338
  if ( defined( $constant ) ) {
339
  $parameters[ $setting ] = constant( $constant );
340
  }
341
  }
342
 
343
+ if ( defined( 'WP_REDIS_GLOBAL_GROUPS' ) && is_array( WP_REDIS_GLOBAL_GROUPS ) ) {
344
+ $this->global_groups = WP_REDIS_GLOBAL_GROUPS;
345
+ }
346
+
347
+ if ( defined( 'WP_REDIS_IGNORED_GROUPS' ) && is_array( WP_REDIS_IGNORED_GROUPS ) ) {
348
+ $this->ignored_groups = WP_REDIS_IGNORED_GROUPS;
349
+ }
350
+
351
  $client = defined( 'WP_REDIS_CLIENT' ) ? WP_REDIS_CLIENT : null;
352
 
353
  if ( class_exists( 'Redis' ) && strcasecmp( 'predis', $client ) !== 0 ) {
405
 
406
  // Load bundled Predis library
407
  if ( ! class_exists( 'Predis\Client' ) ) {
408
+ $plugin_dir = defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins';
409
+ require_once $plugin_dir . '/redis-cache/includes/predis.php';
410
  Predis\Autoloader::register();
411
  }
412
 
422
  $options[ 'replication' ] = true;
423
  }
424
 
425
+ if ( ( defined( 'WP_REDIS_SERVERS' ) || defined( 'WP_REDIS_CLUSTER' ) ) && defined( 'WP_REDIS_PASSWORD' ) ) {
426
+ $options[ 'parameters' ][ 'password' ] = WP_REDIS_PASSWORD;
427
+ }
428
+
429
  $this->redis = new Predis\Client( $parameters, $options );
430
  $this->redis->connect();
431
 
441
  } catch ( Exception $exception ) {
442
 
443
  // When Redis is unavailable, fall back to the internal back by forcing all groups to be "no redis" groups
444
+ $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $this->global_groups ) );
445
 
446
  $this->redis_connected = false;
447
 
471
  return $this->redis_connected;
472
  }
473
 
474
+ /**
475
+ * Returns the Redis instance.
476
+ *
477
+ * @return mixed
478
+ */
479
+ public function redis_instance() {
480
+ return $this->redis;
481
+ }
482
+
483
  /**
484
  * Adds a value to cache.
485
  *
530
  $result = true;
531
 
532
  // save if group not excluded and redis is up
533
+ if ( ! in_array( $group, $this->ignored_groups ) && $this->redis_status() ) {
534
  $exists = $this->redis->exists( $derived_key );
535
 
536
  if ( $add === $exists ) {
574
  $result = true;
575
  }
576
 
577
+ if ( $this->redis_status() && ! in_array( $group, $this->ignored_groups ) ) {
578
  $result = $this->parse_redis_response( $this->redis->del( $derived_key ) );
579
  }
580
 
581
+ do_action('redis_object_cache_delete', $key, $group);
582
+
583
  return $result;
584
  }
585
 
625
  $this->cache_hits++;
626
 
627
  return is_object( $this->cache[ $derived_key ] ) ? clone $this->cache[ $derived_key ] : $this->cache[ $derived_key ];
628
+ } elseif ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
629
  $found = false;
630
  $this->cache_misses++;
631
 
670
  * Mirrors the Memcached Object Cache plugin's argument and return-value formats
671
  *
672
  * @param array $groups Array of groups and keys to retrieve
 
673
  * @return bool|mixed Array of cached values, keys in the format $group:$key. Non-existent keys null.
674
  */
675
  public function get_multi( $groups ) {
681
  $cache = array();
682
 
683
  foreach ( $groups as $group => $keys ) {
684
+ if ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
685
  foreach ( $keys as $key ) {
686
  $cache[ $this->build_key( $key, $group ) ] = $this->get( $key, $group );
687
  }
737
  $result = true;
738
 
739
  // save if group not excluded from redis and redis is up
740
+ if ( ! in_array( $group, $this->ignored_groups ) && $this->redis_status() ) {
741
  $expiration = $this->validate_expiration($expiration);
742
  if ( $expiration ) {
743
  $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) );
771
  $offset = (int) $offset;
772
 
773
  // If group is a non-Redis group, save to internal cache, not Redis
774
+ if ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
775
  $value = $this->get_from_internal_cache( $derived_key, $group );
776
  $value += $offset;
777
  $this->add_to_internal_cache( $derived_key, $value );
787
  return $result;
788
  }
789
 
790
+ /**
791
+ * Alias of `increment()`.
792
+ *
793
+ * @param string $key
794
+ * @param int $offset
795
+ * @param string $group
796
+ * @return bool
797
+ */
798
+ public function incr( $key, $offset = 1, $group = 'default' ) {
799
+ return $this->increment( $key, $offset, $group );
800
+ }
801
+
802
  /**
803
  * Decrement a Redis counter by the amount specified
804
  *
812
  $offset = (int) $offset;
813
 
814
  // If group is a non-Redis group, save to internal cache, not Redis
815
+ if ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
816
  $value = $this->get_from_internal_cache( $derived_key, $group );
817
  $value -= $offset;
818
  $this->add_to_internal_cache( $derived_key, $value );
836
  public function stats() { ?>
837
 
838
  <p>
839
+ <strong>Redis Status:</strong> <?php echo $this->redis_status() ? 'Connected' : 'Not Connected'; ?><br />
840
+ <strong>Redis Client:</strong> <?php echo $this->redis_client; ?><br />
841
  <strong>Cache Hits:</strong> <?php echo $this->cache_hits; ?><br />
842
  <strong>Cache Misses:</strong> <?php echo $this->cache_misses; ?>
843
  </p>
968
  if ( $this->redis_status() ) {
969
  $this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
970
  } else {
971
+ $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
972
  }
973
  }
974
 
980
  public function add_non_persistent_groups( $groups ) {
981
  $groups = (array) $groups;
982
 
983
+ $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
984
  }
985
 
986
  /**
includes/predis.php CHANGED
@@ -319,100 +319,84 @@ class SetIntersectionStore extends Command
319
  }
320
 
321
  /**
322
- * @link http://redis.io/commands/eval
323
  *
324
  * @author Daniele Alessandri <suppakilla@gmail.com>
325
  */
326
- class ServerEval extends Command
327
  {
328
  /**
329
  * {@inheritdoc}
330
  */
331
  public function getId()
332
  {
333
- return 'EVAL';
334
  }
335
 
336
  /**
337
- * Calculates the SHA1 hash of the body of the script.
338
- *
339
- * @return string SHA1 hash.
340
  */
341
- public function getScriptHash()
342
  {
343
- return sha1($this->getArgument(0));
344
  }
345
  }
346
 
347
  /**
348
- * @link http://redis.io/commands/sinter
349
  *
350
  * @author Daniele Alessandri <suppakilla@gmail.com>
351
  */
352
- class SetIntersection extends Command
353
  {
354
  /**
355
  * {@inheritdoc}
356
  */
357
  public function getId()
358
  {
359
- return 'SINTER';
360
  }
361
 
362
  /**
363
- * {@inheritdoc}
 
 
364
  */
365
- protected function filterArguments(array $arguments)
366
  {
367
- return self::normalizeArguments($arguments);
368
  }
369
  }
370
 
371
  /**
372
- * @link http://redis.io/commands/rpush
373
  *
374
  * @author Daniele Alessandri <suppakilla@gmail.com>
375
  */
376
- class ListPushTail extends Command
377
  {
378
  /**
379
  * {@inheritdoc}
380
  */
381
  public function getId()
382
  {
383
- return 'RPUSH';
384
- }
385
-
386
- /**
387
- * {@inheritdoc}
388
- */
389
- protected function filterArguments(array $arguments)
390
- {
391
- return self::normalizeVariadic($arguments);
392
  }
393
  }
394
 
395
  /**
396
- * @link http://redis.io/commands/subscribe
397
  *
398
  * @author Daniele Alessandri <suppakilla@gmail.com>
399
  */
400
- class PubSubSubscribe extends Command
401
  {
402
  /**
403
  * {@inheritdoc}
404
  */
405
  public function getId()
406
  {
407
- return 'SUBSCRIBE';
408
- }
409
-
410
- /**
411
- * {@inheritdoc}
412
- */
413
- protected function filterArguments(array $arguments)
414
- {
415
- return self::normalizeArguments($arguments);
416
  }
417
  }
418
 
@@ -446,74 +430,42 @@ class ListPopFirstBlocking extends Command
446
  }
447
 
448
  /**
449
- * @link http://redis.io/commands/ttl
450
- *
451
- * @author Daniele Alessandri <suppakilla@gmail.com>
452
- */
453
- class KeyTimeToLive extends Command
454
- {
455
- /**
456
- * {@inheritdoc}
457
- */
458
- public function getId()
459
- {
460
- return 'TTL';
461
- }
462
- }
463
-
464
- /**
465
- * @link http://redis.io/commands/expireat
466
  *
467
  * @author Daniele Alessandri <suppakilla@gmail.com>
468
  */
469
- class KeyExpireAt extends Command
470
  {
471
  /**
472
  * {@inheritdoc}
473
  */
474
  public function getId()
475
  {
476
- return 'EXPIREAT';
477
- }
478
-
479
- /**
480
- * {@inheritdoc}
481
- */
482
- public function parseResponse($data)
483
- {
484
- return (bool) $data;
485
  }
486
- }
487
 
488
- /**
489
- * @link http://redis.io/commands/rename
490
- *
491
- * @author Daniele Alessandri <suppakilla@gmail.com>
492
- */
493
- class KeyRename extends Command
494
- {
495
  /**
496
  * {@inheritdoc}
497
  */
498
- public function getId()
499
  {
500
- return 'RENAME';
501
  }
502
  }
503
 
504
  /**
505
- * @link http://redis.io/commands/unsubscribe
506
  *
507
  * @author Daniele Alessandri <suppakilla@gmail.com>
508
  */
509
- class PubSubUnsubscribe extends Command
510
  {
511
  /**
512
  * {@inheritdoc}
513
  */
514
  public function getId()
515
  {
516
- return 'UNSUBSCRIBE';
517
  }
518
 
519
  /**
@@ -526,18 +478,18 @@ class PubSubUnsubscribe extends Command
526
  }
527
 
528
  /**
529
- * @link http://redis.io/commands/zunionstore
530
  *
531
  * @author Daniele Alessandri <suppakilla@gmail.com>
532
  */
533
- class ZSetUnionStore extends Command
534
  {
535
  /**
536
  * {@inheritdoc}
537
  */
538
  public function getId()
539
  {
540
- return 'ZUNIONSTORE';
541
  }
542
 
543
  /**
@@ -545,108 +497,33 @@ class ZSetUnionStore extends Command
545
  */
546
  protected function filterArguments(array $arguments)
547
  {
548
- $options = array();
549
- $argc = count($arguments);
550
-
551
- if ($argc > 2 && is_array($arguments[$argc - 1])) {
552
- $options = $this->prepareOptions(array_pop($arguments));
553
- }
554
-
555
- if (is_array($arguments[1])) {
556
- $arguments = array_merge(
557
- array($arguments[0], count($arguments[1])),
558
- $arguments[1]
559
- );
560
- }
561
-
562
- return array_merge($arguments, $options);
563
- }
564
-
565
- /**
566
- * Returns a list of options and modifiers compatible with Redis.
567
- *
568
- * @param array $options List of options.
569
- *
570
- * @return array
571
- */
572
- private function prepareOptions($options)
573
- {
574
- $opts = array_change_key_case($options, CASE_UPPER);
575
- $finalizedOpts = array();
576
-
577
- if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) {
578
- $finalizedOpts[] = 'WEIGHTS';
579
-
580
- foreach ($opts['WEIGHTS'] as $weight) {
581
- $finalizedOpts[] = $weight;
582
- }
583
- }
584
-
585
- if (isset($opts['AGGREGATE'])) {
586
- $finalizedOpts[] = 'AGGREGATE';
587
- $finalizedOpts[] = $opts['AGGREGATE'];
588
- }
589
-
590
- return $finalizedOpts;
591
  }
592
  }
593
 
594
  /**
595
- * @link http://redis.io/commands/zrangebylex
596
  *
597
  * @author Daniele Alessandri <suppakilla@gmail.com>
598
  */
599
- class ZSetRangeByLex extends ZSetRange
600
  {
601
  /**
602
  * {@inheritdoc}
603
  */
604
  public function getId()
605
  {
606
- return 'ZRANGEBYLEX';
607
- }
608
-
609
- /**
610
- * {@inheritdoc}
611
- */
612
- protected function prepareOptions($options)
613
- {
614
- $opts = array_change_key_case($options, CASE_UPPER);
615
- $finalizedOpts = array();
616
-
617
- if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
618
- $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
619
-
620
- $finalizedOpts[] = 'LIMIT';
621
- $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
622
- $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
623
- }
624
-
625
- return $finalizedOpts;
626
- }
627
-
628
- /**
629
- * {@inheritdoc}
630
- */
631
- protected function withScores()
632
- {
633
- return false;
634
  }
635
- }
636
 
637
- /**
638
- * @link http://redis.io/commands/setex
639
- *
640
- * @author Daniele Alessandri <suppakilla@gmail.com>
641
- */
642
- class StringSetExpire extends Command
643
- {
644
  /**
645
- * {@inheritdoc}
 
 
646
  */
647
- public function getId()
648
  {
649
- return 'SETEX';
650
  }
651
  }
652
 
@@ -664,14 +541,6 @@ class KeyExpire extends Command
664
  {
665
  return 'EXPIRE';
666
  }
667
-
668
- /**
669
- * {@inheritdoc}
670
- */
671
- public function parseResponse($data)
672
- {
673
- return (bool) $data;
674
- }
675
  }
676
 
677
  /**
@@ -774,58 +643,18 @@ class ServerInfo extends Command
774
  }
775
 
776
  /**
777
- * @link http://redis.io/commands/zrangebyscore
778
  *
779
  * @author Daniele Alessandri <suppakilla@gmail.com>
780
  */
781
- class ZSetRangeByScore extends ZSetRange
782
  {
783
  /**
784
  * {@inheritdoc}
785
  */
786
  public function getId()
787
  {
788
- return 'ZRANGEBYSCORE';
789
- }
790
-
791
- /**
792
- * {@inheritdoc}
793
- */
794
- protected function prepareOptions($options)
795
- {
796
- $opts = array_change_key_case($options, CASE_UPPER);
797
- $finalizedOpts = array();
798
-
799
- if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
800
- $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
801
-
802
- $finalizedOpts[] = 'LIMIT';
803
- $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
804
- $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
805
- }
806
-
807
- return array_merge($finalizedOpts, parent::prepareOptions($options));
808
- }
809
-
810
- /**
811
- * {@inheritdoc}
812
- */
813
- protected function withScores()
814
- {
815
- $arguments = $this->getArguments();
816
-
817
- for ($i = 3; $i < count($arguments); ++$i) {
818
- switch (strtoupper($arguments[$i])) {
819
- case 'WITHSCORES':
820
- return true;
821
-
822
- case 'LIMIT':
823
- $i += 2;
824
- break;
825
- }
826
- }
827
-
828
- return false;
829
  }
830
  }
831
 
@@ -866,122 +695,274 @@ class StringSetMultiple extends Command
866
  }
867
 
868
  /**
869
- * @link http://redis.io/commands/evalsha
870
  *
871
  * @author Daniele Alessandri <suppakilla@gmail.com>
872
  */
873
- class ServerEvalSHA extends ServerEval
874
  {
875
  /**
876
  * {@inheritdoc}
877
  */
878
  public function getId()
879
  {
880
- return 'EVALSHA';
881
  }
882
 
883
  /**
884
- * Returns the SHA1 hash of the body of the script.
885
- *
886
- * @return string SHA1 hash.
887
  */
888
- public function getScriptHash()
889
  {
890
- return $this->getArgument(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
891
  }
892
  }
893
 
894
  /**
895
- * @link http://redis.io/commands/decr
896
  *
897
  * @author Daniele Alessandri <suppakilla@gmail.com>
898
  */
899
- class StringDecrement extends Command
900
  {
901
  /**
902
  * {@inheritdoc}
903
  */
904
  public function getId()
905
  {
906
- return 'DECR';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
907
  }
908
  }
909
 
910
  /**
911
- * @link http://redis.io/commands/decrby
912
  *
913
  * @author Daniele Alessandri <suppakilla@gmail.com>
914
  */
915
- class StringDecrementBy extends Command
916
  {
917
  /**
918
  * {@inheritdoc}
919
  */
920
  public function getId()
921
  {
922
- return 'DECRBY';
923
  }
924
  }
925
 
926
  /**
927
- * @link http://redis.io/commands/get
928
  *
929
  * @author Daniele Alessandri <suppakilla@gmail.com>
930
  */
931
- class StringGet extends Command
932
  {
933
  /**
934
  * {@inheritdoc}
935
  */
936
  public function getId()
937
  {
938
- return 'GET';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  }
940
  }
941
 
942
  /**
943
- * @link http://redis.io/commands/bitpos
944
  *
945
  * @author Daniele Alessandri <suppakilla@gmail.com>
946
  */
947
- class StringBitPos extends Command
948
  {
949
  /**
950
  * {@inheritdoc}
951
  */
952
  public function getId()
953
  {
954
- return 'BITPOS';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
955
  }
956
  }
957
 
958
  /**
959
- * @link http://redis.io/commands/bitop
960
  *
961
  * @author Daniele Alessandri <suppakilla@gmail.com>
962
  */
963
- class StringBitOp extends Command
964
  {
965
  /**
966
  * {@inheritdoc}
967
  */
968
  public function getId()
969
  {
970
- return 'BITOP';
971
  }
 
972
 
 
 
 
 
 
 
 
973
  /**
974
  * {@inheritdoc}
975
  */
976
- protected function filterArguments(array $arguments)
977
  {
978
- if (count($arguments) === 3 && is_array($arguments[2])) {
979
- list($operation, $destination) = $arguments;
980
- $arguments = $arguments[2];
981
- array_unshift($arguments, $operation, $destination);
982
- }
983
-
984
- return $arguments;
985
  }
986
  }
987
 
@@ -1018,34 +999,34 @@ class StringBitCount extends Command
1018
  }
1019
 
1020
  /**
1021
- * @link http://redis.io/commands/getbit
1022
  *
1023
  * @author Daniele Alessandri <suppakilla@gmail.com>
1024
  */
1025
- class StringGetBit extends Command
1026
  {
1027
  /**
1028
  * {@inheritdoc}
1029
  */
1030
  public function getId()
1031
  {
1032
- return 'GETBIT';
1033
  }
1034
  }
1035
 
1036
  /**
1037
- * @link http://redis.io/commands/mget
1038
  *
1039
  * @author Daniele Alessandri <suppakilla@gmail.com>
1040
  */
1041
- class StringGetMultiple extends Command
1042
  {
1043
  /**
1044
  * {@inheritdoc}
1045
  */
1046
  public function getId()
1047
  {
1048
- return 'MGET';
1049
  }
1050
 
1051
  /**
@@ -1053,231 +1034,291 @@ class StringGetMultiple extends Command
1053
  */
1054
  protected function filterArguments(array $arguments)
1055
  {
1056
- return self::normalizeArguments($arguments);
 
 
 
 
 
 
1057
  }
1058
  }
1059
 
1060
  /**
1061
- * @link http://redis.io/commands/incrbyfloat
1062
  *
1063
  * @author Daniele Alessandri <suppakilla@gmail.com>
1064
  */
1065
- class StringIncrementByFloat extends Command
1066
  {
1067
  /**
1068
  * {@inheritdoc}
1069
  */
1070
  public function getId()
1071
  {
1072
- return 'INCRBYFLOAT';
1073
  }
1074
  }
1075
 
1076
  /**
1077
- * @link http://redis.io/commands/psetex
1078
  *
1079
  * @author Daniele Alessandri <suppakilla@gmail.com>
1080
  */
1081
- class StringPreciseSetExpire extends StringSetExpire
1082
  {
1083
  /**
1084
  * {@inheritdoc}
1085
  */
1086
  public function getId()
1087
  {
1088
- return 'PSETEX';
1089
  }
1090
  }
1091
 
1092
  /**
1093
- * @link http://redis.io/commands/set
1094
  *
1095
  * @author Daniele Alessandri <suppakilla@gmail.com>
1096
  */
1097
- class StringSet extends Command
1098
  {
1099
  /**
1100
  * {@inheritdoc}
1101
  */
1102
  public function getId()
1103
  {
1104
- return 'SET';
1105
  }
1106
  }
1107
 
1108
  /**
1109
- * @link http://redis.io/commands/incrby
1110
  *
1111
  * @author Daniele Alessandri <suppakilla@gmail.com>
1112
  */
1113
- class StringIncrementBy extends Command
1114
  {
1115
  /**
1116
  * {@inheritdoc}
1117
  */
1118
  public function getId()
1119
  {
1120
- return 'INCRBY';
1121
  }
1122
  }
1123
 
1124
  /**
1125
- * @link http://redis.io/commands/incr
1126
  *
1127
  * @author Daniele Alessandri <suppakilla@gmail.com>
1128
  */
1129
- class StringIncrement extends Command
1130
  {
1131
  /**
1132
  * {@inheritdoc}
1133
  */
1134
  public function getId()
1135
  {
1136
- return 'INCR';
1137
  }
1138
  }
1139
 
1140
  /**
1141
- * @link http://redis.io/commands/getrange
1142
  *
1143
  * @author Daniele Alessandri <suppakilla@gmail.com>
1144
  */
1145
- class StringGetRange extends Command
1146
  {
1147
  /**
1148
  * {@inheritdoc}
1149
  */
1150
  public function getId()
1151
  {
1152
- return 'GETRANGE';
 
 
 
 
 
 
 
 
1153
  }
1154
  }
1155
 
1156
  /**
1157
- * @link http://redis.io/commands/getset
1158
  *
1159
  * @author Daniele Alessandri <suppakilla@gmail.com>
1160
  */
1161
- class StringGetSet extends Command
1162
  {
1163
  /**
1164
  * {@inheritdoc}
1165
  */
1166
  public function getId()
1167
  {
1168
- return 'GETSET';
1169
  }
1170
  }
1171
 
1172
  /**
1173
- * @link http://redis.io/commands/sunionstore
1174
  *
1175
  * @author Daniele Alessandri <suppakilla@gmail.com>
1176
  */
1177
- class SetUnionStore extends SetIntersectionStore
1178
  {
1179
  /**
1180
  * {@inheritdoc}
1181
  */
1182
  public function getId()
1183
  {
1184
- return 'SUNIONSTORE';
1185
  }
1186
  }
1187
 
1188
  /**
1189
- * @link http://redis.io/commands/sunion
1190
  *
1191
  * @author Daniele Alessandri <suppakilla@gmail.com>
1192
  */
1193
- class SetUnion extends SetIntersection
1194
  {
1195
  /**
1196
  * {@inheritdoc}
1197
  */
1198
  public function getId()
1199
  {
1200
- return 'SUNION';
1201
  }
1202
  }
1203
 
1204
  /**
1205
- * @link http://redis.io/commands/sdiff
1206
  *
1207
  * @author Daniele Alessandri <suppakilla@gmail.com>
1208
  */
1209
- class SetDifference extends SetIntersection
1210
  {
1211
  /**
1212
  * {@inheritdoc}
1213
  */
1214
  public function getId()
1215
  {
1216
- return 'SDIFF';
1217
  }
1218
  }
1219
 
1220
  /**
1221
- * @link http://redis.io/commands/sdiffstore
1222
  *
1223
  * @author Daniele Alessandri <suppakilla@gmail.com>
1224
  */
1225
- class SetDifferenceStore extends SetIntersectionStore
1226
  {
1227
  /**
1228
  * {@inheritdoc}
1229
  */
1230
  public function getId()
1231
  {
1232
- return 'SDIFFSTORE';
 
 
 
 
 
 
 
 
1233
  }
1234
  }
1235
 
1236
  /**
1237
- * @link http://redis.io/commands/hget
1238
  *
1239
  * @author Daniele Alessandri <suppakilla@gmail.com>
1240
  */
1241
- class HashGet extends Command
1242
  {
1243
  /**
1244
  * {@inheritdoc}
1245
  */
1246
  public function getId()
1247
  {
1248
- return 'HGET';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1249
  }
1250
  }
1251
 
1252
  /**
1253
- * @link http://redis.io/commands/scard
1254
  *
1255
  * @author Daniele Alessandri <suppakilla@gmail.com>
1256
  */
1257
- class SetCardinality extends Command
1258
  {
1259
  /**
1260
  * {@inheritdoc}
1261
  */
1262
  public function getId()
1263
  {
1264
- return 'SCARD';
1265
  }
1266
  }
1267
 
1268
  /**
1269
- * @link http://redis.io/commands/sadd
1270
  *
1271
  * @author Daniele Alessandri <suppakilla@gmail.com>
1272
  */
1273
- class SetAdd extends Command
1274
  {
1275
  /**
1276
  * {@inheritdoc}
1277
  */
1278
  public function getId()
1279
  {
1280
- return 'SADD';
1281
  }
1282
 
1283
  /**
@@ -1285,7 +1326,11 @@ class SetAdd extends Command
1285
  */
1286
  protected function filterArguments(array $arguments)
1287
  {
1288
- return self::normalizeVariadic($arguments);
 
 
 
 
1289
  }
1290
  }
1291
 
@@ -1345,90 +1390,106 @@ class ServerTime extends Command
1345
  }
1346
 
1347
  /**
1348
- * @link http://redis.io/commands/hexists
1349
  *
1350
  * @author Daniele Alessandri <suppakilla@gmail.com>
1351
  */
1352
- class HashExists extends Command
1353
  {
1354
  /**
1355
  * {@inheritdoc}
1356
  */
1357
  public function getId()
1358
  {
1359
- return 'HEXISTS';
1360
  }
1361
 
1362
  /**
1363
  * {@inheritdoc}
1364
  */
1365
- public function parseResponse($data)
1366
  {
1367
- return (bool) $data;
1368
  }
1369
  }
1370
 
1371
  /**
1372
- * @link http://redis.io/commands/sismember
1373
  *
1374
  * @author Daniele Alessandri <suppakilla@gmail.com>
1375
  */
1376
- class SetIsMember extends Command
1377
  {
1378
  /**
1379
  * {@inheritdoc}
1380
  */
1381
  public function getId()
1382
  {
1383
- return 'SISMEMBER';
1384
  }
 
1385
 
 
 
 
 
 
 
 
1386
  /**
1387
  * {@inheritdoc}
1388
  */
1389
- public function parseResponse($data)
1390
  {
1391
- return (bool) $data;
1392
  }
1393
  }
1394
 
1395
  /**
1396
- * @link http://redis.io/commands/srem
1397
  *
1398
  * @author Daniele Alessandri <suppakilla@gmail.com>
1399
  */
1400
- class SetRemove extends Command
1401
  {
1402
  /**
1403
  * {@inheritdoc}
1404
  */
1405
  public function getId()
1406
  {
1407
- return 'SREM';
1408
  }
 
1409
 
 
 
 
 
 
 
 
1410
  /**
1411
  * {@inheritdoc}
1412
  */
1413
- protected function filterArguments(array $arguments)
1414
  {
1415
- return self::normalizeVariadic($arguments);
1416
  }
1417
  }
1418
 
1419
  /**
1420
- * @link http://redis.io/commands/sscan
1421
  *
1422
  * @author Daniele Alessandri <suppakilla@gmail.com>
1423
  */
1424
- class SetScan extends Command
1425
  {
1426
  /**
1427
  * {@inheritdoc}
1428
  */
1429
  public function getId()
1430
  {
1431
- return 'SSCAN';
1432
  }
1433
 
1434
  /**
@@ -1436,69 +1497,29 @@ class SetScan extends Command
1436
  */
1437
  protected function filterArguments(array $arguments)
1438
  {
1439
- if (count($arguments) === 3 && is_array($arguments[2])) {
1440
- $options = $this->prepareOptions(array_pop($arguments));
1441
- $arguments = array_merge($arguments, $options);
 
1442
  }
1443
 
1444
  return $arguments;
1445
  }
1446
-
1447
- /**
1448
- * Returns a list of options and modifiers compatible with Redis.
1449
- *
1450
- * @param array $options List of options.
1451
- *
1452
- * @return array
1453
- */
1454
- protected function prepareOptions($options)
1455
- {
1456
- $options = array_change_key_case($options, CASE_UPPER);
1457
- $normalized = array();
1458
-
1459
- if (!empty($options['MATCH'])) {
1460
- $normalized[] = 'MATCH';
1461
- $normalized[] = $options['MATCH'];
1462
- }
1463
-
1464
- if (!empty($options['COUNT'])) {
1465
- $normalized[] = 'COUNT';
1466
- $normalized[] = $options['COUNT'];
1467
- }
1468
-
1469
- return $normalized;
1470
- }
1471
- }
1472
-
1473
- /**
1474
- * @link http://redis.io/commands/srandmember
1475
- *
1476
- * @author Daniele Alessandri <suppakilla@gmail.com>
1477
- */
1478
- class SetRandomMember extends Command
1479
- {
1480
- /**
1481
- * {@inheritdoc}
1482
- */
1483
- public function getId()
1484
- {
1485
- return 'SRANDMEMBER';
1486
- }
1487
  }
1488
 
1489
  /**
1490
- * @link http://redis.io/commands/spop
1491
  *
1492
  * @author Daniele Alessandri <suppakilla@gmail.com>
1493
  */
1494
- class SetPop extends Command
1495
  {
1496
  /**
1497
  * {@inheritdoc}
1498
  */
1499
  public function getId()
1500
  {
1501
- return 'SPOP';
1502
  }
1503
  }
1504
 
@@ -1532,125 +1553,155 @@ class SetMove extends Command
1532
  {
1533
  return 'SMOVE';
1534
  }
1535
-
1536
- /**
1537
- * {@inheritdoc}
1538
- */
1539
- public function parseResponse($data)
1540
- {
1541
- return (bool) $data;
1542
- }
1543
  }
1544
 
1545
  /**
1546
- * @link http://redis.io/commands/setbit
1547
  *
1548
  * @author Daniele Alessandri <suppakilla@gmail.com>
1549
  */
1550
- class StringSetBit extends Command
1551
  {
1552
  /**
1553
  * {@inheritdoc}
1554
  */
1555
  public function getId()
1556
  {
1557
- return 'SETBIT';
1558
  }
1559
  }
1560
 
1561
  /**
1562
- * @link http://redis.io/commands/hdel
1563
  *
1564
  * @author Daniele Alessandri <suppakilla@gmail.com>
1565
  */
1566
- class HashDelete extends Command
1567
  {
1568
  /**
1569
  * {@inheritdoc}
1570
  */
1571
  public function getId()
1572
  {
1573
- return 'HDEL';
1574
- }
1575
-
1576
- /**
1577
- * {@inheritdoc}
1578
- */
1579
- protected function filterArguments(array $arguments)
1580
- {
1581
- return self::normalizeVariadic($arguments);
1582
  }
1583
  }
1584
 
1585
  /**
1586
- * @link http://redis.io/commands/zrem
1587
  *
1588
  * @author Daniele Alessandri <suppakilla@gmail.com>
1589
  */
1590
- class ZSetRemove extends Command
1591
  {
1592
  /**
1593
  * {@inheritdoc}
1594
  */
1595
  public function getId()
1596
  {
1597
- return 'ZREM';
1598
- }
1599
-
1600
- /**
1601
- * {@inheritdoc}
1602
- */
1603
- protected function filterArguments(array $arguments)
1604
- {
1605
- return self::normalizeVariadic($arguments);
1606
  }
1607
  }
1608
 
1609
  /**
1610
- * @link http://redis.io/commands/zremrangebylex
1611
  *
1612
  * @author Daniele Alessandri <suppakilla@gmail.com>
1613
  */
1614
- class ZSetRemoveRangeByLex extends Command
1615
  {
1616
  /**
1617
  * {@inheritdoc}
1618
  */
1619
  public function getId()
1620
  {
1621
- return 'ZREMRANGEBYLEX';
1622
  }
1623
  }
1624
 
1625
  /**
1626
- * @link http://redis.io/commands/zremrangebyrank
1627
  *
1628
  * @author Daniele Alessandri <suppakilla@gmail.com>
1629
  */
1630
- class ZSetRemoveRangeByRank extends Command
1631
  {
1632
  /**
1633
  * {@inheritdoc}
1634
  */
1635
  public function getId()
1636
  {
1637
- return 'ZREMRANGEBYRANK';
1638
  }
1639
- }
1640
 
1641
- /**
1642
- * @link http://redis.io/commands/zrank
1643
- *
1644
- * @author Daniele Alessandri <suppakilla@gmail.com>
1645
- */
1646
- class ZSetRank extends Command
1647
- {
1648
  /**
1649
  * {@inheritdoc}
1650
  */
1651
- public function getId()
1652
  {
1653
- return 'ZRANK';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1654
  }
1655
  }
1656
 
@@ -1671,34 +1722,74 @@ class ConnectionEcho extends Command
1671
  }
1672
 
1673
  /**
1674
- * @link http://redis.io/commands/quit
1675
  *
1676
  * @author Daniele Alessandri <suppakilla@gmail.com>
1677
  */
1678
- class ConnectionQuit extends Command
1679
  {
1680
  /**
1681
  * {@inheritdoc}
1682
  */
1683
  public function getId()
1684
  {
1685
- return 'QUIT';
1686
  }
1687
  }
1688
 
1689
  /**
1690
- * @link http://redis.io/commands/ping
1691
  *
1692
  * @author Daniele Alessandri <suppakilla@gmail.com>
1693
  */
1694
- class ConnectionPing extends Command
1695
  {
1696
  /**
1697
  * {@inheritdoc}
1698
  */
1699
  public function getId()
1700
  {
1701
- return 'PING';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1702
  }
1703
  }
1704
 
@@ -1718,6 +1809,22 @@ class ZSetRemoveRangeByScore extends Command
1718
  }
1719
  }
1720
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1721
  /**
1722
  * @link http://redis.io/commands/zrevrange
1723
  *
@@ -1735,34 +1842,50 @@ class ZSetReverseRange extends ZSetRange
1735
  }
1736
 
1737
  /**
1738
- * @link http://redis.io/commands/zscore
1739
  *
1740
  * @author Daniele Alessandri <suppakilla@gmail.com>
1741
  */
1742
- class ZSetScore extends Command
1743
  {
1744
  /**
1745
  * {@inheritdoc}
1746
  */
1747
  public function getId()
1748
  {
1749
- return 'ZSCORE';
1750
  }
1751
  }
1752
 
1753
  /**
1754
- * @link http://redis.io/commands/auth
1755
  *
1756
  * @author Daniele Alessandri <suppakilla@gmail.com>
1757
  */
1758
- class ConnectionAuth extends Command
1759
  {
1760
  /**
1761
  * {@inheritdoc}
1762
  */
1763
  public function getId()
1764
  {
1765
- return 'AUTH';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1766
  }
1767
  }
1768
 
@@ -1840,141 +1963,146 @@ class ZSetScan extends Command
1840
  }
1841
 
1842
  /**
1843
- * @link http://redis.io/commands/zrevrank
1844
  *
1845
  * @author Daniele Alessandri <suppakilla@gmail.com>
1846
  */
1847
- class ZSetReverseRank extends Command
1848
  {
1849
  /**
1850
  * {@inheritdoc}
1851
  */
1852
  public function getId()
1853
  {
1854
- return 'ZREVRANK';
1855
  }
1856
  }
1857
 
1858
- class ZSetReverseRangeByLex extends ZSetRangeByLex
 
 
 
 
 
1859
  {
1860
  /**
1861
  * {@inheritdoc}
1862
  */
1863
  public function getId()
1864
  {
1865
- return 'ZREVRANGEBYLEX';
1866
  }
1867
  }
1868
 
1869
  /**
1870
- * @link http://redis.io/commands/zrevrangebyscore
1871
  *
1872
  * @author Daniele Alessandri <suppakilla@gmail.com>
1873
  */
1874
- class ZSetReverseRangeByScore extends ZSetRangeByScore
1875
  {
1876
  /**
1877
  * {@inheritdoc}
1878
  */
1879
  public function getId()
1880
  {
1881
- return 'ZREVRANGEBYSCORE';
1882
  }
1883
  }
1884
 
1885
  /**
1886
- * @link http://redis.io/commands/zlexcount
1887
  *
1888
  * @author Daniele Alessandri <suppakilla@gmail.com>
1889
  */
1890
- class ZSetLexCount extends Command
1891
  {
1892
  /**
1893
  * {@inheritdoc}
1894
  */
1895
  public function getId()
1896
  {
1897
- return 'ZLEXCOUNT';
1898
  }
1899
  }
1900
 
1901
  /**
1902
- * @link http://redis.io/commands/zinterstore
1903
  *
1904
  * @author Daniele Alessandri <suppakilla@gmail.com>
1905
  */
1906
- class ZSetIntersectionStore extends ZSetUnionStore
1907
  {
1908
  /**
1909
  * {@inheritdoc}
1910
  */
1911
  public function getId()
1912
  {
1913
- return 'ZINTERSTORE';
1914
  }
1915
  }
1916
 
1917
  /**
1918
- * @link http://redis.io/commands/strlen
1919
  *
1920
  * @author Daniele Alessandri <suppakilla@gmail.com>
1921
  */
1922
- class StringStrlen extends Command
1923
  {
1924
  /**
1925
  * {@inheritdoc}
1926
  */
1927
  public function getId()
1928
  {
1929
- return 'STRLEN';
1930
  }
1931
  }
1932
 
1933
  /**
1934
- * @link http://redis.io/commands/substr
1935
  *
1936
  * @author Daniele Alessandri <suppakilla@gmail.com>
1937
  */
1938
- class StringSubstr extends Command
1939
  {
1940
  /**
1941
  * {@inheritdoc}
1942
  */
1943
  public function getId()
1944
  {
1945
- return 'SUBSTR';
1946
  }
1947
  }
1948
 
1949
  /**
1950
- * @link http://redis.io/commands/discard
1951
  *
1952
  * @author Daniele Alessandri <suppakilla@gmail.com>
1953
  */
1954
- class TransactionDiscard extends Command
1955
  {
1956
  /**
1957
  * {@inheritdoc}
1958
  */
1959
  public function getId()
1960
  {
1961
- return 'DISCARD';
1962
  }
1963
  }
1964
 
1965
  /**
1966
- * @link http://redis.io/commands/setrange
1967
  *
1968
  * @author Daniele Alessandri <suppakilla@gmail.com>
1969
  */
1970
- class StringSetRange extends Command
1971
  {
1972
  /**
1973
  * {@inheritdoc}
1974
  */
1975
  public function getId()
1976
  {
1977
- return 'SETRANGE';
1978
  }
1979
  }
1980
 
@@ -1992,53 +2120,69 @@ class StringSetPreserve extends Command
1992
  {
1993
  return 'SETNX';
1994
  }
 
1995
 
 
 
 
 
 
 
 
1996
  /**
1997
  * {@inheritdoc}
1998
  */
1999
- public function parseResponse($data)
2000
  {
2001
- return (bool) $data;
2002
  }
2003
  }
2004
 
2005
  /**
2006
- * @link http://redis.io/commands/select
2007
  *
2008
  * @author Daniele Alessandri <suppakilla@gmail.com>
2009
  */
2010
- class ConnectionSelect extends Command
2011
  {
2012
  /**
2013
  * {@inheritdoc}
2014
  */
2015
  public function getId()
2016
  {
2017
- return 'SELECT';
2018
  }
2019
  }
2020
 
2021
  /**
2022
- * @link http://redis.io/commands/msetnx
2023
  *
2024
  * @author Daniele Alessandri <suppakilla@gmail.com>
2025
  */
2026
- class StringSetMultiplePreserve extends StringSetMultiple
2027
  {
2028
  /**
2029
  * {@inheritdoc}
2030
  */
2031
  public function getId()
2032
  {
2033
- return 'MSETNX';
2034
  }
 
2035
 
 
 
 
 
 
 
 
2036
  /**
2037
  * {@inheritdoc}
2038
  */
2039
- public function parseResponse($data)
2040
  {
2041
- return (bool) $data;
2042
  }
2043
  }
2044
 
@@ -2075,50 +2219,46 @@ class TransactionMulti extends Command
2075
  }
2076
 
2077
  /**
2078
- * @link http://redis.io/commands/zcount
2079
  *
2080
  * @author Daniele Alessandri <suppakilla@gmail.com>
2081
  */
2082
- class ZSetCount extends Command
2083
  {
2084
  /**
2085
  * {@inheritdoc}
2086
  */
2087
  public function getId()
2088
  {
2089
- return 'ZCOUNT';
2090
  }
2091
  }
2092
 
2093
  /**
2094
- * @link http://redis.io/commands/zincrby
2095
  *
2096
  * @author Daniele Alessandri <suppakilla@gmail.com>
2097
  */
2098
- class ZSetIncrementBy extends Command
2099
  {
2100
  /**
2101
  * {@inheritdoc}
2102
  */
2103
  public function getId()
2104
  {
2105
- return 'ZINCRBY';
2106
  }
2107
- }
2108
 
2109
- /**
2110
- * @link http://redis.io/commands/zcard
2111
- *
2112
- * @author Daniele Alessandri <suppakilla@gmail.com>
2113
- */
2114
- class ZSetCardinality extends Command
2115
- {
2116
  /**
2117
  * {@inheritdoc}
2118
  */
2119
- public function getId()
2120
  {
2121
- return 'ZCARD';
 
 
 
 
2122
  }
2123
  }
2124
 
@@ -2154,34 +2294,66 @@ class ZSetAdd extends Command
2154
  }
2155
 
2156
  /**
2157
- * @link http://redis.io/commands/unwatch
2158
  *
2159
  * @author Daniele Alessandri <suppakilla@gmail.com>
2160
  */
2161
- class TransactionUnwatch extends Command
2162
  {
2163
  /**
2164
  * {@inheritdoc}
2165
  */
2166
  public function getId()
2167
  {
2168
- return 'UNWATCH';
2169
  }
2170
  }
2171
 
2172
  /**
2173
- * @link http://redis.io/commands/watch
2174
  *
2175
  * @author Daniele Alessandri <suppakilla@gmail.com>
2176
  */
2177
- class TransactionWatch extends Command
2178
  {
2179
  /**
2180
  * {@inheritdoc}
2181
  */
2182
  public function getId()
2183
  {
2184
- return 'WATCH';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2185
  }
2186
 
2187
  /**
@@ -2189,27 +2361,72 @@ class TransactionWatch extends Command
2189
  */
2190
  protected function filterArguments(array $arguments)
2191
  {
2192
- if (isset($arguments[0]) && is_array($arguments[0])) {
2193
- return $arguments[0];
 
2194
  }
2195
 
2196
  return $arguments;
2197
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2198
  }
2199
 
2200
  /**
2201
- * @link http://redis.io/commands/slaveof
2202
  *
2203
  * @author Daniele Alessandri <suppakilla@gmail.com>
2204
  */
2205
- class ServerSlaveOf extends Command
2206
  {
2207
  /**
2208
  * {@inheritdoc}
2209
  */
2210
  public function getId()
2211
  {
2212
- return 'SLAVEOF';
2213
  }
2214
 
2215
  /**
@@ -2217,43 +2434,85 @@ class ServerSlaveOf extends Command
2217
  */
2218
  protected function filterArguments(array $arguments)
2219
  {
2220
- if (count($arguments) === 0 || $arguments[0] === 'NO ONE') {
2221
- return array('NO', 'ONE');
 
2222
  }
2223
 
2224
  return $arguments;
2225
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2226
  }
2227
 
2228
  /**
2229
- * @link http://redis.io/commands/shutdown
2230
  *
2231
  * @author Daniele Alessandri <suppakilla@gmail.com>
2232
  */
2233
- class ServerShutdown extends Command
2234
  {
2235
  /**
2236
  * {@inheritdoc}
2237
  */
2238
  public function getId()
2239
  {
2240
- return 'SHUTDOWN';
2241
  }
2242
  }
2243
 
2244
  /**
2245
- * @link http://redis.io/commands/hset
2246
  *
2247
  * @author Daniele Alessandri <suppakilla@gmail.com>
2248
  */
2249
- class HashSet extends Command
2250
  {
2251
  /**
2252
  * {@inheritdoc}
2253
  */
2254
  public function getId()
2255
  {
2256
- return 'HSET';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2257
  }
2258
 
2259
  /**
@@ -2261,39 +2520,55 @@ class HashSet extends Command
2261
  */
2262
  public function parseResponse($data)
2263
  {
2264
- return (bool) $data;
2265
  }
2266
  }
2267
 
2268
  /**
2269
- * @link http://redis.io/commands/type
2270
  *
2271
  * @author Daniele Alessandri <suppakilla@gmail.com>
2272
  */
2273
- class KeyType extends Command
2274
  {
2275
  /**
2276
  * {@inheritdoc}
2277
  */
2278
  public function getId()
2279
  {
2280
- return 'TYPE';
2281
  }
2282
  }
2283
 
2284
  /**
2285
- * @link http://redis.io/commands/lindex
2286
  *
2287
  * @author Daniele Alessandri <suppakilla@gmail.com>
2288
  */
2289
- class ListIndex extends Command
2290
  {
2291
  /**
2292
  * {@inheritdoc}
2293
  */
2294
  public function getId()
2295
  {
2296
- return 'LINDEX';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2297
  }
2298
  }
2299
 
@@ -2369,96 +2644,74 @@ class KeySort extends Command
2369
  }
2370
 
2371
  /**
2372
- * @link http://redis.io/commands/scan
2373
  *
2374
  * @author Daniele Alessandri <suppakilla@gmail.com>
2375
  */
2376
- class KeyScan extends Command
2377
  {
2378
  /**
2379
  * {@inheritdoc}
2380
  */
2381
  public function getId()
2382
  {
2383
- return 'SCAN';
2384
  }
 
2385
 
 
 
 
 
 
 
 
2386
  /**
2387
  * {@inheritdoc}
2388
  */
2389
- protected function filterArguments(array $arguments)
2390
  {
2391
- if (count($arguments) === 2 && is_array($arguments[1])) {
2392
- $options = $this->prepareOptions(array_pop($arguments));
2393
- $arguments = array_merge($arguments, $options);
2394
- }
2395
-
2396
- return $arguments;
2397
  }
2398
 
2399
  /**
2400
- * Returns a list of options and modifiers compatible with Redis.
2401
- *
2402
- * @param array $options List of options.
2403
- *
2404
- * @return array
2405
  */
2406
- protected function prepareOptions($options)
2407
  {
2408
- $options = array_change_key_case($options, CASE_UPPER);
2409
- $normalized = array();
2410
-
2411
- if (!empty($options['MATCH'])) {
2412
- $normalized[] = 'MATCH';
2413
- $normalized[] = $options['MATCH'];
2414
- }
2415
-
2416
- if (!empty($options['COUNT'])) {
2417
- $normalized[] = 'COUNT';
2418
- $normalized[] = $options['COUNT'];
2419
- }
2420
-
2421
- return $normalized;
2422
  }
2423
  }
2424
 
2425
  /**
2426
- * @link http://redis.io/commands/renamenx
2427
  *
2428
  * @author Daniele Alessandri <suppakilla@gmail.com>
2429
  */
2430
- class KeyRenamePreserve extends KeyRename
2431
  {
2432
  /**
2433
  * {@inheritdoc}
2434
  */
2435
  public function getId()
2436
  {
2437
- return 'RENAMENX';
2438
- }
2439
-
2440
- /**
2441
- * {@inheritdoc}
2442
- */
2443
- public function parseResponse($data)
2444
- {
2445
- return (bool) $data;
2446
  }
2447
  }
2448
 
2449
  /**
2450
- * @link http://redis.io/commands/restore
2451
  *
2452
  * @author Daniele Alessandri <suppakilla@gmail.com>
2453
  */
2454
- class KeyRestore extends Command
2455
  {
2456
  /**
2457
  * {@inheritdoc}
2458
  */
2459
  public function getId()
2460
  {
2461
- return 'RESTORE';
2462
  }
2463
  }
2464
 
@@ -2495,171 +2748,106 @@ class ListLength extends Command
2495
  }
2496
 
2497
  /**
2498
- * @link http://redis.io/commands/rpoplpush
2499
  *
2500
  * @author Daniele Alessandri <suppakilla@gmail.com>
2501
  */
2502
- class ListPopLastPushHead extends Command
2503
  {
2504
  /**
2505
  * {@inheritdoc}
2506
  */
2507
  public function getId()
2508
  {
2509
- return 'RPOPLPUSH';
2510
  }
2511
  }
2512
 
2513
  /**
2514
- * @link http://redis.io/commands/brpoplpush
2515
  *
2516
  * @author Daniele Alessandri <suppakilla@gmail.com>
2517
  */
2518
- class ListPopLastPushHeadBlocking extends Command
2519
  {
2520
  /**
2521
  * {@inheritdoc}
2522
  */
2523
  public function getId()
2524
  {
2525
- return 'BRPOPLPUSH';
2526
  }
2527
  }
2528
 
2529
  /**
2530
- * @link http://redis.io/commands/lpush
2531
  *
2532
  * @author Daniele Alessandri <suppakilla@gmail.com>
2533
  */
2534
- class ListPushHead extends ListPushTail
2535
  {
2536
  /**
2537
  * {@inheritdoc}
2538
  */
2539
  public function getId()
2540
  {
2541
- return 'LPUSH';
2542
  }
2543
  }
2544
 
2545
  /**
2546
- * @link http://redis.io/commands/brpop
2547
  *
2548
  * @author Daniele Alessandri <suppakilla@gmail.com>
2549
  */
2550
- class ListPopLastBlocking extends ListPopFirstBlocking
2551
  {
2552
  /**
2553
  * {@inheritdoc}
2554
  */
2555
  public function getId()
2556
  {
2557
- return 'BRPOP';
2558
  }
2559
  }
2560
 
2561
  /**
2562
- * @link http://redis.io/commands/rpop
2563
  *
2564
  * @author Daniele Alessandri <suppakilla@gmail.com>
2565
  */
2566
- class ListPopLast extends Command
2567
  {
2568
  /**
2569
  * {@inheritdoc}
2570
  */
2571
  public function getId()
2572
  {
2573
- return 'RPOP';
2574
  }
2575
- }
2576
 
2577
- /**
2578
- * @link http://redis.io/commands/lpop
2579
- *
2580
- * @author Daniele Alessandri <suppakilla@gmail.com>
2581
- */
2582
- class ListPopFirst extends Command
2583
- {
2584
  /**
2585
  * {@inheritdoc}
2586
  */
2587
- public function getId()
2588
  {
2589
- return 'LPOP';
2590
  }
2591
  }
2592
 
2593
  /**
2594
- * @link http://redis.io/commands/hscan
2595
  *
2596
  * @author Daniele Alessandri <suppakilla@gmail.com>
2597
  */
2598
- class HashScan extends Command
2599
  {
2600
  /**
2601
  * {@inheritdoc}
2602
  */
2603
  public function getId()
2604
  {
2605
- return 'HSCAN';
2606
- }
2607
-
2608
- /**
2609
- * {@inheritdoc}
2610
- */
2611
- protected function filterArguments(array $arguments)
2612
- {
2613
- if (count($arguments) === 3 && is_array($arguments[2])) {
2614
- $options = $this->prepareOptions(array_pop($arguments));
2615
- $arguments = array_merge($arguments, $options);
2616
- }
2617
-
2618
- return $arguments;
2619
- }
2620
-
2621
- /**
2622
- * Returns a list of options and modifiers compatible with Redis.
2623
- *
2624
- * @param array $options List of options.
2625
- *
2626
- * @return array
2627
- */
2628
- protected function prepareOptions($options)
2629
- {
2630
- $options = array_change_key_case($options, CASE_UPPER);
2631
- $normalized = array();
2632
-
2633
- if (!empty($options['MATCH'])) {
2634
- $normalized[] = 'MATCH';
2635
- $normalized[] = $options['MATCH'];
2636
- }
2637
-
2638
- if (!empty($options['COUNT'])) {
2639
- $normalized[] = 'COUNT';
2640
- $normalized[] = $options['COUNT'];
2641
- }
2642
-
2643
- return $normalized;
2644
- }
2645
-
2646
- /**
2647
- * {@inheritdoc}
2648
- */
2649
- public function parseResponse($data)
2650
- {
2651
- if (is_array($data)) {
2652
- $fields = $data[1];
2653
- $result = array();
2654
-
2655
- for ($i = 0; $i < count($fields); ++$i) {
2656
- $result[$fields[$i]] = $fields[++$i];
2657
- }
2658
-
2659
- $data[1] = $result;
2660
- }
2661
-
2662
- return $data;
2663
  }
2664
  }
2665
 
@@ -2700,98 +2888,66 @@ class HashSetMultiple extends Command
2700
  }
2701
 
2702
  /**
2703
- * @link http://redis.io/commands/randomkey
2704
- *
2705
- * @author Daniele Alessandri <suppakilla@gmail.com>
2706
- */
2707
- class KeyRandom extends Command
2708
- {
2709
- /**
2710
- * {@inheritdoc}
2711
- */
2712
- public function getId()
2713
- {
2714
- return 'RANDOMKEY';
2715
- }
2716
-
2717
- /**
2718
- * {@inheritdoc}
2719
- */
2720
- public function parseResponse($data)
2721
- {
2722
- return $data !== '' ? $data : null;
2723
- }
2724
- }
2725
-
2726
- /**
2727
- * @link http://redis.io/commands/dump
2728
  *
2729
  * @author Daniele Alessandri <suppakilla@gmail.com>
2730
  */
2731
- class KeyDump extends Command
2732
  {
2733
  /**
2734
  * {@inheritdoc}
2735
  */
2736
  public function getId()
2737
  {
2738
- return 'DUMP';
2739
  }
2740
  }
2741
 
2742
  /**
2743
- * @link http://redis.io/commands/exists
2744
  *
2745
  * @author Daniele Alessandri <suppakilla@gmail.com>
2746
  */
2747
- class KeyExists extends Command
2748
  {
2749
  /**
2750
  * {@inheritdoc}
2751
  */
2752
  public function getId()
2753
  {
2754
- return 'EXISTS';
2755
- }
2756
-
2757
- /**
2758
- * {@inheritdoc}
2759
- */
2760
- public function parseResponse($data)
2761
- {
2762
- return (bool) $data;
2763
  }
2764
  }
2765
 
2766
  /**
2767
- * @link http://redis.io/commands/hstrlen
2768
  *
2769
  * @author Daniele Alessandri <suppakilla@gmail.com>
2770
  */
2771
- class HashStringLength extends Command
2772
  {
2773
  /**
2774
  * {@inheritdoc}
2775
  */
2776
  public function getId()
2777
  {
2778
- return 'HSTRLEN';
2779
  }
2780
  }
2781
 
2782
  /**
2783
- * @link http://redis.io/commands/del
2784
  *
2785
  * @author Daniele Alessandri <suppakilla@gmail.com>
2786
  */
2787
- class KeyDelete extends Command
2788
  {
2789
  /**
2790
  * {@inheritdoc}
2791
  */
2792
  public function getId()
2793
  {
2794
- return 'DEL';
2795
  }
2796
 
2797
  /**
@@ -2799,7 +2955,7 @@ class KeyDelete extends Command
2799
  */
2800
  protected function filterArguments(array $arguments)
2801
  {
2802
- return self::normalizeArguments($arguments);
2803
  }
2804
  }
2805
 
@@ -2828,18 +2984,18 @@ class HyperLogLogMerge extends Command
2828
  }
2829
 
2830
  /**
2831
- * @link http://redis.io/commands/pfadd
2832
  *
2833
  * @author Daniele Alessandri <suppakilla@gmail.com>
2834
  */
2835
- class HyperLogLogAdd extends Command
2836
  {
2837
  /**
2838
  * {@inheritdoc}
2839
  */
2840
  public function getId()
2841
  {
2842
- return 'PFADD';
2843
  }
2844
 
2845
  /**
@@ -2847,31 +3003,37 @@ class HyperLogLogAdd extends Command
2847
  */
2848
  protected function filterArguments(array $arguments)
2849
  {
2850
- return self::normalizeVariadic($arguments);
2851
- }
 
2852
 
2853
- /**
2854
- * {@inheritdoc}
2855
- */
2856
- public function parseResponse($data)
2857
- {
2858
- return (bool) $data;
 
 
 
 
 
2859
  }
2860
  }
2861
 
2862
  /**
2863
- * @link http://redis.io/commands/pfcount
2864
  *
2865
  * @author Daniele Alessandri <suppakilla@gmail.com>
2866
  */
2867
- class HyperLogLogCount extends Command
2868
  {
2869
  /**
2870
  * {@inheritdoc}
2871
  */
2872
  public function getId()
2873
  {
2874
- return 'PFCOUNT';
2875
  }
2876
 
2877
  /**
@@ -2884,106 +3046,98 @@ class HyperLogLogCount extends Command
2884
  }
2885
 
2886
  /**
2887
- * @link http://redis.io/commands/hsetnx
2888
  *
2889
  * @author Daniele Alessandri <suppakilla@gmail.com>
2890
  */
2891
- class HashSetPreserve extends Command
2892
  {
2893
  /**
2894
  * {@inheritdoc}
2895
  */
2896
  public function getId()
2897
  {
2898
- return 'HSETNX';
2899
- }
2900
-
2901
- /**
2902
- * {@inheritdoc}
2903
- */
2904
- public function parseResponse($data)
2905
- {
2906
- return (bool) $data;
2907
  }
2908
  }
2909
 
2910
  /**
2911
- * @link http://redis.io/commands/keys
2912
  *
2913
  * @author Daniele Alessandri <suppakilla@gmail.com>
2914
  */
2915
- class KeyKeys extends Command
2916
  {
2917
  /**
2918
  * {@inheritdoc}
2919
  */
2920
  public function getId()
2921
  {
2922
- return 'KEYS';
2923
  }
2924
  }
2925
 
2926
  /**
2927
- * @link http://redis.io/commands/pexpireat
2928
  *
2929
  * @author Daniele Alessandri <suppakilla@gmail.com>
2930
  */
2931
- class KeyPreciseExpireAt extends KeyExpireAt
2932
  {
2933
  /**
2934
  * {@inheritdoc}
2935
  */
2936
  public function getId()
2937
  {
2938
- return 'PEXPIREAT';
2939
  }
2940
  }
2941
 
2942
  /**
2943
- * @link http://redis.io/commands/pttl
2944
  *
2945
  * @author Daniele Alessandri <suppakilla@gmail.com>
2946
  */
2947
- class KeyPreciseTimeToLive extends KeyTimeToLive
2948
  {
2949
  /**
2950
  * {@inheritdoc}
2951
  */
2952
  public function getId()
2953
  {
2954
- return 'PTTL';
2955
  }
2956
  }
2957
 
2958
  /**
2959
- * @link http://redis.io/commands/pexpire
2960
  *
2961
  * @author Daniele Alessandri <suppakilla@gmail.com>
2962
  */
2963
- class KeyPreciseExpire extends KeyExpire
2964
  {
2965
  /**
2966
  * {@inheritdoc}
2967
  */
2968
  public function getId()
2969
  {
2970
- return 'PEXPIRE';
2971
  }
2972
  }
2973
 
2974
  /**
2975
- * @link http://redis.io/commands/persist
2976
  *
2977
  * @author Daniele Alessandri <suppakilla@gmail.com>
2978
  */
2979
- class KeyPersist extends Command
2980
  {
2981
  /**
2982
  * {@inheritdoc}
2983
  */
2984
  public function getId()
2985
  {
2986
- return 'PERSIST';
2987
  }
2988
 
2989
  /**
@@ -2991,189 +3145,184 @@ class KeyPersist extends Command
2991
  */
2992
  public function parseResponse($data)
2993
  {
2994
- return (bool) $data;
2995
- }
2996
- }
 
 
 
 
 
 
2997
 
2998
  /**
2999
- * @link http://redis.io/commands/migrate
3000
  *
3001
  * @author Daniele Alessandri <suppakilla@gmail.com>
3002
  */
3003
- class KeyMigrate extends Command
3004
  {
3005
  /**
3006
  * {@inheritdoc}
3007
  */
3008
  public function getId()
3009
  {
3010
- return 'MIGRATE';
3011
- }
3012
-
3013
- /**
3014
- * {@inheritdoc}
3015
- */
3016
- protected function filterArguments(array $arguments)
3017
- {
3018
- if (is_array(end($arguments))) {
3019
- foreach (array_pop($arguments) as $modifier => $value) {
3020
- $modifier = strtoupper($modifier);
3021
-
3022
- if ($modifier === 'COPY' && $value == true) {
3023
- $arguments[] = $modifier;
3024
- }
3025
-
3026
- if ($modifier === 'REPLACE' && $value == true) {
3027
- $arguments[] = $modifier;
3028
- }
3029
- }
3030
- }
3031
-
3032
- return $arguments;
3033
  }
3034
  }
3035
 
3036
  /**
3037
- * @link http://redis.io/commands/move
3038
  *
3039
  * @author Daniele Alessandri <suppakilla@gmail.com>
3040
  */
3041
- class KeyMove extends Command
3042
  {
3043
  /**
3044
  * {@inheritdoc}
3045
  */
3046
  public function getId()
3047
  {
3048
- return 'MOVE';
3049
- }
3050
-
3051
- /**
3052
- * {@inheritdoc}
3053
- */
3054
- public function parseResponse($data)
3055
- {
3056
- return (bool) $data;
3057
  }
3058
  }
3059
 
3060
  /**
3061
- * @link http://redis.io/commands/lpushx
3062
  *
3063
  * @author Daniele Alessandri <suppakilla@gmail.com>
3064
  */
3065
- class ListPushHeadX extends Command
3066
  {
3067
  /**
3068
  * {@inheritdoc}
3069
  */
3070
  public function getId()
3071
  {
3072
- return 'LPUSHX';
3073
  }
3074
- }
3075
 
3076
- /**
3077
- * @link http://redis.io/commands/hlen
3078
- *
3079
- * @author Daniele Alessandri <suppakilla@gmail.com>
3080
- */
3081
- class HashLength extends Command
3082
- {
3083
  /**
3084
  * {@inheritdoc}
3085
  */
3086
- public function getId()
3087
  {
3088
- return 'HLEN';
 
 
 
 
 
3089
  }
3090
  }
3091
 
3092
  /**
3093
- * @link http://redis.io/commands/hmget
3094
  *
3095
  * @author Daniele Alessandri <suppakilla@gmail.com>
3096
  */
3097
- class HashGetMultiple extends Command
3098
  {
3099
  /**
3100
  * {@inheritdoc}
3101
  */
3102
  public function getId()
3103
  {
3104
- return 'HMGET';
3105
  }
3106
 
3107
  /**
3108
  * {@inheritdoc}
3109
  */
3110
- protected function filterArguments(array $arguments)
3111
  {
3112
- return self::normalizeVariadic($arguments);
3113
  }
3114
  }
3115
 
3116
  /**
3117
- * @link http://redis.io/commands/flushall
3118
  *
3119
  * @author Daniele Alessandri <suppakilla@gmail.com>
3120
  */
3121
- class ServerFlushAll extends Command
3122
  {
3123
  /**
3124
  * {@inheritdoc}
3125
  */
3126
  public function getId()
3127
  {
3128
- return 'FLUSHALL';
3129
  }
3130
- }
3131
 
3132
- /**
3133
- * @link http://redis.io/commands/flushdb
3134
- *
3135
- * @author Daniele Alessandri <suppakilla@gmail.com>
3136
- */
3137
- class ServerFlushDatabase extends Command
3138
- {
3139
  /**
3140
  * {@inheritdoc}
3141
  */
3142
- public function getId()
3143
  {
3144
- return 'FLUSHDB';
3145
  }
3146
  }
3147
 
3148
  /**
3149
- * @link http://redis.io/commands/hincrby
 
 
 
3150
  *
3151
  * @author Daniele Alessandri <suppakilla@gmail.com>
3152
  */
3153
- class HashIncrementBy extends Command
3154
  {
3155
  /**
3156
  * {@inheritdoc}
3157
  */
3158
  public function getId()
3159
  {
3160
- return 'HINCRBY';
3161
  }
3162
- }
3163
 
3164
- /**
3165
- * @link http://redis.io/commands/dbsize
3166
- *
3167
- * @author Daniele Alessandri <suppakilla@gmail.com>
3168
- */
3169
- class ServerDatabaseSize extends Command
3170
- {
3171
  /**
3172
  * {@inheritdoc}
3173
  */
3174
- public function getId()
3175
  {
3176
- return 'DBSIZE';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3177
  }
3178
  }
3179
 
@@ -3231,359 +3380,387 @@ class ServerConfig extends Command
3231
  }
3232
 
3233
  /**
3234
- * @link http://redis.io/commands/hgetall
3235
  *
3236
  * @author Daniele Alessandri <suppakilla@gmail.com>
3237
  */
3238
- class HashGetAll extends Command
3239
  {
3240
  /**
3241
  * {@inheritdoc}
3242
  */
3243
  public function getId()
3244
  {
3245
- return 'HGETALL';
3246
- }
3247
-
3248
- /**
3249
- * {@inheritdoc}
3250
- */
3251
- public function parseResponse($data)
3252
- {
3253
- $result = array();
3254
-
3255
- for ($i = 0; $i < count($data); ++$i) {
3256
- $result[$data[$i]] = $data[++$i];
3257
- }
3258
-
3259
- return $result;
3260
  }
3261
  }
3262
 
3263
  /**
3264
- * @link http://redis.io/commands/info
3265
  *
3266
  * @author Daniele Alessandri <suppakilla@gmail.com>
3267
  */
3268
- class ServerInfoV26x extends ServerInfo
3269
  {
3270
  /**
3271
  * {@inheritdoc}
3272
  */
3273
- public function parseResponse($data)
3274
  {
3275
- if ($data === '') {
3276
- return array();
3277
- }
3278
-
3279
- $info = array();
3280
-
3281
- $current = null;
3282
- $infoLines = preg_split('/\r?\n/', $data);
3283
-
3284
- if (isset($infoLines[0]) && $infoLines[0][0] !== '#') {
3285
- return parent::parseResponse($data);
3286
- }
3287
-
3288
- foreach ($infoLines as $row) {
3289
- if ($row === '') {
3290
- continue;
3291
- }
3292
-
3293
- if (preg_match('/^# (\w+)$/', $row, $matches)) {
3294
- $info[$matches[1]] = array();
3295
- $current = &$info[$matches[1]];
3296
- continue;
3297
- }
3298
-
3299
- list($k, $v) = $this->parseRow($row);
3300
- $current[$k] = $v;
3301
- }
3302
-
3303
- return $info;
3304
  }
3305
  }
3306
 
3307
  /**
3308
- * @link http://redis.io/commands/script
 
 
 
 
 
3309
  *
3310
  * @author Daniele Alessandri <suppakilla@gmail.com>
3311
  */
3312
- class ServerScript extends Command
3313
  {
 
 
 
 
3314
  /**
3315
- * {@inheritdoc}
 
 
3316
  */
3317
- public function getId()
3318
  {
3319
- return 'SCRIPT';
 
 
 
 
 
 
 
3320
  }
3321
- }
3322
 
3323
- /**
3324
- * @link http://redis.io/topics/sentinel
3325
- *
3326
- * @author Daniele Alessandri <suppakilla@gmail.com>
3327
- */
3328
- class ServerSentinel extends Command
3329
- {
3330
  /**
3331
- * {@inheritdoc}
 
 
 
 
 
3332
  */
3333
- public function getId()
3334
  {
3335
- return 'SENTINEL';
 
 
 
3336
  }
3337
 
3338
  /**
3339
  * {@inheritdoc}
3340
  */
3341
- public function parseResponse($data)
3342
  {
3343
- switch (strtolower($this->getArgument(0))) {
3344
- case 'masters':
3345
- case 'slaves':
3346
- return self::processMastersOrSlaves($data);
3347
 
3348
- default:
3349
- return $data;
3350
- }
 
 
 
 
3351
  }
3352
 
3353
  /**
3354
- * Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES.
3355
- *
3356
- * @param array $servers List of Redis servers.
3357
- *
3358
- * @return array
3359
  */
3360
- protected static function processMastersOrSlaves(array $servers)
3361
  {
3362
- foreach ($servers as $idx => $node) {
3363
- $processed = array();
3364
- $count = count($node);
3365
 
3366
- for ($i = 0; $i < $count; ++$i) {
3367
- $processed[$node[$i]] = $node[++$i];
3368
- }
 
 
 
 
3369
 
3370
- $servers[$idx] = $processed;
 
 
 
 
 
 
3371
  }
3372
-
3373
- return $servers;
3374
  }
3375
- }
3376
 
3377
- /**
3378
- * @link http://redis.io/commands/save
3379
- *
3380
- * @author Daniele Alessandri <suppakilla@gmail.com>
3381
- */
3382
- class ServerSave extends Command
3383
- {
3384
  /**
3385
  * {@inheritdoc}
3386
  */
3387
- public function getId()
3388
  {
3389
- return 'SAVE';
3390
  }
3391
- }
3392
 
3393
- /**
3394
- * @link http://redis.io/commands/object
3395
- *
3396
- * @author Daniele Alessandri <suppakilla@gmail.com>
3397
- */
3398
- class ServerObject extends Command
3399
- {
3400
  /**
3401
  * {@inheritdoc}
3402
  */
3403
- public function getId()
3404
  {
3405
- return 'OBJECT';
 
 
3406
  }
3407
- }
3408
 
3409
- /**
3410
- * @link http://redis.io/commands/lastsave
3411
- *
3412
- * @author Daniele Alessandri <suppakilla@gmail.com>
3413
- */
3414
- class ServerLastSave extends Command
3415
- {
3416
  /**
3417
  * {@inheritdoc}
3418
  */
3419
- public function getId()
3420
  {
3421
- return 'LASTSAVE';
3422
  }
3423
  }
3424
 
3425
  /**
3426
- * @link http://redis.io/commands/monitor
3427
  *
3428
  * @author Daniele Alessandri <suppakilla@gmail.com>
3429
  */
3430
- class ServerMonitor extends Command
3431
  {
3432
  /**
3433
  * {@inheritdoc}
3434
  */
3435
  public function getId()
3436
  {
3437
- return 'MONITOR';
3438
  }
3439
  }
3440
 
3441
  /**
3442
- * @link http://redis.io/commands/client-list
3443
- * @link http://redis.io/commands/client-kill
3444
- * @link http://redis.io/commands/client-getname
3445
- * @link http://redis.io/commands/client-setname
3446
  *
3447
  * @author Daniele Alessandri <suppakilla@gmail.com>
3448
  */
3449
- class ServerClient extends Command
3450
  {
3451
  /**
3452
  * {@inheritdoc}
3453
  */
3454
  public function getId()
3455
  {
3456
- return 'CLIENT';
3457
  }
3458
 
3459
  /**
3460
  * {@inheritdoc}
3461
  */
3462
- public function parseResponse($data)
3463
  {
3464
- $args = array_change_key_case($this->getArguments(), CASE_UPPER);
3465
-
3466
- switch (strtoupper($args[0])) {
3467
- case 'LIST':
3468
- return $this->parseClientList($data);
3469
- case 'KILL':
3470
- case 'GETNAME':
3471
- case 'SETNAME':
3472
- default:
3473
- return $data;
3474
  }
 
 
3475
  }
 
3476
 
 
 
 
 
 
 
 
3477
  /**
3478
- * Parses the response to CLIENT LIST and returns a structured list.
3479
- *
3480
- * @param string $data Response buffer.
3481
- *
3482
- * @return array
3483
  */
3484
- protected function parseClientList($data)
3485
  {
3486
- $clients = array();
 
 
3487
 
3488
- foreach (explode("\n", $data, -1) as $clientData) {
3489
- $client = array();
3490
 
3491
- foreach (explode(' ', $clientData) as $kv) {
3492
- @list($k, $v) = explode('=', $kv);
3493
- $client[$k] = $v;
 
 
 
 
 
 
 
3494
  }
3495
 
3496
- $clients[] = $client;
 
 
 
 
 
 
 
3497
  }
3498
 
3499
- return $clients;
3500
  }
3501
  }
3502
 
3503
  /**
3504
- * @link http://redis.io/commands/bgsave
3505
  *
3506
  * @author Daniele Alessandri <suppakilla@gmail.com>
3507
  */
3508
- class ServerBackgroundSave extends Command
3509
  {
3510
  /**
3511
  * {@inheritdoc}
3512
  */
3513
  public function getId()
3514
  {
3515
- return 'BGSAVE';
3516
  }
 
3517
 
 
 
 
 
 
 
 
3518
  /**
3519
  * {@inheritdoc}
3520
  */
3521
- public function parseResponse($data)
3522
  {
3523
- return $data === 'Background saving started' ? true : $data;
3524
  }
3525
  }
3526
 
3527
  /**
3528
- * @link http://redis.io/commands/ltrim
3529
  *
3530
  * @author Daniele Alessandri <suppakilla@gmail.com>
3531
  */
3532
- class ListTrim extends Command
3533
  {
3534
  /**
3535
  * {@inheritdoc}
3536
  */
3537
  public function getId()
3538
  {
3539
- return 'LTRIM';
3540
  }
3541
  }
3542
 
3543
  /**
3544
- * Defines a command whose keys can be prefixed.
 
 
 
3545
  *
3546
  * @author Daniele Alessandri <suppakilla@gmail.com>
3547
  */
3548
- interface PrefixableCommandInterface extends CommandInterface
3549
  {
3550
  /**
3551
- * Prefixes all the keys found in the arguments of the command.
3552
  *
3553
- * @param string $prefix String used to prefix the keys.
3554
  */
3555
- public function prefixKeys($prefix);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3556
  }
3557
 
3558
  /**
3559
- * @link http://redis.io/commands/publish
3560
  *
3561
  * @author Daniele Alessandri <suppakilla@gmail.com>
3562
  */
3563
- class PubSubPublish extends Command
3564
  {
3565
  /**
3566
  * {@inheritdoc}
3567
  */
3568
  public function getId()
3569
  {
3570
- return 'PUBLISH';
3571
  }
3572
  }
3573
 
3574
  /**
3575
- * @link http://redis.io/commands/lset
3576
  *
3577
  * @author Daniele Alessandri <suppakilla@gmail.com>
3578
  */
3579
- class ListSet extends Command
3580
  {
3581
  /**
3582
  * {@inheritdoc}
3583
  */
3584
  public function getId()
3585
  {
3586
- return 'LSET';
3587
  }
3588
  }
3589
 
@@ -3604,141 +3781,130 @@ class ListRemove extends Command
3604
  }
3605
 
3606
  /**
3607
- * @link http://redis.io/commands/rpushx
3608
  *
3609
  * @author Daniele Alessandri <suppakilla@gmail.com>
3610
  */
3611
- class ListPushTailX extends Command
3612
  {
3613
  /**
3614
  * {@inheritdoc}
3615
  */
3616
  public function getId()
3617
  {
3618
- return 'RPUSHX';
3619
  }
3620
  }
3621
 
3622
  /**
3623
- * @link http://redis.io/commands/lrange
3624
  *
3625
  * @author Daniele Alessandri <suppakilla@gmail.com>
3626
  */
3627
- class ListRange extends Command
3628
  {
3629
  /**
3630
  * {@inheritdoc}
3631
  */
3632
  public function getId()
3633
  {
3634
- return 'LRANGE';
3635
  }
3636
  }
3637
 
3638
  /**
3639
- * @link http://redis.io/commands/pubsub
3640
  *
3641
  * @author Daniele Alessandri <suppakilla@gmail.com>
3642
  */
3643
- class PubSubPubsub extends Command
3644
  {
3645
  /**
3646
  * {@inheritdoc}
3647
  */
3648
  public function getId()
3649
  {
3650
- return 'PUBSUB';
3651
  }
 
3652
 
 
 
 
 
 
 
 
3653
  /**
3654
  * {@inheritdoc}
3655
  */
3656
- public function parseResponse($data)
3657
  {
3658
- switch (strtolower($this->getArgument(0))) {
3659
- case 'numsub':
3660
- return self::processNumsub($data);
3661
-
3662
- default:
3663
- return $data;
3664
- }
3665
  }
 
3666
 
 
 
 
 
 
 
 
3667
  /**
3668
- * Returns the processed response to PUBSUB NUMSUB.
3669
- *
3670
- * @param array $channels List of channels
3671
- *
3672
- * @return array
3673
  */
3674
- protected static function processNumsub(array $channels)
3675
  {
3676
- $processed = array();
3677
- $count = count($channels);
3678
-
3679
- for ($i = 0; $i < $count; ++$i) {
3680
- $processed[$channels[$i]] = $channels[++$i];
3681
- }
3682
-
3683
- return $processed;
3684
  }
3685
  }
3686
 
3687
  /**
3688
- * @link http://redis.io/commands/hkeys
3689
  *
3690
  * @author Daniele Alessandri <suppakilla@gmail.com>
3691
  */
3692
- class HashKeys extends Command
3693
  {
3694
  /**
3695
  * {@inheritdoc}
3696
  */
3697
  public function getId()
3698
  {
3699
- return 'HKEYS';
3700
  }
3701
  }
3702
 
3703
  /**
3704
- * Base class used to implement an higher level abstraction for commands based
3705
- * on Lua scripting with EVAL and EVALSHA.
3706
- *
3707
- * @link http://redis.io/commands/eval
3708
  *
3709
  * @author Daniele Alessandri <suppakilla@gmail.com>
3710
  */
3711
- abstract class ScriptCommand extends ServerEvalSHA
3712
  {
3713
  /**
3714
- * Gets the body of a Lua script.
3715
- *
3716
- * @return string
3717
- */
3718
- abstract public function getScript();
3719
-
3720
- /**
3721
- * Specifies the number of arguments that should be considered as keys.
3722
- *
3723
- * The default behaviour for the base class is to return 0 to indicate that
3724
- * all the elements of the arguments array should be considered as keys, but
3725
- * subclasses can enforce a static number of keys.
3726
- *
3727
- * @return int
3728
  */
3729
- protected function getKeysCount()
3730
  {
3731
- return 0;
3732
  }
 
3733
 
 
 
 
 
 
 
 
3734
  /**
3735
- * Returns the elements from the arguments that are identified as keys.
3736
- *
3737
- * @return array
3738
  */
3739
- public function getKeys()
3740
  {
3741
- return array_slice($this->getArguments(), 2, $this->getKeysCount());
3742
  }
3743
 
3744
  /**
@@ -3746,231 +3912,153 @@ abstract class ScriptCommand extends ServerEvalSHA
3746
  */
3747
  protected function filterArguments(array $arguments)
3748
  {
3749
- if (($numkeys = $this->getKeysCount()) && $numkeys < 0) {
3750
- $numkeys = count($arguments) + $numkeys;
3751
- }
3752
-
3753
- return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments);
3754
  }
 
3755
 
 
 
 
 
 
 
 
3756
  /**
3757
- * @return array
3758
  */
3759
- public function getEvalArguments()
3760
  {
3761
- $arguments = $this->getArguments();
3762
- $arguments[0] = $this->getScript();
3763
-
3764
- return $arguments;
3765
  }
3766
  }
3767
 
3768
  /**
3769
- * @link http://redis.io/commands/bgrewriteaof
3770
  *
3771
  * @author Daniele Alessandri <suppakilla@gmail.com>
3772
  */
3773
- class ServerBackgroundRewriteAOF extends Command
3774
  {
3775
  /**
3776
- * {@inheritdoc}
 
 
3777
  */
3778
- public function getId()
3779
- {
3780
- return 'BGREWRITEAOF';
3781
- }
3782
 
 
 
 
 
 
 
 
3783
  /**
3784
  * {@inheritdoc}
3785
  */
3786
- public function parseResponse($data)
3787
  {
3788
- return $data == 'Background append only file rewriting started';
3789
  }
3790
  }
3791
 
3792
  /**
3793
- * Class for generic "anonymous" Redis commands.
3794
- *
3795
- * This command class does not filter input arguments or parse responses, but
3796
- * can be used to leverage the standard Predis API to execute any command simply
3797
- * by providing the needed arguments following the command signature as defined
3798
- * by Redis in its documentation.
3799
  *
3800
  * @author Daniele Alessandri <suppakilla@gmail.com>
3801
  */
3802
- class RawCommand implements CommandInterface
3803
  {
3804
- private $slot;
3805
- private $commandID;
3806
- private $arguments;
 
 
 
 
3807
 
3808
  /**
3809
- * @param array $arguments Command ID and its arguments.
3810
- *
3811
- * @throws \InvalidArgumentException
3812
  */
3813
- public function __construct(array $arguments)
3814
  {
3815
- if (!$arguments) {
3816
- throw new \InvalidArgumentException(
3817
- 'The arguments array must contain at least the command ID.'
3818
- );
3819
- }
3820
 
3821
- $this->commandID = strtoupper(array_shift($arguments));
3822
- $this->arguments = $arguments;
 
3823
  }
3824
 
3825
  /**
3826
- * Creates a new raw command using a variadic method.
3827
  *
3828
- * @param string $commandID Redis command ID.
3829
- * @param string ... Arguments list for the command.
3830
  *
3831
- * @return CommandInterface
3832
  */
3833
- public static function create($commandID /* [ $arg, ... */)
3834
  {
3835
- $arguments = func_get_args();
3836
- $command = new self($arguments);
3837
 
3838
- return $command;
 
 
 
 
3839
  }
 
3840
 
 
 
 
 
 
 
 
3841
  /**
3842
  * {@inheritdoc}
3843
  */
3844
  public function getId()
3845
  {
3846
- return $this->commandID;
3847
  }
 
3848
 
 
 
 
 
 
 
 
3849
  /**
3850
  * {@inheritdoc}
3851
  */
3852
- public function setArguments(array $arguments)
3853
  {
3854
- $this->arguments = $arguments;
3855
- unset($this->slot);
3856
  }
 
3857
 
 
 
 
 
 
 
 
3858
  /**
3859
  * {@inheritdoc}
3860
  */
3861
- public function setRawArguments(array $arguments)
3862
  {
3863
- $this->setArguments($arguments);
3864
  }
3865
-
3866
- /**
3867
- * {@inheritdoc}
3868
- */
3869
- public function getArguments()
3870
- {
3871
- return $this->arguments;
3872
- }
3873
-
3874
- /**
3875
- * {@inheritdoc}
3876
- */
3877
- public function getArgument($index)
3878
- {
3879
- if (isset($this->arguments[$index])) {
3880
- return $this->arguments[$index];
3881
- }
3882
- }
3883
-
3884
- /**
3885
- * {@inheritdoc}
3886
- */
3887
- public function setSlot($slot)
3888
- {
3889
- $this->slot = $slot;
3890
- }
3891
-
3892
- /**
3893
- * {@inheritdoc}
3894
- */
3895
- public function getSlot()
3896
- {
3897
- if (isset($this->slot)) {
3898
- return $this->slot;
3899
- }
3900
- }
3901
-
3902
- /**
3903
- * {@inheritdoc}
3904
- */
3905
- public function parseResponse($data)
3906
- {
3907
- return $data;
3908
- }
3909
- }
3910
-
3911
- /**
3912
- * @link http://redis.io/commands/punsubscribe
3913
- *
3914
- * @author Daniele Alessandri <suppakilla@gmail.com>
3915
- */
3916
- class PubSubUnsubscribeByPattern extends PubSubUnsubscribe
3917
- {
3918
- /**
3919
- * {@inheritdoc}
3920
- */
3921
- public function getId()
3922
- {
3923
- return 'PUNSUBSCRIBE';
3924
- }
3925
- }
3926
-
3927
- /**
3928
- * @link http://redis.io/commands/psubscribe
3929
- *
3930
- * @author Daniele Alessandri <suppakilla@gmail.com>
3931
- */
3932
- class PubSubSubscribeByPattern extends PubSubSubscribe
3933
- {
3934
- /**
3935
- * {@inheritdoc}
3936
- */
3937
- public function getId()
3938
- {
3939
- return 'PSUBSCRIBE';
3940
- }
3941
- }
3942
-
3943
- /**
3944
- * @link http://redis.io/commands/hincrbyfloat
3945
- *
3946
- * @author Daniele Alessandri <suppakilla@gmail.com>
3947
- */
3948
- class HashIncrementByFloat extends Command
3949
- {
3950
- /**
3951
- * {@inheritdoc}
3952
- */
3953
- public function getId()
3954
- {
3955
- return 'HINCRBYFLOAT';
3956
- }
3957
- }
3958
-
3959
- /**
3960
- * @link http://redis.io/commands/hvals
3961
- *
3962
- * @author Daniele Alessandri <suppakilla@gmail.com>
3963
- */
3964
- class HashValues extends Command
3965
- {
3966
- /**
3967
- * {@inheritdoc}
3968
- */
3969
- public function getId()
3970
- {
3971
- return 'HVALS';
3972
- }
3973
- }
3974
 
3975
  /* --------------------------------------------------------------------------- */
3976
 
@@ -3984,6 +4072,7 @@ use Predis\Protocol\Text\ProtocolProcessor as TextProtocolProcessor;
3984
  use Predis\Command\RawCommand;
3985
  use Predis\NotSupportedException;
3986
  use Predis\Response\Error as ErrorResponse;
 
3987
  use Predis\Response\Status as StatusResponse;
3988
 
3989
  /**
@@ -4165,20 +4254,7 @@ abstract class AbstractConnection implements NodeConnectionInterface
4165
  *
4166
  * @return ParametersInterface
4167
  */
4168
- protected function assertParameters(ParametersInterface $parameters)
4169
- {
4170
- switch ($parameters->scheme) {
4171
- case 'tcp':
4172
- case 'redis':
4173
- case 'unix':
4174
- break;
4175
-
4176
- default:
4177
- throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'.");
4178
- }
4179
-
4180
- return $parameters;
4181
- }
4182
 
4183
  /**
4184
  * Creates the underlying resource used to communicate with Redis.
@@ -4352,15 +4428,16 @@ abstract class AbstractConnection implements NodeConnectionInterface
4352
  * Standard connection to Redis servers implemented on top of PHP's streams.
4353
  * The connection parameters supported by this class are:.
4354
  *
4355
- * - scheme: it can be either 'redis', 'tcp' or 'unix'.
4356
  * - host: hostname or IP address of the server.
4357
  * - port: TCP port of the server.
4358
  * - path: path of a UNIX domain socket when scheme is 'unix'.
4359
- * - timeout: timeout to perform the connection.
4360
  * - read_write_timeout: timeout of read / write operations.
4361
  * - async_connect: performs the connection asynchronously.
4362
  * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
4363
  * - persistent: the connection is left intact after a GC collection.
 
4364
  *
4365
  * @author Daniele Alessandri <suppakilla@gmail.com>
4366
  */
@@ -4383,50 +4460,79 @@ class StreamConnection extends AbstractConnection
4383
  /**
4384
  * {@inheritdoc}
4385
  */
4386
- protected function createResource()
4387
  {
4388
- switch ($this->parameters->scheme) {
4389
  case 'tcp':
4390
  case 'redis':
4391
- return $this->tcpStreamInitializer($this->parameters);
4392
-
4393
  case 'unix':
4394
- return $this->unixStreamInitializer($this->parameters);
 
 
 
 
 
4395
 
4396
  default:
4397
- throw new \InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'.");
4398
  }
 
 
4399
  }
4400
 
4401
  /**
4402
- * Initializes a TCP stream resource.
4403
  *
4404
  * @param ParametersInterface $parameters Initialization parameters for the connection.
4405
  *
4406
- * @return resource
4407
  */
4408
- protected function tcpStreamInitializer(ParametersInterface $parameters)
4409
  {
4410
- if (!filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
4411
- $uri = "tcp://$parameters->host:$parameters->port";
4412
- } else {
4413
- $uri = "tcp://[$parameters->host]:$parameters->port";
 
4414
  }
 
4415
 
4416
- $flags = STREAM_CLIENT_CONNECT;
 
 
 
 
 
 
 
 
4417
 
4418
- if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
4419
- $flags |= STREAM_CLIENT_ASYNC_CONNECT;
4420
- }
 
 
 
4421
 
4422
- if (isset($parameters->persistent) && (bool) $parameters->persistent) {
4423
- $flags |= STREAM_CLIENT_PERSISTENT;
4424
- $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
4425
  }
 
4426
 
4427
- $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
 
 
 
 
 
 
 
 
 
 
 
4428
 
4429
- if (!$resource) {
4430
  $this->onConnectionError(trim($errstr), $errno);
4431
  }
4432
 
@@ -4446,6 +4552,42 @@ class StreamConnection extends AbstractConnection
4446
  return $resource;
4447
  }
4448
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4449
  /**
4450
  * Initializes a UNIX stream resource.
4451
  *
@@ -4456,28 +4598,61 @@ class StreamConnection extends AbstractConnection
4456
  protected function unixStreamInitializer(ParametersInterface $parameters)
4457
  {
4458
  if (!isset($parameters->path)) {
4459
- throw new InvalidArgumentException('Missing UNIX domain socket path.');
4460
  }
4461
 
4462
- $uri = "unix://{$parameters->path}";
4463
  $flags = STREAM_CLIENT_CONNECT;
4464
 
4465
- if ((bool) $parameters->persistent) {
4466
- $flags |= STREAM_CLIENT_PERSISTENT;
 
 
 
 
 
 
 
 
4467
  }
4468
 
4469
- $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4470
 
4471
- if (!$resource) {
4472
- $this->onConnectionError(trim($errstr), $errno);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4473
  }
4474
 
4475
- if (isset($parameters->read_write_timeout)) {
4476
- $rwtimeout = (float) $parameters->read_write_timeout;
4477
- $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
4478
- $timeoutSeconds = floor($rwtimeout);
4479
- $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
4480
- stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
 
 
 
 
 
 
 
 
 
 
4481
  }
4482
 
4483
  return $resource;
@@ -4490,7 +4665,11 @@ class StreamConnection extends AbstractConnection
4490
  {
4491
  if (parent::connect() && $this->initCommands) {
4492
  foreach ($this->initCommands as $command) {
4493
- $this->executeCommand($command);
 
 
 
 
4494
  }
4495
  }
4496
  }
@@ -4589,7 +4768,8 @@ class StreamConnection extends AbstractConnection
4589
  return $multibulk;
4590
 
4591
  case ':':
4592
- return (int) $payload;
 
4593
 
4594
  case '-':
4595
  return new ErrorResponse($payload);
@@ -4614,8 +4794,7 @@ class StreamConnection extends AbstractConnection
4614
 
4615
  $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
4616
 
4617
- for ($i = 0, $reqlen--; $i < $reqlen; ++$i) {
4618
- $argument = $arguments[$i];
4619
  $arglen = strlen($argument);
4620
  $buffer .= "\${$arglen}\r\n{$argument}\r\n";
4621
  }
@@ -4625,53 +4804,40 @@ class StreamConnection extends AbstractConnection
4625
  }
4626
 
4627
  /**
4628
- * Interface defining a container for connection parameters.
4629
- *
4630
- * The actual list of connection parameters depends on the features supported by
4631
- * each connection backend class (please refer to their specific documentation),
4632
- * but the most common parameters used through the library are:
4633
- *
4634
- * @property-read string scheme Connection scheme, such as 'tcp' or 'unix'.
4635
- * @property-read string host IP address or hostname of Redis.
4636
- * @property-read int port TCP port on which Redis is listening to.
4637
- * @property-read string path Path of a UNIX domain socket file.
4638
- * @property-read string alias Alias for the connection.
4639
- * @property-read float timeout Timeout for the connect() operation.
4640
- * @property-read float read_write_timeout Timeout for read() and write() operations.
4641
- * @property-read bool async_connect Performs the connect() operation asynchronously.
4642
- * @property-read bool tcp_nodelay Toggles the Nagle's algorithm for coalescing.
4643
- * @property-read bool persistent Leaves the connection open after a GC collection.
4644
- * @property-read string password Password to access Redis (see the AUTH command).
4645
- * @property-read string database Database index (see the SELECT command).
4646
  *
4647
  * @author Daniele Alessandri <suppakilla@gmail.com>
4648
  */
4649
- interface ParametersInterface
4650
  {
4651
  /**
4652
- * Checks if the specified parameters is set.
4653
- *
4654
- * @param string $parameter Name of the parameter.
 
 
 
4655
  *
4656
- * @return bool
4657
  */
4658
- public function __isset($parameter);
4659
 
4660
  /**
4661
- * Returns the value of the specified parameter.
4662
  *
4663
- * @param string $parameter Name of the parameter.
4664
  *
4665
- * @return mixed|null
4666
  */
4667
- public function __get($parameter);
4668
 
4669
  /**
4670
- * Returns an array representation of the connection parameters.
4671
  *
4672
- * @return array
4673
  */
4674
- public function toArray();
4675
  }
4676
 
4677
  /**
@@ -4715,203 +4881,145 @@ interface FactoryInterface
4715
  }
4716
 
4717
  /**
4718
- * Defines a connection to communicate with a single Redis server that leverages
4719
- * an external protocol processor to handle pluggable protocol handlers.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4720
  *
4721
  * @author Daniele Alessandri <suppakilla@gmail.com>
4722
  */
4723
- interface CompositeConnectionInterface extends NodeConnectionInterface
4724
  {
4725
  /**
4726
- * Returns the protocol processor used by the connection.
4727
- */
4728
- public function getProtocol();
4729
-
4730
- /**
4731
- * Writes the buffer containing over the connection.
4732
  *
4733
- * @param string $buffer String buffer to be sent over the connection.
 
 
4734
  */
4735
- public function writeBuffer($buffer);
4736
 
4737
  /**
4738
- * Reads the given number of bytes from the connection.
4739
  *
4740
- * @param int $length Number of bytes to read from the connection.
4741
  *
4742
- * @return string
4743
  */
4744
- public function readBuffer($length);
4745
 
4746
  /**
4747
- * Reads a line from the connection.
4748
  *
4749
- * @param string
4750
  */
4751
- public function readLine();
4752
  }
4753
 
4754
  /**
4755
- * This class provides the implementation of a Predis connection that uses PHP's
4756
- * streams for network communication and wraps the phpiredis C extension (PHP
4757
- * bindings for hiredis) to parse and serialize the Redis protocol.
4758
- *
4759
- * This class is intended to provide an optional low-overhead alternative for
4760
- * processing responses from Redis compared to the standard pure-PHP classes.
4761
- * Differences in speed when dealing with short inline responses are practically
4762
- * nonexistent, the actual speed boost is for big multibulk responses when this
4763
- * protocol processor can parse and return responses very fast.
4764
- *
4765
- * For instructions on how to build and install the phpiredis extension, please
4766
- * consult the repository of the project.
4767
- *
4768
- * The connection parameters supported by this class are:
4769
- *
4770
- * - scheme: it can be either 'redis', 'tcp' or 'unix'.
4771
- * - host: hostname or IP address of the server.
4772
- * - port: TCP port of the server.
4773
- * - path: path of a UNIX domain socket when scheme is 'unix'.
4774
- * - timeout: timeout to perform the connection.
4775
- * - read_write_timeout: timeout of read / write operations.
4776
- * - async_connect: performs the connection asynchronously.
4777
- * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
4778
- * - persistent: the connection is left intact after a GC collection.
4779
- *
4780
- * @link https://github.com/nrk/phpiredis
4781
  *
4782
  * @author Daniele Alessandri <suppakilla@gmail.com>
4783
  */
4784
- class PhpiredisStreamConnection extends StreamConnection
4785
  {
4786
- private $reader;
4787
 
4788
  /**
4789
- * {@inheritdoc}
 
4790
  */
4791
- public function __construct(ParametersInterface $parameters)
4792
- {
4793
- $this->assertExtensions();
4794
-
4795
- parent::__construct($parameters);
4796
-
4797
- $this->reader = $this->createReader();
4798
  }
4799
 
4800
  /**
4801
  * {@inheritdoc}
4802
  */
4803
- public function __destruct()
4804
  {
4805
- phpiredis_reader_destroy($this->reader);
4806
-
4807
- parent::__destruct();
4808
  }
4809
 
4810
  /**
4811
- * Checks if the phpiredis extension is loaded in PHP.
4812
  */
4813
- private function assertExtensions()
4814
  {
4815
- if (!extension_loaded('phpiredis')) {
4816
- throw new NotSupportedException(
4817
- 'The "phpiredis" extension is required by this connection backend.'
4818
- );
4819
- }
4820
  }
4821
 
4822
  /**
4823
  * {@inheritdoc}
4824
  */
4825
- protected function tcpStreamInitializer(ParametersInterface $parameters)
4826
  {
4827
- $uri = "tcp://[{$parameters->host}]:{$parameters->port}";
4828
- $flags = STREAM_CLIENT_CONNECT;
4829
- $socket = null;
4830
-
4831
- if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
4832
- $flags |= STREAM_CLIENT_ASYNC_CONNECT;
4833
- }
4834
-
4835
- if (isset($parameters->persistent) && (bool) $parameters->persistent) {
4836
- $flags |= STREAM_CLIENT_PERSISTENT;
4837
- $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
4838
- }
4839
-
4840
- $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4841
-
4842
- if (!$resource) {
4843
- $this->onConnectionError(trim($errstr), $errno);
4844
  }
4845
 
4846
- if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
4847
- $rwtimeout = (float) $parameters->read_write_timeout;
4848
- $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
4849
 
4850
- $timeout = array(
4851
- 'sec' => $timeoutSeconds = floor($rwtimeout),
4852
- 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
4853
- );
4854
 
4855
- $socket = $socket ?: socket_import_stream($resource);
4856
- @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
4857
- @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
4858
- }
4859
 
4860
- if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
4861
- $socket = $socket ?: socket_import_stream($resource);
4862
- socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
4863
- }
4864
 
4865
- return $resource;
4866
  }
4867
 
4868
  /**
4869
- * Creates a new instance of the protocol reader resource.
4870
- *
4871
- * @return resource
4872
  */
4873
- private function createReader()
4874
  {
4875
- $reader = phpiredis_reader_create();
 
4876
 
4877
- phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
4878
- phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
4879
 
4880
- return $reader;
4881
- }
 
4882
 
4883
- /**
4884
- * Returns the underlying protocol reader resource.
4885
- *
4886
- * @return resource
4887
- */
4888
- protected function getReader()
4889
- {
4890
- return $this->reader;
4891
- }
4892
 
4893
- /**
4894
- * Returns the handler used by the protocol reader for inline responses.
4895
- *
4896
- * @return \Closure
4897
- */
4898
- protected function getStatusHandler()
4899
- {
4900
- return function ($payload) {
4901
- return StatusResponse::get($payload);
4902
- };
4903
  }
4904
 
4905
  /**
4906
- * Returns the handler used by the protocol reader for error responses.
4907
- *
4908
- * @return \Closure
4909
  */
4910
- protected function getErrorHandler()
4911
  {
4912
- return function ($errorMessage) {
4913
- return new ErrorResponse($errorMessage);
4914
- };
4915
  }
4916
 
4917
  /**
@@ -4919,362 +5027,354 @@ class PhpiredisStreamConnection extends StreamConnection
4919
  */
4920
  public function read()
4921
  {
4922
- $socket = $this->getResource();
4923
- $reader = $this->reader;
4924
-
4925
- while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
4926
- $buffer = stream_socket_recvfrom($socket, 4096);
4927
-
4928
- if ($buffer === false || $buffer === '') {
4929
- $this->onConnectionError('Error while reading bytes from the server.');
4930
- }
4931
-
4932
- phpiredis_reader_feed($reader, $buffer);
4933
- }
4934
-
4935
- if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
4936
- return phpiredis_reader_get_reply($reader);
4937
- } else {
4938
- $this->onProtocolError(phpiredis_reader_get_error($reader));
4939
-
4940
- return;
4941
- }
4942
  }
4943
 
4944
  /**
4945
  * {@inheritdoc}
4946
  */
4947
- public function writeRequest(CommandInterface $command)
4948
  {
4949
- $arguments = $command->getArguments();
4950
- array_unshift($arguments, $command->getId());
4951
-
4952
- $this->write(phpiredis_format_command($arguments));
4953
  }
 
4954
 
4955
- /**
4956
- * {@inheritdoc}
4957
- */
4958
- public function __wakeup()
4959
- {
4960
- $this->assertExtensions();
4961
- $this->reader = $this->createReader();
4962
- }
4963
  }
4964
 
4965
  /**
4966
- * This class implements a Predis connection that actually talks with Webdis
4967
- * instead of connecting directly to Redis. It relies on the cURL extension to
4968
- * communicate with the web server and the phpiredis extension to parse the
4969
- * protocol for responses returned in the http response bodies.
4970
- *
4971
- * Some features are not yet available or they simply cannot be implemented:
4972
- * - Pipelining commands.
4973
- * - Publish / Subscribe.
4974
- * - MULTI / EXEC transactions (not yet supported by Webdis).
4975
- *
4976
- * The connection parameters supported by this class are:
4977
- *
4978
- * - scheme: must be 'http'.
4979
- * - host: hostname or IP address of the server.
4980
- * - port: TCP port of the server.
4981
- * - timeout: timeout to perform the connection.
4982
- * - user: username for authentication.
4983
- * - pass: password for authentication.
4984
- *
4985
- * @link http://webd.is
4986
- * @link http://github.com/nicolasff/webdis
4987
- * @link http://github.com/seppo0010/phpiredis
4988
  *
4989
  * @author Daniele Alessandri <suppakilla@gmail.com>
4990
  */
4991
- class WebdisConnection implements NodeConnectionInterface
4992
  {
4993
- private $parameters;
4994
- private $resource;
4995
- private $reader;
 
 
 
 
 
 
 
4996
 
4997
  /**
4998
- * @param ParametersInterface $parameters Initialization parameters for the connection.
 
 
 
 
4999
  *
5000
  * @throws \InvalidArgumentException
 
 
5001
  */
5002
- public function __construct(ParametersInterface $parameters)
5003
  {
5004
- $this->assertExtensions();
5005
-
5006
- if ($parameters->scheme !== 'http') {
5007
- throw new \InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'.");
5008
  }
5009
 
5010
- $this->parameters = $parameters;
5011
 
5012
- $this->resource = $this->createCurl();
5013
- $this->reader = $this->createReader();
 
 
 
 
 
5014
  }
5015
 
5016
  /**
5017
- * Frees the underlying cURL and protocol reader resources when the garbage
5018
- * collector kicks in.
5019
  */
5020
- public function __destruct()
5021
  {
5022
- curl_close($this->resource);
5023
- phpiredis_reader_destroy($this->reader);
5024
  }
5025
 
5026
  /**
5027
- * Helper method used to throw on unsupported methods.
5028
- *
5029
- * @param string $method Name of the unsupported method.
5030
- *
5031
- * @throws NotSupportedException
5032
  */
5033
- private function throwNotSupportedException($method)
5034
  {
5035
- $class = __CLASS__;
5036
- throw new NotSupportedException("The method $class::$method() is not supported.");
5037
  }
5038
 
5039
  /**
5040
- * Checks if the cURL and phpiredis extensions are loaded in PHP.
5041
  */
5042
- private function assertExtensions()
5043
  {
5044
- if (!extension_loaded('curl')) {
5045
- throw new NotSupportedException(
5046
- 'The "curl" extension is required by this connection backend.'
5047
- );
5048
- }
5049
-
5050
- if (!extension_loaded('phpiredis')) {
5051
- throw new NotSupportedException(
5052
- 'The "phpiredis" extension is required by this connection backend.'
5053
- );
5054
  }
5055
- }
5056
 
5057
- /**
5058
- * Initializes cURL.
5059
- *
5060
- * @return resource
5061
- */
5062
- private function createCurl()
5063
- {
5064
- $parameters = $this->getParameters();
5065
 
5066
- if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) {
5067
- $host = "[$host]";
5068
  }
5069
 
5070
- $options = array(
5071
- CURLOPT_FAILONERROR => true,
5072
- CURLOPT_CONNECTTIMEOUT_MS => $parameters->timeout * 1000,
5073
- CURLOPT_URL => "$parameters->scheme://$host:$parameters->port",
5074
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
5075
- CURLOPT_POST => true,
5076
- CURLOPT_WRITEFUNCTION => array($this, 'feedReader'),
5077
- );
5078
 
5079
- if (isset($parameters->user, $parameters->pass)) {
5080
- $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}";
 
 
 
5081
  }
5082
 
5083
- curl_setopt_array($resource = curl_init(), $options);
 
 
 
 
 
5084
 
5085
- return $resource;
5086
  }
5087
 
5088
  /**
5089
- * Initializes the phpiredis protocol reader.
5090
- *
5091
- * @return resource
5092
  */
5093
- private function createReader()
5094
  {
5095
- $reader = phpiredis_reader_create();
5096
-
5097
- phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
5098
- phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
5099
-
5100
- return $reader;
5101
  }
5102
 
5103
  /**
5104
- * Returns the handler used by the protocol reader for inline responses.
5105
  *
5106
- * @return \Closure
 
 
 
5107
  */
5108
- protected function getStatusHandler()
5109
  {
5110
- return function ($payload) {
5111
- return StatusResponse::get($payload);
5112
- };
5113
  }
5114
 
5115
  /**
5116
- * Returns the handler used by the protocol reader for error responses.
5117
  *
5118
- * @return \Closure
5119
  */
5120
- protected function getErrorHandler()
5121
  {
5122
- return function ($payload) {
5123
- return new ErrorResponse($payload);
5124
- };
5125
  }
5126
 
5127
  /**
5128
- * Feeds the phpredis reader resource with the data read from the network.
5129
  *
5130
- * @param resource $resource Reader resource.
5131
- * @param string $buffer Buffer of data read from a connection.
5132
  *
5133
- * @return int
5134
  */
5135
- protected function feedReader($resource, $buffer)
5136
  {
5137
- phpiredis_reader_feed($this->reader, $buffer);
 
 
 
 
5138
 
5139
- return strlen($buffer);
 
 
 
 
5140
  }
5141
 
5142
  /**
5143
- * {@inheritdoc}
 
 
5144
  */
5145
- public function connect()
5146
  {
5147
- // NOOP
 
 
 
 
 
 
 
 
 
 
 
 
5148
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5149
 
5150
  /**
5151
- * {@inheritdoc}
5152
  */
5153
- public function disconnect()
5154
  {
5155
- // NOOP
5156
  }
5157
 
5158
  /**
5159
- * {@inheritdoc}
 
 
5160
  */
5161
- public function isConnected()
5162
  {
5163
- return true;
5164
  }
5165
 
5166
  /**
5167
- * Checks if the specified command is supported by this connection class.
5168
- *
5169
- * @param CommandInterface $command Command instance.
5170
  *
5171
- * @throws NotSupportedException
5172
  *
5173
- * @return string
5174
  */
5175
- protected function getCommandId(CommandInterface $command)
5176
  {
5177
- switch ($commandID = $command->getId()) {
5178
- case 'AUTH':
5179
- case 'SELECT':
5180
- case 'MULTI':
5181
- case 'EXEC':
5182
- case 'WATCH':
5183
- case 'UNWATCH':
5184
- case 'DISCARD':
5185
- case 'MONITOR':
5186
- throw new NotSupportedException("Command '$commandID' is not allowed by Webdis.");
5187
-
5188
- default:
5189
- return $commandID;
5190
  }
5191
- }
5192
-
5193
- /**
5194
- * {@inheritdoc}
5195
- */
5196
- public function writeRequest(CommandInterface $command)
5197
- {
5198
- $this->throwNotSupportedException(__FUNCTION__);
5199
- }
5200
 
5201
- /**
5202
- * {@inheritdoc}
5203
- */
5204
- public function readResponse(CommandInterface $command)
5205
- {
5206
- $this->throwNotSupportedException(__FUNCTION__);
5207
  }
5208
 
5209
  /**
5210
- * {@inheritdoc}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5211
  */
5212
- public function executeCommand(CommandInterface $command)
5213
  {
5214
- $resource = $this->resource;
5215
- $commandId = $this->getCommandId($command);
 
 
 
5216
 
5217
- if ($arguments = $command->getArguments()) {
5218
- $arguments = implode('/', array_map('urlencode', $arguments));
5219
- $serializedCommand = "$commandId/$arguments.raw";
5220
- } else {
5221
- $serializedCommand = "$commandId.raw";
5222
  }
5223
 
5224
- curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand);
 
 
 
 
 
 
5225
 
5226
- if (curl_exec($resource) === false) {
5227
- $error = curl_error($resource);
5228
- $errno = curl_errno($resource);
5229
 
5230
- throw new ConnectionException($this, trim($error), $errno);
5231
  }
5232
 
5233
- if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) {
5234
- throw new ProtocolException($this, phpiredis_reader_get_error($this->reader));
5235
- }
 
 
5236
 
5237
- return phpiredis_reader_get_reply($this->reader);
5238
- }
5239
 
5240
- /**
5241
- * {@inheritdoc}
5242
- */
5243
- public function getResource()
5244
- {
5245
- return $this->resource;
 
 
 
5246
  }
5247
 
5248
  /**
5249
- * {@inheritdoc}
 
 
 
 
5250
  */
5251
- public function getParameters()
5252
  {
5253
- return $this->parameters;
5254
  }
5255
 
5256
  /**
5257
  * {@inheritdoc}
5258
  */
5259
- public function addConnectCommand(CommandInterface $command)
5260
  {
5261
- $this->throwNotSupportedException(__FUNCTION__);
 
 
5262
  }
5263
 
5264
  /**
5265
  * {@inheritdoc}
5266
  */
5267
- public function read()
5268
  {
5269
- $this->throwNotSupportedException(__FUNCTION__);
5270
  }
5271
 
5272
  /**
5273
  * {@inheritdoc}
5274
  */
5275
- public function __toString()
5276
  {
5277
- return "{$this->parameters->host}:{$this->parameters->port}";
5278
  }
5279
 
5280
  /**
@@ -5284,17 +5384,6 @@ class WebdisConnection implements NodeConnectionInterface
5284
  {
5285
  return array('parameters');
5286
  }
5287
-
5288
- /**
5289
- * {@inheritdoc}
5290
- */
5291
- public function __wakeup()
5292
- {
5293
- $this->assertExtensions();
5294
-
5295
- $this->resource = $this->createCurl();
5296
- $this->reader = $this->createReader();
5297
- }
5298
  }
5299
 
5300
  /**
@@ -5317,7 +5406,7 @@ class WebdisConnection implements NodeConnectionInterface
5317
  * - host: hostname or IP address of the server.
5318
  * - port: TCP port of the server.
5319
  * - path: path of a UNIX domain socket when scheme is 'unix'.
5320
- * - timeout: timeout to perform the connection.
5321
  * - read_write_timeout: timeout of read / write operations.
5322
  *
5323
  * @link http://github.com/nrk/phpiredis
@@ -5374,7 +5463,15 @@ class PhpiredisSocketConnection extends AbstractConnection
5374
  */
5375
  protected function assertParameters(ParametersInterface $parameters)
5376
  {
5377
- parent::assertParameters($parameters);
 
 
 
 
 
 
 
 
5378
 
5379
  if (isset($parameters->persistent)) {
5380
  throw new NotSupportedException(
@@ -5415,11 +5512,17 @@ class PhpiredisSocketConnection extends AbstractConnection
5415
  *
5416
  * @return \Closure
5417
  */
5418
- private function getStatusHandler()
5419
  {
5420
- return function ($payload) {
5421
- return StatusResponse::get($payload);
5422
- };
 
 
 
 
 
 
5423
  }
5424
 
5425
  /**
@@ -5429,9 +5532,15 @@ class PhpiredisSocketConnection extends AbstractConnection
5429
  */
5430
  protected function getErrorHandler()
5431
  {
5432
- return function ($payload) {
5433
- return new ErrorResponse($payload);
5434
- };
 
 
 
 
 
 
5435
  }
5436
 
5437
  /**
@@ -5563,7 +5672,7 @@ class PhpiredisSocketConnection extends AbstractConnection
5563
  $null = null;
5564
  $selectable = array($socket);
5565
 
5566
- $timeout = (float) $parameters->timeout;
5567
  $timeoutSecs = floor($timeout);
5568
  $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000;
5569
 
@@ -5589,7 +5698,11 @@ class PhpiredisSocketConnection extends AbstractConnection
5589
  {
5590
  if (parent::connect() && $this->initCommands) {
5591
  foreach ($this->initCommands as $command) {
5592
- $this->executeCommand($command);
 
 
 
 
5593
  }
5594
  }
5595
  }
@@ -5674,566 +5787,716 @@ class PhpiredisSocketConnection extends AbstractConnection
5674
  }
5675
 
5676
  /**
5677
- * Connection abstraction to Redis servers based on PHP's stream that uses an
5678
- * external protocol processor defining the protocol used for the communication.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5679
  *
5680
  * @author Daniele Alessandri <suppakilla@gmail.com>
5681
  */
5682
- class CompositeStreamConnection extends StreamConnection implements CompositeConnectionInterface
5683
  {
5684
- protected $protocol;
5685
 
5686
  /**
5687
- * @param ParametersInterface $parameters Initialization parameters for the connection.
5688
- * @param ProtocolProcessorInterface $protocol Protocol processor.
5689
  */
5690
- public function __construct(
5691
- ParametersInterface $parameters,
5692
- ProtocolProcessorInterface $protocol = null
5693
- ) {
5694
- $this->parameters = $this->assertParameters($parameters);
5695
- $this->protocol = $protocol ?: new TextProtocolProcessor();
 
5696
  }
5697
 
5698
  /**
5699
  * {@inheritdoc}
5700
  */
5701
- public function getProtocol()
5702
  {
5703
- return $this->protocol;
 
 
5704
  }
5705
 
5706
  /**
5707
- * {@inheritdoc}
5708
  */
5709
- public function writeBuffer($buffer)
5710
  {
5711
- $this->write($buffer);
 
 
 
 
5712
  }
5713
 
5714
  /**
5715
  * {@inheritdoc}
5716
  */
5717
- public function readBuffer($length)
5718
  {
5719
- if ($length <= 0) {
5720
- throw new \InvalidArgumentException('Length parameter must be greater than 0.');
5721
- }
5722
-
5723
- $value = '';
5724
- $socket = $this->getResource();
5725
-
5726
- do {
5727
- $chunk = fread($socket, $length);
5728
-
5729
- if ($chunk === false || $chunk === '') {
5730
- $this->onConnectionError('Error while reading bytes from the server.');
5731
- }
5732
-
5733
- $value .= $chunk;
5734
- } while (($length -= strlen($chunk)) > 0);
5735
-
5736
- return $value;
5737
  }
5738
 
5739
  /**
5740
  * {@inheritdoc}
5741
  */
5742
- public function readLine()
5743
  {
5744
- $value = '';
5745
- $socket = $this->getResource();
5746
 
5747
- do {
5748
- $chunk = fgets($socket);
5749
 
5750
- if ($chunk === false || $chunk === '') {
5751
- $this->onConnectionError('Error while reading line from the server.');
5752
- }
5753
 
5754
- $value .= $chunk;
5755
- } while (substr($value, -2) !== "\r\n");
 
5756
 
5757
- return substr($value, 0, -2);
5758
- }
 
 
5759
 
5760
- /**
5761
- * {@inheritdoc}
5762
- */
5763
- public function writeRequest(CommandInterface $command)
5764
- {
5765
- $this->protocol->write($this, $command);
5766
- }
5767
 
5768
- /**
5769
- * {@inheritdoc}
5770
- */
5771
- public function read()
5772
- {
5773
- return $this->protocol->read($this);
5774
  }
5775
 
5776
  /**
5777
- * {@inheritdoc}
 
 
5778
  */
5779
- public function __sleep()
5780
  {
5781
- return array_merge(parent::__sleep(), array('protocol'));
5782
- }
5783
- }
5784
-
5785
- /**
5786
- * Exception class that identifies connection-related errors.
5787
- *
5788
- * @author Daniele Alessandri <suppakilla@gmail.com>
5789
- */
5790
- class ConnectionException extends CommunicationException
5791
- {
5792
- }
5793
-
5794
- /**
5795
- * Container for connection parameters used to initialize connections to Redis.
5796
- *
5797
- * {@inheritdoc}
5798
- *
5799
- * @author Daniele Alessandri <suppakilla@gmail.com>
5800
- */
5801
- class Parameters implements ParametersInterface
5802
- {
5803
- private $parameters;
5804
 
5805
- private static $defaults = array(
5806
- 'scheme' => 'tcp',
5807
- 'host' => '127.0.0.1',
5808
- 'port' => 6379,
5809
- 'timeout' => 5.0,
5810
- );
5811
 
5812
- /**
5813
- * @param array $parameters Named array of connection parameters.
5814
- */
5815
- public function __construct(array $parameters = array())
5816
- {
5817
- $this->parameters = $this->filter($parameters) + $this->getDefaults();
5818
  }
5819
 
5820
  /**
5821
- * Returns some default parameters with their values.
5822
  *
5823
- * @return array
5824
  */
5825
- protected function getDefaults()
5826
  {
5827
- return self::$defaults;
5828
  }
5829
 
5830
  /**
5831
- * Creates a new instance by supplying the initial parameters either in the
5832
- * form of an URI string or a named array.
5833
- *
5834
- * @param array|string $parameters Set of connection parameters.
5835
  *
5836
- * @return Parameters
5837
  */
5838
- public static function create($parameters)
5839
  {
5840
- if (is_string($parameters)) {
5841
- $parameters = static::parse($parameters);
 
 
 
 
5842
  }
5843
 
5844
- return new static($parameters ?: array());
5845
  }
5846
 
5847
  /**
5848
- * Parses an URI string returning an array of connection parameters.
5849
- *
5850
- * When using the "redis" and "rediss" schemes the URI is parsed according
5851
- * to the rules defined by the provisional registration documents approved
5852
- * by IANA. If the URI has a password in its "user-information" part or a
5853
- * database number in the "path" part these values override the values of
5854
- * "password" and "database" if they are present in the "query" part.
5855
- *
5856
- * @link http://www.iana.org/assignments/uri-schemes/prov/redis
5857
- * @link http://www.iana.org/assignments/uri-schemes/prov/redis
5858
- *
5859
- * @param string $uri URI string.
5860
- *
5861
- * @throws \InvalidArgumentException
5862
  *
5863
- * @return array
5864
  */
5865
- public static function parse($uri)
5866
  {
5867
- if (stripos($uri, 'unix') === 0) {
5868
- // Hack to support URIs for UNIX sockets with minimal effort.
5869
- $uri = str_ireplace('unix:///', 'unix://localhost/', $uri);
5870
- }
5871
 
5872
- if (!$parsed = parse_url($uri)) {
5873
- throw new \InvalidArgumentException("Invalid parameters URI: $uri");
 
 
5874
  }
5875
 
5876
- if (
5877
- isset($parsed['host'])
5878
- && false !== strpos($parsed['host'], '[')
5879
- && false !== strpos($parsed['host'], ']')
5880
- ) {
5881
- $parsed['host'] = substr($parsed['host'], 1, -1);
5882
- }
5883
 
5884
- if (isset($parsed['query'])) {
5885
- parse_str($parsed['query'], $queryarray);
5886
- unset($parsed['query']);
 
 
 
 
5887
 
5888
- $parsed = array_merge($parsed, $queryarray);
5889
- }
5890
 
5891
- if (stripos($uri, 'redis') === 0) {
5892
- if (isset($parsed['pass'])) {
5893
- $parsed['password'] = $parsed['pass'];
5894
- unset($parsed['pass']);
5895
  }
5896
 
5897
- if (isset($parsed['path']) && preg_match('/^\/(\d+)(\/.*)?/', $parsed['path'], $path)) {
5898
- $parsed['database'] = $path[1];
5899
-
5900
- if (isset($path[2])) {
5901
- $parsed['path'] = $path[2];
5902
- } else {
5903
- unset($parsed['path']);
5904
- }
5905
- }
5906
  }
5907
 
5908
- return $parsed;
5909
- }
5910
-
5911
- /**
5912
- * Validates and converts each value of the connection parameters array.
5913
- *
5914
- * @param array $parameters Connection parameters.
5915
- *
5916
- * @return array
5917
- */
5918
- protected function filter(array $parameters)
5919
- {
5920
- return $parameters ?: array();
5921
- }
5922
 
5923
- /**
5924
- * {@inheritdoc}
5925
- */
5926
- public function __get($parameter)
5927
- {
5928
- if (isset($this->parameters[$parameter])) {
5929
- return $this->parameters[$parameter];
5930
  }
5931
  }
5932
 
5933
  /**
5934
  * {@inheritdoc}
5935
  */
5936
- public function __isset($parameter)
5937
  {
5938
- return isset($this->parameters[$parameter]);
5939
- }
5940
 
5941
- /**
5942
- * {@inheritdoc}
5943
- */
5944
- public function toArray()
5945
- {
5946
- return $this->parameters;
5947
  }
5948
 
5949
  /**
5950
  * {@inheritdoc}
5951
  */
5952
- public function __sleep()
5953
  {
5954
- return array('parameters');
 
5955
  }
5956
  }
5957
 
5958
  /**
5959
- * Standard connection factory for creating connections to Redis nodes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5960
  *
5961
  * @author Daniele Alessandri <suppakilla@gmail.com>
5962
  */
5963
- class Factory implements FactoryInterface
5964
  {
5965
- protected $schemes = array(
5966
- 'tcp' => 'Predis\Connection\StreamConnection',
5967
- 'unix' => 'Predis\Connection\StreamConnection',
5968
- 'redis' => 'Predis\Connection\StreamConnection',
5969
- 'http' => 'Predis\Connection\WebdisConnection',
5970
- );
5971
 
5972
  /**
5973
- * Checks if the provided argument represents a valid connection class
5974
- * implementing Predis\Connection\NodeConnectionInterface. Optionally,
5975
- * callable objects are used for lazy initialization of connection objects.
5976
- *
5977
- * @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
5978
  *
5979
  * @throws \InvalidArgumentException
5980
- *
5981
- * @return mixed
5982
  */
5983
- protected function checkInitializer($initializer)
5984
  {
5985
- if (is_callable($initializer)) {
5986
- return $initializer;
5987
- }
5988
-
5989
- $class = new \ReflectionClass($initializer);
5990
 
5991
- if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) {
5992
- throw new \InvalidArgumentException(
5993
- 'A connection initializer must be a valid connection class or a callable object.'
5994
- );
5995
  }
5996
 
5997
- return $initializer;
5998
- }
 
 
 
5999
 
6000
  /**
6001
- * {@inheritdoc}
 
6002
  */
6003
- public function define($scheme, $initializer)
6004
  {
6005
- $this->schemes[$scheme] = $this->checkInitializer($initializer);
 
6006
  }
6007
 
6008
  /**
6009
- * {@inheritdoc}
 
 
 
 
6010
  */
6011
- public function undefine($scheme)
6012
  {
6013
- unset($this->schemes[$scheme]);
 
6014
  }
6015
 
6016
  /**
6017
- * {@inheritdoc}
6018
  */
6019
- public function create($parameters)
6020
  {
6021
- if (!$parameters instanceof ParametersInterface) {
6022
- $parameters = $this->createParameters($parameters);
6023
- }
6024
-
6025
- $scheme = $parameters->scheme;
6026
-
6027
- if (!isset($this->schemes[$scheme])) {
6028
- throw new \InvalidArgumentException("Unknown connection scheme: '$scheme'.");
6029
- }
6030
-
6031
- $initializer = $this->schemes[$scheme];
6032
-
6033
- if (is_callable($initializer)) {
6034
- $connection = call_user_func($initializer, $parameters, $this);
6035
- } else {
6036
- $connection = new $initializer($parameters);
6037
- $this->prepareConnection($connection);
6038
  }
6039
 
6040
- if (!$connection instanceof NodeConnectionInterface) {
6041
- throw new \UnexpectedValueException(
6042
- 'Objects returned by connection initializers must implement '.
6043
- "'Predis\Connection\NodeConnectionInterface'."
6044
  );
6045
  }
6046
-
6047
- return $connection;
6048
  }
6049
 
6050
  /**
6051
- * {@inheritdoc}
 
 
6052
  */
6053
- public function aggregate(AggregateConnectionInterface $connection, array $parameters)
6054
  {
6055
- foreach ($parameters as $node) {
6056
- $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6057
  }
 
 
 
 
6058
  }
6059
 
6060
  /**
6061
- * Creates a connection parameters instance from the supplied argument.
6062
- *
6063
- * @param mixed $parameters Original connection parameters.
6064
  *
6065
- * @return ParametersInterface
6066
  */
6067
- protected function createParameters($parameters)
6068
  {
6069
- return Parameters::create($parameters);
 
 
 
 
 
6070
  }
6071
 
6072
  /**
6073
- * Prepares a connection instance after its initialization.
6074
  *
6075
- * @param NodeConnectionInterface $connection Connection instance.
6076
  */
6077
- protected function prepareConnection(NodeConnectionInterface $connection)
6078
  {
6079
- $parameters = $connection->getParameters();
6080
 
6081
- if (isset($parameters->password)) {
6082
- $connection->addConnectCommand(
6083
- new RawCommand(array('AUTH', $parameters->password))
6084
- );
6085
  }
6086
 
6087
- if (isset($parameters->database)) {
6088
- $connection->addConnectCommand(
6089
- new RawCommand(array('SELECT', $parameters->database))
6090
- );
6091
- }
6092
  }
6093
- }
6094
-
6095
- /* --------------------------------------------------------------------------- */
6096
-
6097
- namespace Predis\Profile;
6098
-
6099
- use Predis\ClientException;
6100
- use Predis\Command\CommandInterface;
6101
- use Predis\Command\Processor\ProcessorInterface;
6102
 
6103
- /**
6104
- * A profile defines all the features and commands supported by certain versions
6105
- * of Redis. Instances of Predis\Client should use a server profile matching the
6106
- * version of Redis being used.
6107
- *
6108
- * @author Daniele Alessandri <suppakilla@gmail.com>
6109
- */
6110
- interface ProfileInterface
6111
- {
6112
  /**
6113
- * Returns the profile version corresponding to the Redis version.
6114
  *
6115
- * @return string
6116
  */
6117
- public function getVersion();
 
 
6118
 
6119
- /**
6120
- * Checks if the profile supports the specified command.
6121
- *
6122
- * @param string $commandID Command ID.
6123
- *
6124
- * @return bool
6125
- */
6126
- public function supportsCommand($commandID);
6127
 
6128
- /**
6129
- * Checks if the profile supports the specified list of commands.
6130
- *
6131
- * @param array $commandIDs List of command IDs.
6132
- *
6133
- * @return string
6134
- */
6135
- public function supportsCommands(array $commandIDs);
6136
 
6137
  /**
6138
- * Creates a new command instance.
6139
  *
6140
- * @param string $commandID Command ID.
6141
- * @param array $arguments Arguments for the command.
6142
  *
6143
- * @return CommandInterface
6144
  */
6145
- public function createCommand($commandID, array $arguments = array());
6146
- }
 
6147
 
6148
- /**
6149
- * Base class implementing common functionalities for Redis server profiles.
6150
- *
6151
- * @author Daniele Alessandri <suppakilla@gmail.com>
6152
- */
6153
- abstract class RedisProfile implements ProfileInterface
6154
- {
6155
- private $commands;
6156
- private $processor;
6157
 
6158
  /**
6159
- *
6160
  */
6161
- public function __construct()
6162
  {
6163
- $this->commands = $this->getSupportedCommands();
6164
  }
6165
 
6166
- /**
6167
- * Returns a map of all the commands supported by the profile and their
6168
- * actual PHP classes.
6169
- *
6170
- * @return array
6171
- */
6172
- abstract protected function getSupportedCommands();
6173
-
6174
  /**
6175
  * {@inheritdoc}
6176
  */
6177
- public function supportsCommand($commandID)
6178
  {
6179
- return isset($this->commands[strtoupper($commandID)]);
6180
  }
6181
 
6182
  /**
6183
  * {@inheritdoc}
6184
  */
6185
- public function supportsCommands(array $commandIDs)
6186
  {
6187
- foreach ($commandIDs as $commandID) {
6188
- if (!$this->supportsCommand($commandID)) {
6189
- return false;
6190
- }
6191
- }
6192
-
6193
  return true;
6194
  }
6195
 
6196
  /**
6197
- * Returns the fully-qualified name of a class representing the specified
6198
- * command ID registered in the current server profile.
6199
  *
6200
- * @param string $commandID Command ID.
6201
  *
6202
- * @return string|null
 
 
6203
  */
6204
- public function getCommandClass($commandID)
6205
  {
6206
- if (isset($this->commands[$commandID = strtoupper($commandID)])) {
6207
- return $this->commands[$commandID];
 
 
 
 
 
 
 
 
 
 
 
6208
  }
6209
  }
6210
 
6211
  /**
6212
  * {@inheritdoc}
6213
  */
6214
- public function createCommand($commandID, array $arguments = array())
6215
  {
6216
- $commandID = strtoupper($commandID);
6217
-
6218
- if (!isset($this->commands[$commandID])) {
6219
- throw new ClientException("Command '$commandID' is not a registered Redis command.");
6220
- }
6221
-
6222
- $commandClass = $this->commands[$commandID];
6223
- $command = new $commandClass();
6224
- $command->setArguments($arguments);
6225
-
6226
- if (isset($this->processor)) {
6227
- $this->processor->process($command);
6228
- }
6229
-
6230
- return $command;
6231
  }
6232
 
6233
  /**
6234
- * Defines a new command in the server profile.
6235
- *
6236
- * @param string $commandID Command ID.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6237
  * @param string $class Fully-qualified name of a Predis\Command\CommandInterface.
6238
  *
6239
  * @throws \InvalidArgumentException
@@ -6281,14 +6544,14 @@ abstract class RedisProfile implements ProfileInterface
6281
  *
6282
  * @author Daniele Alessandri <suppakilla@gmail.com>
6283
  */
6284
- class RedisVersion300 extends RedisProfile
6285
  {
6286
  /**
6287
  * {@inheritdoc}
6288
  */
6289
  public function getVersion()
6290
  {
6291
- return '3.0';
6292
  }
6293
 
6294
  /**
@@ -6528,25 +6791,149 @@ class RedisVersion300 extends RedisProfile
6528
  /* remote server control commands */
6529
  'COMMAND' => 'Predis\Command\ServerCommand',
6530
 
6531
- /* ---------------- Redis 3.0 ---------------- */
6532
 
 
 
 
 
 
 
 
 
 
 
 
6533
  );
6534
  }
6535
  }
6536
 
6537
  /**
6538
- * Server profile for Redis 2.6.
6539
  *
6540
  * @author Daniele Alessandri <suppakilla@gmail.com>
6541
  */
6542
- class RedisVersion260 extends RedisProfile
6543
  {
6544
- /**
6545
- * {@inheritdoc}
6546
- */
6547
- public function getVersion()
6548
- {
6549
- return '2.6';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6550
  }
6551
 
6552
  /**
@@ -6570,8 +6957,6 @@ class RedisVersion260 extends RedisProfile
6570
  'TTL' => 'Predis\Command\KeyTimeToLive',
6571
  'MOVE' => 'Predis\Command\KeyMove',
6572
  'SORT' => 'Predis\Command\KeySort',
6573
- 'DUMP' => 'Predis\Command\KeyDump',
6574
- 'RESTORE' => 'Predis\Command\KeyRestore',
6575
 
6576
  /* commands operating on string values */
6577
  'SET' => 'Predis\Command\StringSet',
@@ -6634,7 +7019,7 @@ class RedisVersion260 extends RedisProfile
6634
  'QUIT' => 'Predis\Command\ConnectionQuit',
6635
 
6636
  /* remote server control commands */
6637
- 'INFO' => 'Predis\Command\ServerInfoV26x',
6638
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6639
  'MONITOR' => 'Predis\Command\ServerMonitor',
6640
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
@@ -6693,83 +7078,23 @@ class RedisVersion260 extends RedisProfile
6693
 
6694
  /* remote server control commands */
6695
  'CONFIG' => 'Predis\Command\ServerConfig',
6696
-
6697
- /* ---------------- Redis 2.2 ---------------- */
6698
-
6699
- /* commands operating on the key space */
6700
- 'PERSIST' => 'Predis\Command\KeyPersist',
6701
-
6702
- /* commands operating on string values */
6703
- 'STRLEN' => 'Predis\Command\StringStrlen',
6704
- 'SETRANGE' => 'Predis\Command\StringSetRange',
6705
- 'GETRANGE' => 'Predis\Command\StringGetRange',
6706
- 'SETBIT' => 'Predis\Command\StringSetBit',
6707
- 'GETBIT' => 'Predis\Command\StringGetBit',
6708
-
6709
- /* commands operating on lists */
6710
- 'RPUSHX' => 'Predis\Command\ListPushTailX',
6711
- 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6712
- 'LINSERT' => 'Predis\Command\ListInsert',
6713
- 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6714
-
6715
- /* commands operating on sorted sets */
6716
- 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6717
-
6718
- /* transactions */
6719
- 'WATCH' => 'Predis\Command\TransactionWatch',
6720
- 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6721
-
6722
- /* remote server control commands */
6723
- 'OBJECT' => 'Predis\Command\ServerObject',
6724
- 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6725
-
6726
- /* ---------------- Redis 2.4 ---------------- */
6727
-
6728
- /* remote server control commands */
6729
- 'CLIENT' => 'Predis\Command\ServerClient',
6730
-
6731
- /* ---------------- Redis 2.6 ---------------- */
6732
-
6733
- /* commands operating on the key space */
6734
- 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6735
- 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6736
- 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6737
- 'MIGRATE' => 'Predis\Command\KeyMigrate',
6738
-
6739
- /* commands operating on string values */
6740
- 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6741
- 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6742
- 'BITOP' => 'Predis\Command\StringBitOp',
6743
- 'BITCOUNT' => 'Predis\Command\StringBitCount',
6744
-
6745
- /* commands operating on hashes */
6746
- 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6747
-
6748
- /* scripting */
6749
- 'EVAL' => 'Predis\Command\ServerEval',
6750
- 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6751
- 'SCRIPT' => 'Predis\Command\ServerScript',
6752
-
6753
- /* remote server control commands */
6754
- 'TIME' => 'Predis\Command\ServerTime',
6755
- 'SENTINEL' => 'Predis\Command\ServerSentinel',
6756
  );
6757
  }
6758
  }
6759
 
6760
  /**
6761
- * Server profile for Redis 2.8.
6762
  *
6763
  * @author Daniele Alessandri <suppakilla@gmail.com>
6764
  */
6765
- class RedisVersion280 extends RedisProfile
6766
  {
6767
  /**
6768
  * {@inheritdoc}
6769
  */
6770
  public function getVersion()
6771
  {
6772
- return '2.8';
6773
  }
6774
 
6775
  /**
@@ -6793,8 +7118,6 @@ class RedisVersion280 extends RedisProfile
6793
  'TTL' => 'Predis\Command\KeyTimeToLive',
6794
  'MOVE' => 'Predis\Command\KeyMove',
6795
  'SORT' => 'Predis\Command\KeySort',
6796
- 'DUMP' => 'Predis\Command\KeyDump',
6797
- 'RESTORE' => 'Predis\Command\KeyRestore',
6798
 
6799
  /* commands operating on string values */
6800
  'SET' => 'Predis\Command\StringSet',
@@ -6857,7 +7180,7 @@ class RedisVersion280 extends RedisProfile
6857
  'QUIT' => 'Predis\Command\ConnectionQuit',
6858
 
6859
  /* remote server control commands */
6860
- 'INFO' => 'Predis\Command\ServerInfoV26x',
6861
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6862
  'MONITOR' => 'Predis\Command\ServerMonitor',
6863
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
@@ -6945,69 +7268,6 @@ class RedisVersion280 extends RedisProfile
6945
  /* remote server control commands */
6946
  'OBJECT' => 'Predis\Command\ServerObject',
6947
  'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6948
-
6949
- /* ---------------- Redis 2.4 ---------------- */
6950
-
6951
- /* remote server control commands */
6952
- 'CLIENT' => 'Predis\Command\ServerClient',
6953
-
6954
- /* ---------------- Redis 2.6 ---------------- */
6955
-
6956
- /* commands operating on the key space */
6957
- 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6958
- 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6959
- 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6960
- 'MIGRATE' => 'Predis\Command\KeyMigrate',
6961
-
6962
- /* commands operating on string values */
6963
- 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6964
- 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6965
- 'BITOP' => 'Predis\Command\StringBitOp',
6966
- 'BITCOUNT' => 'Predis\Command\StringBitCount',
6967
-
6968
- /* commands operating on hashes */
6969
- 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6970
-
6971
- /* scripting */
6972
- 'EVAL' => 'Predis\Command\ServerEval',
6973
- 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6974
- 'SCRIPT' => 'Predis\Command\ServerScript',
6975
-
6976
- /* remote server control commands */
6977
- 'TIME' => 'Predis\Command\ServerTime',
6978
- 'SENTINEL' => 'Predis\Command\ServerSentinel',
6979
-
6980
- /* ---------------- Redis 2.8 ---------------- */
6981
-
6982
- /* commands operating on the key space */
6983
- 'SCAN' => 'Predis\Command\KeyScan',
6984
-
6985
- /* commands operating on string values */
6986
- 'BITPOS' => 'Predis\Command\StringBitPos',
6987
-
6988
- /* commands operating on sets */
6989
- 'SSCAN' => 'Predis\Command\SetScan',
6990
-
6991
- /* commands operating on sorted sets */
6992
- 'ZSCAN' => 'Predis\Command\ZSetScan',
6993
- 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
6994
- 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
6995
- 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
6996
- 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex',
6997
-
6998
- /* commands operating on hashes */
6999
- 'HSCAN' => 'Predis\Command\HashScan',
7000
-
7001
- /* publish - subscribe */
7002
- 'PUBSUB' => 'Predis\Command\PubSubPubsub',
7003
-
7004
- /* commands operating on HyperLogLog */
7005
- 'PFADD' => 'Predis\Command\HyperLogLogAdd',
7006
- 'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
7007
- 'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
7008
-
7009
- /* remote server control commands */
7010
- 'COMMAND' => 'Predis\Command\ServerCommand',
7011
  );
7012
  }
7013
  }
@@ -7208,18 +7468,18 @@ class RedisVersion240 extends RedisProfile
7208
  }
7209
 
7210
  /**
7211
- * Server profile for Redis 2.0.
7212
  *
7213
  * @author Daniele Alessandri <suppakilla@gmail.com>
7214
  */
7215
- class RedisVersion200 extends RedisProfile
7216
  {
7217
  /**
7218
  * {@inheritdoc}
7219
  */
7220
  public function getVersion()
7221
  {
7222
- return '2.0';
7223
  }
7224
 
7225
  /**
@@ -7243,6 +7503,8 @@ class RedisVersion200 extends RedisProfile
7243
  'TTL' => 'Predis\Command\KeyTimeToLive',
7244
  'MOVE' => 'Predis\Command\KeyMove',
7245
  'SORT' => 'Predis\Command\KeySort',
 
 
7246
 
7247
  /* commands operating on string values */
7248
  'SET' => 'Predis\Command\StringSet',
@@ -7305,7 +7567,7 @@ class RedisVersion200 extends RedisProfile
7305
  'QUIT' => 'Predis\Command\ConnectionQuit',
7306
 
7307
  /* remote server control commands */
7308
- 'INFO' => 'Predis\Command\ServerInfo',
7309
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7310
  'MONITOR' => 'Predis\Command\ServerMonitor',
7311
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
@@ -7364,138 +7626,83 @@ class RedisVersion200 extends RedisProfile
7364
 
7365
  /* remote server control commands */
7366
  'CONFIG' => 'Predis\Command\ServerConfig',
7367
- );
7368
- }
7369
- }
7370
 
7371
- /**
7372
- * Server profile for the current unstable version of Redis.
7373
- *
7374
- * @author Daniele Alessandri <suppakilla@gmail.com>
7375
- */
7376
- class RedisUnstable extends RedisVersion300
7377
- {
7378
- /**
7379
- * {@inheritdoc}
7380
- */
7381
- public function getVersion()
7382
- {
7383
- return '3.2';
7384
- }
7385
 
7386
- /**
7387
- * {@inheritdoc}
7388
- */
7389
- public function getSupportedCommands()
7390
- {
7391
- return array_merge(parent::getSupportedCommands(), array(
7392
- /* ---------------- Redis 3.2 ---------------- */
7393
 
7394
- /* commands operating on hashes */
7395
- 'HSTRLEN' => 'Predis\Command\HashStringLength',
7396
- ));
7397
- }
7398
- }
 
7399
 
7400
- /**
7401
- * Factory class for creating profile instances from strings.
7402
- *
7403
- * @author Daniele Alessandri <suppakilla@gmail.com>
7404
- */
7405
- final class Factory
7406
- {
7407
- private static $profiles = array(
7408
- '2.0' => 'Predis\Profile\RedisVersion200',
7409
- '2.2' => 'Predis\Profile\RedisVersion220',
7410
- '2.4' => 'Predis\Profile\RedisVersion240',
7411
- '2.6' => 'Predis\Profile\RedisVersion260',
7412
- '2.8' => 'Predis\Profile\RedisVersion280',
7413
- '3.0' => 'Predis\Profile\RedisVersion300',
7414
- 'dev' => 'Predis\Profile\RedisUnstable',
7415
- 'default' => 'Predis\Profile\RedisVersion300',
7416
- );
7417
 
7418
- /**
7419
- *
7420
- */
7421
- private function __construct()
7422
- {
7423
- // NOOP
7424
- }
7425
 
7426
- /**
7427
- * Returns the default server profile.
7428
- *
7429
- * @return ProfileInterface
7430
- */
7431
- public static function getDefault()
7432
- {
7433
- return self::get('default');
7434
- }
7435
 
7436
- /**
7437
- * Returns the development server profile.
7438
- *
7439
- * @return ProfileInterface
7440
- */
7441
- public static function getDevelopment()
7442
- {
7443
- return self::get('dev');
7444
- }
7445
 
7446
- /**
7447
- * Registers a new server profile.
7448
- *
7449
- * @param string $alias Profile version or alias.
7450
- * @param string $class FQN of a class implementing Predis\Profile\ProfileInterface.
7451
- *
7452
- * @throws \InvalidArgumentException
7453
- */
7454
- public static function define($alias, $class)
7455
- {
7456
- $reflection = new \ReflectionClass($class);
7457
 
7458
- if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) {
7459
- throw new \InvalidArgumentException("The class '$class' is not a valid profile class.");
7460
- }
7461
 
7462
- self::$profiles[$alias] = $class;
7463
- }
7464
 
7465
- /**
7466
- * Returns the specified server profile.
7467
- *
7468
- * @param string $version Profile version or alias.
7469
- *
7470
- * @throws ClientException
7471
- *
7472
- * @return ProfileInterface
7473
- */
7474
- public static function get($version)
7475
- {
7476
- if (!isset(self::$profiles[$version])) {
7477
- throw new ClientException("Unknown server profile: '$version'.");
7478
- }
7479
 
7480
- $profile = self::$profiles[$version];
 
 
 
 
7481
 
7482
- return new $profile();
 
 
 
 
 
 
 
 
 
 
 
7483
  }
7484
  }
7485
 
7486
  /**
7487
- * Server profile for Redis 2.2.
7488
  *
7489
  * @author Daniele Alessandri <suppakilla@gmail.com>
7490
  */
7491
- class RedisVersion220 extends RedisProfile
7492
  {
7493
  /**
7494
  * {@inheritdoc}
7495
  */
7496
  public function getVersion()
7497
  {
7498
- return '2.2';
7499
  }
7500
 
7501
  /**
@@ -7519,6 +7726,8 @@ class RedisVersion220 extends RedisProfile
7519
  'TTL' => 'Predis\Command\KeyTimeToLive',
7520
  'MOVE' => 'Predis\Command\KeyMove',
7521
  'SORT' => 'Predis\Command\KeySort',
 
 
7522
 
7523
  /* commands operating on string values */
7524
  'SET' => 'Predis\Command\StringSet',
@@ -7581,7 +7790,7 @@ class RedisVersion220 extends RedisProfile
7581
  'QUIT' => 'Predis\Command\ConnectionQuit',
7582
 
7583
  /* remote server control commands */
7584
- 'INFO' => 'Predis\Command\ServerInfo',
7585
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7586
  'MONITOR' => 'Predis\Command\ServerMonitor',
7587
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
@@ -7669,3358 +7878,4743 @@ class RedisVersion220 extends RedisProfile
7669
  /* remote server control commands */
7670
  'OBJECT' => 'Predis\Command\ServerObject',
7671
  'SLOWLOG' => 'Predis\Command\ServerSlowlog',
7672
- );
7673
- }
7674
- }
7675
 
7676
- /* --------------------------------------------------------------------------- */
7677
 
7678
- namespace Predis;
 
7679
 
7680
- use Predis\Command\CommandInterface;
7681
- use Predis\Command\RawCommand;
7682
- use Predis\Command\ScriptCommand;
7683
- use Predis\Configuration\Options;
7684
- use Predis\Configuration\OptionsInterface;
7685
- use Predis\Connection\AggregateConnectionInterface;
7686
- use Predis\Connection\ConnectionInterface;
7687
- use Predis\Connection\ParametersInterface;
7688
- use Predis\Monitor\Consumer as MonitorConsumer;
7689
- use Predis\Pipeline\Pipeline;
7690
- use Predis\PubSub\Consumer as PubSubConsumer;
7691
- use Predis\Response\ErrorInterface as ErrorResponseInterface;
7692
- use Predis\Response\ResponseInterface;
7693
- use Predis\Response\ServerException;
7694
- use Predis\Transaction\MultiExec as MultiExecTransaction;
7695
- use Predis\Profile\ProfileInterface;
7696
- use Predis\Connection\NodeConnectionInterface;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7697
 
7698
  /**
7699
- * Base exception class for Predis-related errors.
7700
  *
7701
  * @author Daniele Alessandri <suppakilla@gmail.com>
7702
  */
7703
- abstract class PredisException extends \Exception
7704
  {
7705
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7706
 
7707
- /**
7708
- * Interface defining a client-side context such as a pipeline or transaction.
7709
- *
7710
- * @method $this del(array $keys)
7711
- * @method $this dump($key)
7712
- * @method $this exists($key)
7713
- * @method $this expire($key, $seconds)
7714
- * @method $this expireat($key, $timestamp)
7715
- * @method $this keys($pattern)
7716
- * @method $this move($key, $db)
7717
- * @method $this object($subcommand, $key)
7718
- * @method $this persist($key)
7719
- * @method $this pexpire($key, $milliseconds)
7720
- * @method $this pexpireat($key, $timestamp)
7721
- * @method $this pttl($key)
7722
- * @method $this randomkey()
7723
- * @method $this rename($key, $target)
7724
- * @method $this renamenx($key, $target)
7725
- * @method $this scan($cursor, array $options = null)
7726
- * @method $this sort($key, array $options = null)
7727
- * @method $this ttl($key)
7728
- * @method $this type($key)
7729
- * @method $this append($key, $value)
7730
- * @method $this bitcount($key, $start = null, $end = null)
7731
- * @method $this bitop($operation, $destkey, $key)
7732
- * @method $this decr($key)
7733
- * @method $this decrby($key, $decrement)
7734
- * @method $this get($key)
7735
- * @method $this getbit($key, $offset)
7736
- * @method $this getrange($key, $start, $end)
7737
- * @method $this getset($key, $value)
7738
- * @method $this incr($key)
7739
- * @method $this incrby($key, $increment)
7740
- * @method $this incrbyfloat($key, $increment)
7741
- * @method $this mget(array $keys)
7742
- * @method $this mset(array $dictionary)
7743
- * @method $this msetnx(array $dictionary)
7744
- * @method $this psetex($key, $milliseconds, $value)
7745
- * @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
7746
- * @method $this setbit($key, $offset, $value)
7747
- * @method $this setex($key, $seconds, $value)
7748
- * @method $this setnx($key, $value)
7749
- * @method $this setrange($key, $offset, $value)
7750
- * @method $this strlen($key)
7751
- * @method $this hdel($key, array $fields)
7752
- * @method $this hexists($key, $field)
7753
- * @method $this hget($key, $field)
7754
- * @method $this hgetall($key)
7755
- * @method $this hincrby($key, $field, $increment)
7756
- * @method $this hincrbyfloat($key, $field, $increment)
7757
- * @method $this hkeys($key)
7758
- * @method $this hlen($key)
7759
- * @method $this hmget($key, array $fields)
7760
- * @method $this hmset($key, array $dictionary)
7761
- * @method $this hscan($key, $cursor, array $options = null)
7762
- * @method $this hset($key, $field, $value)
7763
- * @method $this hsetnx($key, $field, $value)
7764
- * @method $this hvals($key)
7765
- * @method $this blpop(array $keys, $timeout)
7766
- * @method $this brpop(array $keys, $timeout)
7767
- * @method $this brpoplpush($source, $destination, $timeout)
7768
- * @method $this lindex($key, $index)
7769
- * @method $this linsert($key, $whence, $pivot, $value)
7770
- * @method $this llen($key)
7771
- * @method $this lpop($key)
7772
- * @method $this lpush($key, array $values)
7773
- * @method $this lpushx($key, $value)
7774
- * @method $this lrange($key, $start, $stop)
7775
- * @method $this lrem($key, $count, $value)
7776
- * @method $this lset($key, $index, $value)
7777
- * @method $this ltrim($key, $start, $stop)
7778
- * @method $this rpop($key)
7779
- * @method $this rpoplpush($source, $destination)
7780
- * @method $this rpush($key, array $values)
7781
- * @method $this rpushx($key, $value)
7782
- * @method $this sadd($key, array $members)
7783
- * @method $this scard($key)
7784
- * @method $this sdiff(array $keys)
7785
- * @method $this sdiffstore($destination, array $keys)
7786
- * @method $this sinter(array $keys)
7787
- * @method $this sinterstore($destination, array $keys)
7788
- * @method $this sismember($key, $member)
7789
- * @method $this smembers($key)
7790
- * @method $this smove($source, $destination, $member)
7791
- * @method $this spop($key)
7792
- * @method $this srandmember($key, $count = null)
7793
- * @method $this srem($key, $member)
7794
- * @method $this sscan($key, $cursor, array $options = null)
7795
- * @method $this sunion(array $keys)
7796
- * @method $this sunionstore($destination, array $keys)
7797
- * @method $this zadd($key, array $membersAndScoresDictionary)
7798
- * @method $this zcard($key)
7799
- * @method $this zcount($key, $min, $max)
7800
- * @method $this zincrby($key, $increment, $member)
7801
- * @method $this zinterstore($destination, array $keys, array $options = null)
7802
- * @method $this zrange($key, $start, $stop, array $options = null)
7803
- * @method $this zrangebyscore($key, $min, $max, array $options = null)
7804
- * @method $this zrank($key, $member)
7805
- * @method $this zrem($key, $member)
7806
- * @method $this zremrangebyrank($key, $start, $stop)
7807
- * @method $this zremrangebyscore($key, $min, $max)
7808
- * @method $this zrevrange($key, $start, $stop, array $options = null)
7809
- * @method $this zrevrangebyscore($key, $min, $max, array $options = null)
7810
- * @method $this zrevrank($key, $member)
7811
- * @method $this zunionstore($destination, array $keys, array $options = null)
7812
- * @method $this zscore($key, $member)
7813
- * @method $this zscan($key, $cursor, array $options = null)
7814
- * @method $this zrangebylex($key, $start, $stop, array $options = null)
7815
- * @method $this zremrangebylex($key, $min, $max)
7816
- * @method $this zlexcount($key, $min, $max)
7817
- * @method $this pfadd($key, array $elements)
7818
- * @method $this pfmerge($destinationKey, array $sourceKeys)
7819
- * @method $this pfcount(array $keys)
7820
- * @method $this pubsub($subcommand, $argument)
7821
- * @method $this publish($channel, $message)
7822
- * @method $this discard()
7823
- * @method $this exec()
7824
- * @method $this multi()
7825
- * @method $this unwatch()
7826
- * @method $this watch($key)
7827
- * @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7828
- * @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7829
- * @method $this script($subcommand, $argument = null)
7830
- * @method $this auth($password)
7831
- * @method $this echo($message)
7832
- * @method $this ping($message = null)
7833
- * @method $this select($database)
7834
- * @method $this bgrewriteaof()
7835
- * @method $this bgsave()
7836
- * @method $this client($subcommand, $argument = null)
7837
- * @method $this config($subcommand, $argument = null)
7838
- * @method $this dbsize()
7839
- * @method $this flushall()
7840
- * @method $this flushdb()
7841
- * @method $this info($section = null)
7842
- * @method $this lastsave()
7843
- * @method $this save()
7844
- * @method $this slaveof($host, $port)
7845
- * @method $this slowlog($subcommand, $argument = null)
7846
- * @method $this time()
7847
- * @method $this command()
7848
- *
7849
- * @author Daniele Alessandri <suppakilla@gmail.com>
7850
- */
7851
- interface ClientContextInterface
7852
- {
7853
  /**
7854
- * Sends the specified command instance to Redis.
7855
  *
7856
- * @param CommandInterface $command Command instance.
 
 
 
7857
  *
7858
  * @return mixed
7859
  */
7860
- public function executeCommand(CommandInterface $command);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7861
 
7862
  /**
7863
- * Sends the specified command with its arguments to Redis.
 
 
 
7864
  *
7865
- * @param string $method Command ID.
7866
- * @param array $arguments Arguments for the command.
7867
  *
7868
  * @return mixed
7869
  */
7870
- public function __call($method, $arguments);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7871
 
7872
  /**
7873
- * Starts the execution of the context.
 
7874
  *
7875
- * @param mixed $callable Optional callback for execution.
7876
  *
7877
- * @return array
7878
  */
7879
- public function execute($callable = null);
7880
- }
 
 
7881
 
7882
- /**
7883
- * Base exception class for network-related errors.
7884
- *
7885
- * @author Daniele Alessandri <suppakilla@gmail.com>
7886
- */
7887
- abstract class CommunicationException extends PredisException
7888
- {
7889
- private $connection;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7890
 
7891
  /**
7892
- * @param NodeConnectionInterface $connection Connection that generated the exception.
7893
- * @param string $message Error message.
7894
- * @param int $code Error code.
7895
- * @param \Exception $innerException Inner exception for wrapping the original error.
 
 
7896
  */
7897
- public function __construct(
7898
- NodeConnectionInterface $connection,
7899
- $message = null,
7900
- $code = null,
7901
- \Exception $innerException = null
7902
- ) {
7903
- parent::__construct($message, $code, $innerException);
7904
- $this->connection = $connection;
7905
  }
7906
 
7907
  /**
7908
- * Gets the connection that generated the exception.
7909
  *
7910
- * @return NodeConnectionInterface
 
 
 
7911
  */
7912
- public function getConnection()
7913
  {
7914
- return $this->connection;
 
 
 
 
 
 
7915
  }
7916
 
7917
  /**
7918
- * Indicates if the receiver should reset the underlying connection.
 
7919
  *
7920
- * @return bool
 
 
7921
  */
7922
- public function shouldResetConnection()
7923
  {
7924
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7925
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7926
 
7927
  /**
7928
- * Helper method to handle exceptions generated by a connection object.
7929
  *
7930
- * @param CommunicationException $exception Exception.
7931
  *
7932
- * @throws CommunicationException
7933
  */
7934
- public static function handle(CommunicationException $exception)
7935
- {
7936
- if ($exception->shouldResetConnection()) {
7937
- $connection = $exception->getConnection();
7938
-
7939
- if ($connection->isConnected()) {
7940
- $connection->disconnect();
7941
- }
7942
- }
7943
-
7944
- throw $exception;
7945
- }
7946
  }
7947
 
7948
  /**
7949
- * Interface defining a client able to execute commands against Redis.
7950
- *
7951
- * All the commands exposed by the client generally have the same signature as
7952
- * described by the Redis documentation, but some of them offer an additional
7953
- * and more friendly interface to ease programming which is described in the
7954
- * following list of methods:
7955
  *
7956
- * @method int del(array $keys)
7957
- * @method string dump($key)
7958
- * @method int exists($key)
7959
- * @method int expire($key, $seconds)
7960
- * @method int expireat($key, $timestamp)
7961
- * @method array keys($pattern)
7962
- * @method int move($key, $db)
7963
- * @method mixed object($subcommand, $key)
7964
- * @method int persist($key)
7965
- * @method int pexpire($key, $milliseconds)
7966
- * @method int pexpireat($key, $timestamp)
7967
- * @method int pttl($key)
7968
- * @method string randomkey()
7969
- * @method mixed rename($key, $target)
7970
- * @method int renamenx($key, $target)
7971
- * @method array scan($cursor, array $options = null)
7972
- * @method array sort($key, array $options = null)
7973
- * @method int ttl($key)
7974
- * @method mixed type($key)
7975
- * @method int append($key, $value)
7976
- * @method int bitcount($key, $start = null, $end = null)
7977
- * @method int bitop($operation, $destkey, $key)
7978
- * @method int decr($key)
7979
- * @method int decrby($key, $decrement)
7980
- * @method string get($key)
7981
- * @method int getbit($key, $offset)
7982
- * @method string getrange($key, $start, $end)
7983
- * @method string getset($key, $value)
7984
- * @method int incr($key)
7985
- * @method int incrby($key, $increment)
7986
- * @method string incrbyfloat($key, $increment)
7987
- * @method array mget(array $keys)
7988
- * @method mixed mset(array $dictionary)
7989
- * @method int msetnx(array $dictionary)
7990
- * @method mixed psetex($key, $milliseconds, $value)
7991
- * @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
7992
- * @method int setbit($key, $offset, $value)
7993
- * @method int setex($key, $seconds, $value)
7994
- * @method int setnx($key, $value)
7995
- * @method int setrange($key, $offset, $value)
7996
- * @method int strlen($key)
7997
- * @method int hdel($key, array $fields)
7998
- * @method int hexists($key, $field)
7999
- * @method string hget($key, $field)
8000
- * @method array hgetall($key)
8001
- * @method int hincrby($key, $field, $increment)
8002
- * @method string hincrbyfloat($key, $field, $increment)
8003
- * @method array hkeys($key)
8004
- * @method int hlen($key)
8005
- * @method array hmget($key, array $fields)
8006
- * @method mixed hmset($key, array $dictionary)
8007
- * @method array hscan($key, $cursor, array $options = null)
8008
- * @method int hset($key, $field, $value)
8009
- * @method int hsetnx($key, $field, $value)
8010
- * @method array hvals($key)
8011
- * @method array blpop(array $keys, $timeout)
8012
- * @method array brpop(array $keys, $timeout)
8013
- * @method array brpoplpush($source, $destination, $timeout)
8014
- * @method string lindex($key, $index)
8015
- * @method int linsert($key, $whence, $pivot, $value)
8016
- * @method int llen($key)
8017
- * @method string lpop($key)
8018
- * @method int lpush($key, array $values)
8019
- * @method int lpushx($key, $value)
8020
- * @method array lrange($key, $start, $stop)
8021
- * @method int lrem($key, $count, $value)
8022
- * @method mixed lset($key, $index, $value)
8023
- * @method mixed ltrim($key, $start, $stop)
8024
- * @method string rpop($key)
8025
- * @method string rpoplpush($source, $destination)
8026
- * @method int rpush($key, array $values)
8027
- * @method int rpushx($key, $value)
8028
- * @method int sadd($key, array $members)
8029
- * @method int scard($key)
8030
- * @method array sdiff(array $keys)
8031
- * @method int sdiffstore($destination, array $keys)
8032
- * @method array sinter(array $keys)
8033
- * @method int sinterstore($destination, array $keys)
8034
- * @method int sismember($key, $member)
8035
- * @method array smembers($key)
8036
- * @method int smove($source, $destination, $member)
8037
- * @method string spop($key)
8038
- * @method string srandmember($key, $count = null)
8039
- * @method int srem($key, $member)
8040
- * @method array sscan($key, $cursor, array $options = null)
8041
- * @method array sunion(array $keys)
8042
- * @method int sunionstore($destination, array $keys)
8043
- * @method int zadd($key, array $membersAndScoresDictionary)
8044
- * @method int zcard($key)
8045
- * @method string zcount($key, $min, $max)
8046
- * @method string zincrby($key, $increment, $member)
8047
- * @method int zinterstore($destination, array $keys, array $options = null)
8048
- * @method array zrange($key, $start, $stop, array $options = null)
8049
- * @method array zrangebyscore($key, $min, $max, array $options = null)
8050
- * @method int zrank($key, $member)
8051
- * @method int zrem($key, $member)
8052
- * @method int zremrangebyrank($key, $start, $stop)
8053
- * @method int zremrangebyscore($key, $min, $max)
8054
- * @method array zrevrange($key, $start, $stop, array $options = null)
8055
- * @method array zrevrangebyscore($key, $min, $max, array $options = null)
8056
- * @method int zrevrank($key, $member)
8057
- * @method int zunionstore($destination, array $keys, array $options = null)
8058
- * @method string zscore($key, $member)
8059
- * @method array zscan($key, $cursor, array $options = null)
8060
- * @method array zrangebylex($key, $start, $stop, array $options = null)
8061
- * @method int zremrangebylex($key, $min, $max)
8062
- * @method int zlexcount($key, $min, $max)
8063
- * @method int pfadd($key, array $elements)
8064
- * @method mixed pfmerge($destinationKey, array $sourceKeys)
8065
- * @method int pfcount(array $keys)
8066
- * @method mixed pubsub($subcommand, $argument)
8067
- * @method int publish($channel, $message)
8068
- * @method mixed discard()
8069
- * @method array exec()
8070
- * @method mixed multi()
8071
- * @method mixed unwatch()
8072
- * @method mixed watch($key)
8073
- * @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
8074
- * @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
8075
- * @method mixed script($subcommand, $argument = null)
8076
- * @method mixed auth($password)
8077
- * @method string echo($message)
8078
- * @method mixed ping($message = null)
8079
- * @method mixed select($database)
8080
- * @method mixed bgrewriteaof()
8081
- * @method mixed bgsave()
8082
- * @method mixed client($subcommand, $argument = null)
8083
- * @method mixed config($subcommand, $argument = null)
8084
- * @method int dbsize()
8085
- * @method mixed flushall()
8086
- * @method mixed flushdb()
8087
- * @method array info($section = null)
8088
- * @method int lastsave()
8089
- * @method mixed save()
8090
- * @method mixed slaveof($host, $port)
8091
- * @method mixed slowlog($subcommand, $argument = null)
8092
- * @method array time()
8093
- * @method array command()
8094
  *
8095
  * @author Daniele Alessandri <suppakilla@gmail.com>
8096
  */
8097
- interface ClientInterface
8098
  {
8099
  /**
8100
- * Returns the server profile used by the client.
8101
  *
8102
- * @return ProfileInterface
 
 
8103
  */
8104
- public function getProfile();
8105
 
8106
  /**
8107
- * Returns the client options specified upon initialization.
8108
  *
8109
- * @return OptionsInterface
 
 
8110
  */
8111
- public function getOptions();
8112
 
8113
  /**
8114
- * Opens the underlying connection to the server.
 
 
 
 
8115
  */
8116
- public function connect();
8117
 
8118
  /**
8119
- * Closes the underlying connection from the server.
 
 
 
 
8120
  */
8121
- public function disconnect();
 
8122
 
 
 
 
 
 
 
 
 
 
8123
  /**
8124
- * Returns the underlying connection instance.
8125
  *
8126
- * @return ConnectionInterface
 
 
 
8127
  */
8128
- public function getConnection();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8129
 
8130
  /**
8131
- * Creates a new instance of the specified Redis command.
8132
- *
8133
- * @param string $method Command ID.
8134
- * @param array $arguments Arguments for the command.
8135
- *
8136
- * @return CommandInterface
8137
  */
8138
- public function createCommand($method, $arguments = array());
 
 
 
 
 
 
 
 
 
 
8139
 
8140
- /**
8141
- * Executes the specified Redis command.
8142
- *
8143
- * @param CommandInterface $command Command instance.
8144
- *
8145
- * @return mixed
8146
- */
8147
- public function executeCommand(CommandInterface $command);
8148
 
8149
  /**
8150
- * Creates a Redis command with the specified arguments and sends a request
8151
- * to the server.
8152
- *
8153
- * @param string $method Command ID.
8154
- * @param array $arguments Arguments for the command.
8155
- *
8156
- * @return mixed
8157
  */
8158
- public function __call($method, $arguments);
 
 
 
8159
  }
8160
 
8161
  /**
8162
- * Exception class thrown when trying to use features not supported by certain
8163
- * classes or abstractions of Predis.
8164
  *
8165
  * @author Daniele Alessandri <suppakilla@gmail.com>
8166
  */
8167
- class NotSupportedException extends PredisException
8168
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8169
  }
8170
 
8171
  /**
8172
- * Exception class that identifies client-side errors.
 
8173
  *
8174
  * @author Daniele Alessandri <suppakilla@gmail.com>
8175
  */
8176
- class ClientException extends PredisException
8177
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8178
  }
8179
 
8180
  /**
8181
- * Client class used for connecting and executing commands on Redis.
8182
- *
8183
- * This is the main high-level abstraction of Predis upon which various other
8184
- * abstractions are built. Internally it aggregates various other classes each
8185
- * one with its own responsibility and scope.
8186
  *
8187
  * {@inheritdoc}
8188
  *
8189
  * @author Daniele Alessandri <suppakilla@gmail.com>
8190
  */
8191
- class Client implements ClientInterface
8192
  {
8193
- const VERSION = '1.0.3';
8194
-
8195
- protected $connection;
8196
  protected $options;
8197
- private $profile;
8198
 
8199
  /**
8200
- * @param mixed $parameters Connection parameters for one or more servers.
8201
- * @param mixed $options Options to configure some behaviours of the client.
8202
  */
8203
- public function __construct($parameters = null, $options = null)
8204
  {
8205
- $this->options = $this->createOptions($options ?: array());
8206
- $this->connection = $this->createConnection($parameters ?: array());
8207
- $this->profile = $this->options->profile;
8208
  }
8209
 
8210
  /**
8211
- * Creates a new instance of Predis\Configuration\Options from different
8212
- * types of arguments or simply returns the passed argument if it is an
8213
- * instance of Predis\Configuration\OptionsInterface.
8214
- *
8215
- * @param mixed $options Client options.
8216
- *
8217
- * @throws \InvalidArgumentException
8218
  *
8219
- * @return OptionsInterface
8220
  */
8221
- protected function createOptions($options)
8222
  {
8223
- if (is_array($options)) {
8224
- return new Options($options);
8225
- }
 
 
 
 
 
 
8226
 
8227
- if ($options instanceof OptionsInterface) {
8228
- return $options;
 
 
 
 
 
 
 
 
8229
  }
 
8230
 
8231
- throw new \InvalidArgumentException('Invalid type for client options.');
 
 
 
 
 
 
 
 
8232
  }
8233
 
8234
  /**
8235
- * Creates single or aggregate connections from different types of arguments
8236
- * (string, array) or returns the passed argument if it is an instance of a
8237
- * class implementing Predis\Connection\ConnectionInterface.
8238
- *
8239
- * Accepted types for connection parameters are:
8240
- *
8241
- * - Instance of Predis\Connection\ConnectionInterface.
8242
- * - Instance of Predis\Connection\ParametersInterface.
8243
- * - Array
8244
- * - String
8245
- * - Callable
8246
- *
8247
- * @param mixed $parameters Connection parameters or connection instance.
8248
- *
8249
- * @throws \InvalidArgumentException
8250
- *
8251
- * @return ConnectionInterface
8252
  */
8253
- protected function createConnection($parameters)
8254
  {
8255
- if ($parameters instanceof ConnectionInterface) {
8256
- return $parameters;
8257
- }
 
 
8258
 
8259
- if ($parameters instanceof ParametersInterface || is_string($parameters)) {
8260
- return $this->options->connections->create($parameters);
 
 
 
 
 
8261
  }
8262
 
8263
- if (is_array($parameters)) {
8264
- if (!isset($parameters[0])) {
8265
- return $this->options->connections->create($parameters);
8266
- }
8267
-
8268
- $options = $this->options;
8269
 
8270
- if ($options->defined('aggregate')) {
8271
- $initializer = $this->getConnectionInitializerWrapper($options->aggregate);
8272
- $connection = $initializer($parameters, $options);
8273
- } else {
8274
- if ($options->defined('replication') && $replication = $options->replication) {
8275
- $connection = $replication;
8276
- } else {
8277
- $connection = $options->cluster;
8278
- }
8279
 
8280
- $options->connections->aggregate($connection, $parameters);
 
 
 
8281
  }
8282
 
8283
- return $connection;
8284
  }
8285
 
8286
- if (is_callable($parameters)) {
8287
- $initializer = $this->getConnectionInitializerWrapper($parameters);
8288
- $connection = $initializer($this->options);
8289
 
8290
- return $connection;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8291
  }
8292
 
8293
- throw new \InvalidArgumentException('Invalid type for connection parameters.');
8294
  }
8295
 
8296
  /**
8297
- * Wraps a callable to make sure that its returned value represents a valid
8298
- * connection type.
8299
- *
8300
- * @param mixed $callable
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8301
  *
8302
- * @return \Closure
 
8303
  */
8304
- protected function getConnectionInitializerWrapper($callable)
8305
  {
8306
- return function () use ($callable) {
8307
- $connection = call_user_func_array($callable, func_get_args());
8308
-
8309
- if (!$connection instanceof ConnectionInterface) {
8310
- throw new \UnexpectedValueException(
8311
- 'The callable connection initializer returned an invalid type.'
8312
- );
8313
- }
8314
 
8315
- return $connection;
8316
- };
8317
  }
8318
 
8319
  /**
8320
  * {@inheritdoc}
8321
  */
8322
- public function getProfile()
8323
  {
8324
- return $this->profile;
 
 
 
 
 
 
 
8325
  }
8326
 
8327
  /**
8328
  * {@inheritdoc}
8329
  */
8330
- public function getOptions()
8331
  {
8332
- return $this->options;
 
 
 
8333
  }
 
8334
 
 
 
 
 
 
 
 
 
8335
  /**
8336
- * Creates a new client instance for the specified connection ID or alias,
8337
- * only when working with an aggregate connection (cluster, replication).
8338
- * The new client instances uses the same options of the original one.
8339
- *
8340
- * @param string $connectionID Identifier of a connection.
8341
- *
8342
- * @throws \InvalidArgumentException
8343
  *
8344
- * @return Client
 
 
8345
  */
8346
- public function getClientFor($connectionID)
8347
  {
8348
- if (!$connection = $this->getConnectionById($connectionID)) {
8349
- throw new \InvalidArgumentException("Invalid connection ID: $connectionID.");
8350
  }
8351
 
8352
- return new static($connection, $this->options);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8353
  }
8354
 
8355
  /**
8356
- * Opens the underlying connection and connects to the server.
8357
  */
8358
- public function connect()
8359
  {
8360
- $this->connection->connect();
 
 
 
 
 
 
 
8361
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8362
 
 
 
 
 
 
 
 
 
8363
  /**
8364
- * Closes the underlying connection and disconnects from the server.
 
 
8365
  */
8366
- public function disconnect()
8367
- {
8368
- $this->connection->disconnect();
8369
- }
8370
 
8371
  /**
8372
- * Closes the underlying connection and disconnects from the server.
8373
  *
8374
- * This is the same as `Client::disconnect()` as it does not actually send
8375
- * the `QUIT` command to Redis, but simply closes the connection.
8376
  */
8377
- public function quit()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8378
  {
8379
- $this->disconnect();
8380
  }
8381
 
8382
  /**
8383
- * Returns the current state of the underlying connection.
8384
- *
8385
- * @return bool
8386
  */
8387
- public function isConnected()
8388
  {
8389
- return $this->connection->isConnected();
8390
  }
8391
 
8392
  /**
8393
  * {@inheritdoc}
8394
  */
8395
- public function getConnection()
8396
  {
8397
- return $this->connection;
 
 
8398
  }
8399
 
8400
  /**
8401
- * Retrieves the specified connection from the aggregate connection when the
8402
- * client is in cluster or replication mode.
8403
- *
8404
- * @param string $connectionID Index or alias of the single connection.
8405
- *
8406
- * @throws NotSupportedException
8407
  *
8408
- * @return Connection\NodeConnectionInterface
8409
  */
8410
- public function getConnectionById($connectionID)
8411
  {
8412
- if (!$this->connection instanceof AggregateConnectionInterface) {
8413
- throw new NotSupportedException(
8414
- 'Retrieving connections by ID is supported only by aggregate connections.'
8415
- );
8416
- }
8417
-
8418
- return $this->connection->getConnectionById($connectionID);
8419
  }
 
8420
 
 
 
 
 
 
 
 
8421
  /**
8422
- * Executes a command without filtering its arguments, parsing the response,
8423
- * applying any prefix to keys or throwing exceptions on Redis errors even
8424
- * regardless of client options.
8425
- *
8426
- * It is possibile to indentify Redis error responses from normal responses
8427
- * using the second optional argument which is populated by reference.
8428
- *
8429
- * @param array $arguments Command arguments as defined by the command signature.
8430
- * @param bool $error Set to TRUE when Redis returned an error response.
8431
  *
8432
- * @return mixed
8433
  */
8434
- public function executeRaw(array $arguments, &$error = null)
8435
  {
8436
- $error = false;
8437
 
8438
- $response = $this->connection->executeCommand(
8439
- new RawCommand($arguments)
8440
- );
8441
 
8442
- if ($response instanceof ResponseInterface) {
8443
- if ($response instanceof ErrorResponseInterface) {
8444
- $error = true;
8445
- }
 
 
 
 
 
 
8446
 
8447
- return (string) $response;
8448
- }
 
 
 
 
 
 
 
8449
 
8450
- return $response;
8451
- }
8452
 
8453
  /**
8454
- * {@inheritdoc}
8455
  */
8456
- public function __call($commandID, $arguments)
8457
  {
8458
- return $this->executeCommand(
8459
- $this->createCommand($commandID, $arguments)
8460
- );
8461
  }
8462
 
8463
  /**
8464
- * {@inheritdoc}
 
 
8465
  */
8466
- public function createCommand($commandID, $arguments = array())
8467
  {
8468
- return $this->profile->createCommand($commandID, $arguments);
8469
  }
8470
 
8471
  /**
8472
- * {@inheritdoc}
 
 
8473
  */
8474
- public function executeCommand(CommandInterface $command)
8475
  {
8476
- $response = $this->connection->executeCommand($command);
8477
-
8478
- if ($response instanceof ResponseInterface) {
8479
- if ($response instanceof ErrorResponseInterface) {
8480
- $response = $this->onErrorResponse($command, $response);
8481
- }
8482
-
8483
- return $response;
8484
- }
8485
-
8486
- return $command->parseResponse($response);
8487
  }
8488
 
8489
  /**
8490
- * Handles -ERR responses returned by Redis.
8491
  *
8492
- * @param CommandInterface $command Redis command that generated the error.
8493
- * @param ErrorResponseInterface $response Instance of the error response.
8494
  *
8495
- * @throws ServerException
8496
  *
8497
- * @return mixed
8498
  */
8499
- protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
8500
  {
8501
- if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
8502
- $eval = $this->createCommand('EVAL');
8503
- $eval->setRawArguments($command->getEvalArguments());
8504
-
8505
- $response = $this->executeCommand($eval);
 
8506
 
8507
- if (!$response instanceof ResponseInterface) {
8508
- $response = $command->parseResponse($response);
8509
- }
8510
 
8511
- return $response;
 
8512
  }
 
 
8513
 
8514
- if ($this->options->exceptions) {
8515
- throw new ServerException($response->getMessage());
8516
- }
8517
 
8518
- return $response;
8519
- }
 
 
 
 
 
 
8520
 
 
 
 
 
 
 
 
8521
  /**
8522
- * Executes the specified initializer method on `$this` by adjusting the
8523
- * actual invokation depending on the arity (0, 1 or 2 arguments). This is
8524
- * simply an utility method to create Redis contexts instances since they
8525
- * follow a common initialization path.
8526
  *
8527
- * @param string $initializer Method name.
8528
- * @param array $argv Arguments for the method.
8529
  *
8530
  * @return mixed
8531
  */
8532
- private function sharedContextFactory($initializer, $argv = null)
8533
- {
8534
- switch (count($argv)) {
8535
- case 0:
8536
- return $this->$initializer();
8537
 
8538
- case 1:
8539
- return is_array($argv[0])
8540
- ? $this->$initializer($argv[0])
8541
- : $this->$initializer(null, $argv[0]);
 
 
 
 
 
 
 
 
 
 
 
 
8542
 
8543
- case 2:
8544
- list($arg0, $arg1) = $argv;
 
 
 
8545
 
8546
- return $this->$initializer($arg0, $arg1);
 
 
8547
 
8548
- default:
8549
- return $this->$initializer($this, $argv);
8550
  }
 
 
 
 
 
 
8551
  }
 
8552
 
 
 
 
 
 
 
 
 
 
 
8553
  /**
8554
- * Creates a new pipeline context and returns it, or returns the results of
8555
- * a pipeline executed inside the optionally provided callable object.
8556
- *
8557
- * @param mixed ... Array of options, a callable for execution, or both.
8558
- *
8559
- * @return Pipeline|array
8560
  */
8561
- public function pipeline(/* arguments */)
8562
  {
8563
- return $this->sharedContextFactory('createPipeline', func_get_args());
8564
  }
 
8565
 
 
 
 
 
 
 
 
 
 
 
8566
  /**
8567
- * Actual pipeline context initializer method.
8568
- *
8569
- * @param array $options Options for the context.
8570
- * @param mixed $callable Optional callable used to execute the context.
8571
- *
8572
- * @return Pipeline|array
8573
  */
8574
- protected function createPipeline(array $options = null, $callable = null)
8575
  {
8576
- if (isset($options['atomic']) && $options['atomic']) {
8577
- $class = 'Predis\Pipeline\Atomic';
8578
- } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
8579
- $class = 'Predis\Pipeline\FireAndForget';
8580
- } else {
8581
- $class = 'Predis\Pipeline\Pipeline';
8582
  }
8583
 
8584
- /*
8585
- * @var ClientContextInterface
8586
- */
8587
- $pipeline = new $class($this);
8588
-
8589
- if (isset($callable)) {
8590
- return $pipeline->execute($callable);
8591
  }
8592
 
8593
- return $pipeline;
8594
  }
 
8595
 
 
 
 
 
 
 
 
 
 
 
8596
  /**
8597
- * Creates a new transaction context and returns it, or returns the results
8598
- * of a transaction executed inside the optionally provided callable object.
8599
- *
8600
- * @param mixed ... Array of options, a callable for execution, or both.
8601
- *
8602
- * @return MultiExecTransaction|array
8603
  */
8604
- public function transaction(/* arguments */)
8605
  {
8606
- return $this->sharedContextFactory('createTransaction', func_get_args());
8607
- }
8608
 
8609
- /**
8610
- * Actual transaction context initializer method.
8611
- *
8612
- * @param array $options Options for the context.
8613
- * @param mixed $callable Optional callable used to execute the context.
8614
- *
8615
- * @return MultiExecTransaction|array
8616
- */
8617
- protected function createTransaction(array $options = null, $callable = null)
8618
- {
8619
- $transaction = new MultiExecTransaction($this, $options);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8620
 
8621
- if (isset($callable)) {
8622
- return $transaction->execute($callable);
8623
  }
8624
 
8625
- return $transaction;
8626
  }
 
8627
 
 
 
 
 
 
 
 
 
 
 
 
8628
  /**
8629
- * Creates a new publis/subscribe context and returns it, or starts its loop
8630
- * inside the optionally provided callable object.
8631
- *
8632
- * @param mixed ... Array of options, a callable for execution, or both.
8633
- *
8634
- * @return PubSubConsumer|null
8635
  */
8636
- public function pubSubLoop(/* arguments */)
8637
  {
8638
- return $this->sharedContextFactory('createPubSub', func_get_args());
8639
  }
 
8640
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8641
  /**
8642
- * Actual publish/subscribe context initializer method.
8643
- *
8644
- * @param array $options Options for the context.
8645
- * @param mixed $callable Optional callable used to execute the context.
8646
- *
8647
- * @return PubSubConsumer|null
8648
  */
8649
- protected function createPubSub(array $options = null, $callable = null)
8650
  {
8651
- $pubsub = new PubSubConsumer($this, $options);
8652
-
8653
- if (!isset($callable)) {
8654
- return $pubsub;
8655
- }
8656
 
8657
- foreach ($pubsub as $message) {
8658
- if (call_user_func($callable, $pubsub, $message) === false) {
8659
- $pubsub->stop();
8660
- }
8661
  }
8662
- }
8663
 
8664
- /**
8665
- * Creates a new monitor consumer and returns it.
8666
- *
8667
- * @return MonitorConsumer
8668
- */
8669
- public function monitor()
8670
- {
8671
- return new MonitorConsumer($this);
8672
  }
8673
  }
8674
 
 
 
 
 
 
 
 
8675
  /**
8676
- * Implements a lightweight PSR-0 compliant autoloader for Predis.
 
 
 
 
 
 
 
 
8677
  *
8678
- * @author Eric Naeseth <eric@thumbtack.com>
8679
  * @author Daniele Alessandri <suppakilla@gmail.com>
8680
  */
8681
- class Autoloader
8682
  {
8683
- private $directory;
8684
- private $prefix;
8685
- private $prefixLength;
 
 
 
 
 
 
 
8686
 
8687
  /**
8688
- * @param string $baseDirectory Base directory where the source files are located.
 
 
8689
  */
8690
- public function __construct($baseDirectory = __DIR__)
8691
  {
8692
- $this->directory = $baseDirectory;
8693
- $this->prefix = __NAMESPACE__.'\\';
8694
- $this->prefixLength = strlen($this->prefix);
 
 
8695
  }
8696
 
8697
  /**
8698
- * Registers the autoloader class with the PHP SPL autoloader.
 
8699
  *
8700
- * @param bool $prepend Prepend the autoloader on the stack instead of appending it.
 
 
 
8701
  */
8702
- public static function register($prepend = false)
8703
  {
8704
- spl_autoload_register(array(new self(), 'autoload'), true, $prepend);
 
 
8705
  }
8706
 
8707
  /**
8708
- * Loads a class from a file using its fully qualified name.
 
 
 
 
 
 
 
 
 
 
 
 
 
8709
  *
8710
- * @param string $className Fully qualified name of a class.
8711
  */
8712
- public function autoload($className)
8713
  {
8714
- if (0 === strpos($className, $this->prefix)) {
8715
- $parts = explode('\\', substr($className, $this->prefixLength));
8716
- $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
8717
 
8718
- if (is_file($filepath)) {
8719
- require $filepath;
8720
- }
8721
  }
8722
- }
8723
- }
8724
-
8725
- /* --------------------------------------------------------------------------- */
8726
 
8727
- namespace Predis\Configuration;
 
 
8728
 
8729
- use Predis\Connection\Aggregate\ClusterInterface;
8730
- use Predis\Connection\Aggregate\PredisCluster;
8731
- use Predis\Connection\Aggregate\RedisCluster;
8732
- use Predis\Connection\Factory;
8733
- use Predis\Connection\FactoryInterface;
8734
- use Predis\Command\Processor\KeyPrefixProcessor;
8735
- use Predis\Command\Processor\ProcessorInterface;
8736
- use Predis\Profile\Factory as Predis_Factory;
8737
- use Predis\Profile\ProfileInterface;
8738
- use Predis\Profile\RedisProfile;
8739
- use Predis\Connection\Aggregate\MasterSlaveReplication;
8740
- use Predis\Connection\Aggregate\ReplicationInterface;
8741
 
8742
- /**
8743
- * Defines an handler used by Predis\Configuration\Options to filter, validate
8744
- * or return default values for a given option.
8745
- *
8746
- * @author Daniele Alessandri <suppakilla@gmail.com>
8747
- */
8748
- interface OptionInterface
8749
- {
8750
  /**
8751
- * Filters and validates the passed value.
8752
- *
8753
- * @param OptionsInterface $options Options container.
8754
- * @param mixed $value Input value.
8755
  *
8756
- * @return mixed
8757
  */
8758
- public function filter(OptionsInterface $options, $value);
8759
 
8760
  /**
8761
- * Returns the default value for the option.
8762
- *
8763
- * @param OptionsInterface $options Options container.
8764
- *
8765
- * @return mixed
8766
  */
8767
- public function getDefault(OptionsInterface $options);
8768
- }
 
 
 
 
 
 
 
 
 
8769
 
8770
- /**
8771
- * Interface defining a container for client options.
8772
- *
8773
- * @property-read mixed aggregate Custom connection aggregator.
8774
- * @property-read mixed cluster Aggregate connection for clustering.
8775
- * @property-read mixed connections Connection factory.
8776
- * @property-read mixed exceptions Toggles exceptions in client for -ERR responses.
8777
- * @property-read mixed prefix Key prefixing strategy using the given prefix.
8778
- * @property-read mixed profile Server profile.
8779
- * @property-read mixed replication Aggregate connection for replication.
8780
- *
8781
- * @author Daniele Alessandri <suppakilla@gmail.com>
8782
- */
8783
- interface OptionsInterface
8784
- {
8785
  /**
8786
- * Returns the default value for the given option.
8787
- *
8788
- * @param string $option Name of the option.
8789
- *
8790
- * @return mixed|null
8791
  */
8792
- public function getDefault($option);
 
 
 
 
8793
 
8794
  /**
8795
- * Checks if the given option has been set by the user upon initialization.
8796
- *
8797
- * @param string $option Name of the option.
8798
- *
8799
- * @return bool
8800
  */
8801
- public function defined($option);
 
 
 
 
8802
 
8803
  /**
8804
- * Checks if the given option has been set and does not evaluate to NULL.
8805
- *
8806
- * @param string $option Name of the option.
8807
- *
8808
- * @return bool
8809
  */
8810
- public function __isset($option);
 
 
 
8811
 
8812
  /**
8813
- * Returns the value of the given option.
8814
- *
8815
- * @param string $option Name of the option.
8816
- *
8817
- * @return mixed|null
8818
  */
8819
- public function __get($option);
8820
- }
 
 
8821
 
8822
- /**
8823
- * Configures a command processor that apply the specified prefix string to a
8824
- * series of Redis commands considered prefixable.
8825
- *
8826
- * @author Daniele Alessandri <suppakilla@gmail.com>
8827
- */
8828
- class PrefixOption implements OptionInterface
8829
- {
8830
  /**
8831
  * {@inheritdoc}
8832
  */
8833
- public function filter(OptionsInterface $options, $value)
8834
  {
8835
- if ($value instanceof ProcessorInterface) {
8836
- return $value;
8837
- }
 
8838
 
8839
- return new KeyPrefixProcessor($value);
 
 
 
 
 
 
 
8840
  }
8841
 
8842
  /**
8843
  * {@inheritdoc}
8844
  */
8845
- public function getDefault(OptionsInterface $options)
8846
  {
8847
- // NOOP
8848
  }
8849
  }
8850
 
8851
  /**
8852
- * Configures the server profile to be used by the client to create command
8853
- * instances depending on the specified version of the Redis server.
8854
  *
8855
  * @author Daniele Alessandri <suppakilla@gmail.com>
 
 
8856
  */
8857
- class ProfileOption implements OptionInterface
8858
  {
 
 
8859
  /**
8860
- * Sets the commands processors that need to be applied to the profile.
8861
- *
8862
- * @param OptionsInterface $options Client options.
8863
- * @param ProfileInterface $profile Server profile.
8864
  */
8865
- protected function setProcessors(OptionsInterface $options, ProfileInterface $profile)
8866
  {
8867
- if (isset($options->prefix) && $profile instanceof RedisProfile) {
8868
- // NOTE: directly using __get('prefix') is actually a workaround for
8869
- // HHVM 2.3.0. It's correct and respects the options interface, it's
8870
- // just ugly. We will remove this hack when HHVM will fix re-entrant
8871
- // calls to __get() once and for all.
8872
 
8873
- $profile->setProcessor($options->__get('prefix'));
8874
- }
 
8875
  }
8876
 
8877
  /**
8878
  * {@inheritdoc}
8879
  */
8880
- public function filter(OptionsInterface $options, $value)
8881
  {
8882
- if (is_string($value)) {
8883
- $value = Predis_Factory::get($value);
8884
- $this->setProcessors($options, $value);
8885
- } elseif (!$value instanceof ProfileInterface) {
8886
- throw new \InvalidArgumentException('Invalid value for the profile option.');
8887
- }
8888
-
8889
- return $value;
8890
  }
8891
 
8892
  /**
8893
  * {@inheritdoc}
8894
  */
8895
- public function getDefault(OptionsInterface $options)
8896
  {
8897
- $profile = Predis_Factory::getDefault();
8898
- $this->setProcessors($options, $profile);
 
8899
 
8900
- return $profile;
 
8901
  }
8902
  }
8903
 
8904
  /**
8905
- * Configures an aggregate connection used for master/slave replication among
8906
- * multiple Redis nodes.
8907
  *
8908
  * @author Daniele Alessandri <suppakilla@gmail.com>
 
 
8909
  */
8910
- class ReplicationOption implements OptionInterface
8911
  {
8912
  /**
8913
  * {@inheritdoc}
8914
- *
8915
- * @todo There's more code than needed due to a bug in filter_var() as
8916
- * discussed here https://bugs.php.net/bug.php?id=49510 and different
8917
- * behaviours when encountering NULL values on PHP 5.3.
8918
  */
8919
- public function filter(OptionsInterface $options, $value)
8920
  {
8921
- if ($value instanceof ReplicationInterface) {
8922
- return $value;
8923
- }
8924
-
8925
- if (is_bool($value) || $value === null) {
8926
- return $value ? $this->getDefault($options) : null;
8927
- }
8928
-
8929
- if (
8930
- !is_object($value) &&
8931
- null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)
8932
- ) {
8933
- return $asbool ? $this->getDefault($options) : null;
8934
- }
8935
 
8936
- throw new \InvalidArgumentException(
8937
- "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected."
8938
- );
8939
  }
8940
 
8941
  /**
8942
  * {@inheritdoc}
8943
  */
8944
- public function getDefault(OptionsInterface $options)
8945
  {
8946
- return new MasterSlaveReplication();
8947
  }
8948
  }
8949
 
8950
  /**
8951
- * Manages Predis options with filtering, conversion and lazy initialization of
8952
- * values using a mini-DI container approach.
8953
  *
8954
- * {@inheritdoc}
 
 
 
 
8955
  *
8956
  * @author Daniele Alessandri <suppakilla@gmail.com>
 
 
8957
  */
8958
- class Options implements OptionsInterface
8959
  {
8960
- protected $input;
8961
- protected $options;
8962
- protected $handlers;
 
 
 
 
 
 
8963
 
8964
  /**
8965
- * @param array $options Array of options with their values
 
 
 
 
8966
  */
8967
- public function __construct(array $options = array())
8968
  {
8969
- $this->input = $options;
8970
- $this->options = array();
8971
- $this->handlers = $this->getHandlers();
 
 
 
 
 
 
 
 
8972
  }
8973
 
8974
  /**
8975
- * Ensures that the default options are initialized.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8976
  *
8977
  * @return array
8978
  */
8979
- protected function getHandlers()
8980
  {
8981
- return array(
8982
- 'cluster' => 'Predis\Configuration\ClusterOption',
8983
- 'connections' => 'Predis\Configuration\ConnectionFactoryOption',
8984
- 'exceptions' => 'Predis\Configuration\ExceptionsOption',
8985
- 'prefix' => 'Predis\Configuration\PrefixOption',
8986
- 'profile' => 'Predis\Configuration\ProfileOption',
8987
- 'replication' => 'Predis\Configuration\ReplicationOption',
8988
- );
8989
  }
8990
 
8991
  /**
8992
- * {@inheritdoc}
 
8993
  */
8994
- public function getDefault($option)
8995
  {
8996
- if (isset($this->handlers[$option])) {
8997
- $handler = $this->handlers[$option];
8998
- $handler = new $handler();
8999
 
9000
- return $handler->getDefault($this);
 
9001
  }
 
 
 
 
 
 
 
 
 
 
 
9002
  }
9003
 
9004
  /**
9005
  * {@inheritdoc}
9006
  */
9007
- public function defined($option)
9008
  {
9009
- return (
9010
- array_key_exists($option, $this->options) ||
9011
- array_key_exists($option, $this->input)
9012
- );
9013
  }
9014
 
9015
  /**
9016
  * {@inheritdoc}
9017
  */
9018
- public function __isset($option)
9019
  {
9020
- return (
9021
- array_key_exists($option, $this->options) ||
9022
- array_key_exists($option, $this->input)
9023
- ) && $this->__get($option) !== null;
9024
  }
9025
 
9026
  /**
9027
  * {@inheritdoc}
9028
  */
9029
- public function __get($option)
9030
  {
9031
- if (isset($this->options[$option]) || array_key_exists($option, $this->options)) {
9032
- return $this->options[$option];
9033
- }
9034
-
9035
- if (isset($this->input[$option]) || array_key_exists($option, $this->input)) {
9036
- $value = $this->input[$option];
9037
- unset($this->input[$option]);
9038
-
9039
- if (is_object($value) && method_exists($value, '__invoke')) {
9040
- $value = $value($this, $option);
9041
- }
9042
-
9043
- if (isset($this->handlers[$option])) {
9044
- $handler = $this->handlers[$option];
9045
- $handler = new $handler();
9046
- $value = $handler->filter($this, $value);
9047
- }
9048
-
9049
- return $this->options[$option] = $value;
9050
- }
9051
-
9052
- if (isset($this->handlers[$option])) {
9053
- return $this->options[$option] = $this->getDefault($option);
9054
- }
9055
-
9056
- return;
9057
  }
9058
- }
9059
 
9060
- /**
9061
- * Configures a connection factory used by the client to create new connection
9062
- * instances for single Redis nodes.
9063
- *
9064
- * @author Daniele Alessandri <suppakilla@gmail.com>
9065
- */
9066
- class ConnectionFactoryOption implements OptionInterface
9067
- {
9068
  /**
9069
  * {@inheritdoc}
9070
  */
9071
- public function filter(OptionsInterface $options, $value)
9072
  {
9073
- if ($value instanceof FactoryInterface) {
9074
- return $value;
9075
- } elseif (is_array($value)) {
9076
- $factory = $this->getDefault($options);
9077
-
9078
- foreach ($value as $scheme => $initializer) {
9079
- $factory->define($scheme, $initializer);
9080
- }
9081
 
9082
- return $factory;
 
9083
  } else {
9084
- throw new \InvalidArgumentException(
9085
- 'Invalid value provided for the connections option.'
9086
- );
9087
  }
9088
  }
9089
 
9090
  /**
9091
  * {@inheritdoc}
9092
  */
9093
- public function getDefault(OptionsInterface $options)
9094
  {
9095
- return new Factory();
9096
  }
9097
  }
9098
 
9099
  /**
9100
- * Configures whether consumers (such as the client) should throw exceptions on
9101
- * Redis errors (-ERR responses) or just return instances of error responses.
9102
  *
9103
  * @author Daniele Alessandri <suppakilla@gmail.com>
 
 
9104
  */
9105
- class ExceptionsOption implements OptionInterface
9106
  {
 
 
9107
  /**
9108
  * {@inheritdoc}
9109
  */
9110
- public function filter(OptionsInterface $options, $value)
9111
  {
9112
- return filter_var($value, FILTER_VALIDATE_BOOLEAN);
 
 
 
 
9113
  }
9114
 
9115
  /**
9116
  * {@inheritdoc}
9117
  */
9118
- public function getDefault(OptionsInterface $options)
9119
  {
9120
- return true;
9121
  }
9122
  }
9123
 
9124
  /**
9125
- * Configures an aggregate connection used for clustering
9126
- * multiple Redis nodes using various implementations with
9127
- * different algorithms or strategies.
9128
  *
9129
  * @author Daniele Alessandri <suppakilla@gmail.com>
 
 
9130
  */
9131
- class ClusterOption implements OptionInterface
9132
  {
 
 
9133
  /**
9134
- * Creates a new cluster connection from on a known descriptive name.
9135
- *
9136
- * @param OptionsInterface $options Instance of the client options.
9137
- * @param string $id Descriptive identifier of the cluster type (`predis`, `redis-cluster`)
9138
- *
9139
- * @return ClusterInterface|null
9140
  */
9141
- protected function createByDescription(OptionsInterface $options, $id)
9142
  {
9143
- switch ($id) {
9144
- case 'predis':
9145
- case 'predis-cluster':
9146
- return new PredisCluster();
9147
 
9148
- case 'redis':
9149
- case 'redis-cluster':
9150
- return new RedisCluster($options->connections);
9151
 
9152
- default:
9153
- return;
9154
- }
9155
  }
9156
 
9157
  /**
9158
  * {@inheritdoc}
9159
  */
9160
- public function filter(OptionsInterface $options, $value)
9161
  {
9162
- if (is_string($value)) {
9163
- $value = $this->createByDescription($options, $value);
9164
- }
9165
-
9166
- if (!$value instanceof ClusterInterface) {
9167
- throw new \InvalidArgumentException(
9168
- "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected."
9169
- );
9170
- }
9171
-
9172
- return $value;
9173
  }
9174
 
9175
  /**
9176
  * {@inheritdoc}
9177
  */
9178
- public function getDefault(OptionsInterface $options)
9179
  {
9180
- return new PredisCluster();
 
 
 
 
 
9181
  }
9182
  }
9183
 
9184
  /* --------------------------------------------------------------------------- */
9185
 
9186
- namespace Predis\Response;
9187
-
9188
- use Predis\PredisException;
9189
 
9190
- /**
9191
- * Represents a complex response object from Redis.
9192
- *
9193
- * @author Daniele Alessandri <suppakilla@gmail.com>
9194
- */
9195
- interface ResponseInterface
9196
- {
9197
- }
 
 
 
 
 
 
 
 
 
 
9198
 
9199
  /**
9200
- * Represents an error returned by Redis (responses identified by "-" in the
9201
- * Redis protocol) during the execution of an operation on the server.
9202
  *
9203
  * @author Daniele Alessandri <suppakilla@gmail.com>
9204
  */
9205
- interface ErrorInterface extends ResponseInterface
9206
  {
9207
- /**
9208
- * Returns the error message.
9209
- *
9210
- * @return string
9211
- */
9212
- public function getMessage();
9213
-
9214
- /**
9215
- * Returns the error type (e.g. ERR, ASK, MOVED).
9216
- *
9217
- * @return string
9218
- */
9219
- public function getErrorType();
9220
  }
9221
 
9222
  /**
9223
- * Represents a status response returned by Redis.
9224
  *
9225
  * @author Daniele Alessandri <suppakilla@gmail.com>
9226
  */
9227
- class Status implements ResponseInterface
9228
  {
9229
- private static $OK;
9230
- private static $QUEUED;
9231
-
9232
- private $payload;
9233
-
9234
- /**
9235
- * @param string $payload Payload of the status response as returned by Redis.
9236
- */
9237
- public function __construct($payload)
9238
- {
9239
- $this->payload = $payload;
9240
- }
9241
-
9242
  /**
9243
- * Converts the response object to its string representation.
9244
  *
9245
- * @return string
9246
  */
9247
- public function __toString()
9248
- {
9249
- return $this->payload;
9250
- }
9251
 
9252
  /**
9253
- * Returns the payload of status response.
 
9254
  *
9255
- * @return string
9256
  */
9257
- public function getPayload()
9258
- {
9259
- return $this->payload;
9260
- }
9261
 
9262
  /**
9263
- * Returns an instance of a status response object.
9264
- *
9265
- * Common status responses such as OK or QUEUED are cached in order to lower
9266
- * the global memory usage especially when using pipelines.
9267
- *
9268
- * @param string $payload Status response payload.
9269
  *
9270
- * @return string
9271
  */
9272
- public static function get($payload)
9273
- {
9274
- switch ($payload) {
9275
- case 'OK':
9276
- case 'QUEUED':
9277
- if (isset(self::$$payload)) {
9278
- return self::$$payload;
9279
- }
9280
-
9281
- return self::$$payload = new self($payload);
9282
 
9283
- default:
9284
- return new self($payload);
9285
- }
9286
- }
 
 
9287
  }
9288
 
9289
  /**
9290
- * Represents an error returned by Redis (-ERR responses) during the execution
9291
- * of a command on the server.
9292
  *
9293
  * @author Daniele Alessandri <suppakilla@gmail.com>
9294
  */
9295
- class Error implements ErrorInterface
9296
  {
9297
- private $message;
 
 
 
9298
 
9299
  /**
9300
- * @param string $message Error message returned by Redis
9301
  */
9302
- public function __construct($message)
9303
- {
9304
- $this->message = $message;
9305
- }
9306
 
9307
  /**
9308
- * {@inheritdoc}
9309
  */
9310
- public function getMessage()
9311
- {
9312
- return $this->message;
9313
- }
9314
 
9315
  /**
9316
- * {@inheritdoc}
9317
  */
9318
- public function getErrorType()
9319
- {
9320
- list($errorType) = explode(' ', $this->getMessage(), 2);
9321
 
9322
- return $errorType;
9323
- }
 
 
9324
 
9325
  /**
9326
- * Converts the object to its string representation.
9327
- *
9328
- * @return string
9329
  */
9330
- public function __toString()
9331
- {
9332
- return $this->getMessage();
9333
- }
9334
- }
9335
 
9336
- /**
9337
- * Exception class that identifies server-side Redis errors.
9338
- *
9339
- * @author Daniele Alessandri <suppakilla@gmail.com>
9340
- */
9341
- class ServerException extends PredisException implements ErrorInterface
9342
- {
9343
  /**
9344
- * Gets the type of the error returned by Redis.
9345
- *
9346
- * @return string
9347
  */
9348
- public function getErrorType()
9349
  {
9350
- list($errorType) = explode(' ', $this->getMessage(), 2);
9351
-
9352
- return $errorType;
9353
  }
9354
 
9355
  /**
9356
- * Converts the exception to an instance of Predis\Response\Error.
9357
  *
9358
- * @return Error
9359
  */
9360
- public function toErrorResponse()
9361
  {
9362
- return new Error($this->getMessage());
9363
- }
9364
- }
9365
-
9366
- /* --------------------------------------------------------------------------- */
9367
-
9368
- namespace Predis\Protocol\Text\Handler;
9369
 
9370
- use Predis\CommunicationException;
9371
- use Predis\Connection\CompositeConnectionInterface;
9372
- use Predis\Protocol\ProtocolException;
9373
- use Predis\Response\Error;
9374
- use Predis\Response\Status;
9375
- use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
9376
 
9377
- /**
9378
- * Defines a pluggable handler used to parse a particular type of response.
9379
- *
9380
- * @author Daniele Alessandri <suppakilla@gmail.com>
9381
- */
9382
- interface ResponseHandlerInterface
9383
- {
9384
  /**
9385
- * Deserializes a response returned by Redis and reads more data from the
9386
- * connection if needed.
9387
  *
9388
- * @param CompositeConnectionInterface $connection Redis connection.
9389
- * @param string $payload String payload.
9390
- *
9391
- * @return mixed
9392
  */
9393
- public function handle(CompositeConnectionInterface $connection, $payload);
9394
- }
 
 
9395
 
9396
- /**
9397
- * Handler for the status response type in the standard Redis wire protocol. It
9398
- * translates certain classes of status response to PHP objects or just returns
9399
- * the payload as a string.
9400
- *
9401
- * @link http://redis.io/topics/protocol
9402
- *
9403
- * @author Daniele Alessandri <suppakilla@gmail.com>
9404
- */
9405
- class StatusResponse implements ResponseHandlerInterface
9406
- {
9407
  /**
9408
- * {@inheritdoc}
9409
  */
9410
- public function handle(CompositeConnectionInterface $connection, $payload)
9411
  {
9412
- return Status::get($payload);
9413
  }
9414
- }
9415
 
9416
- /**
9417
- * Handler for the multibulk response type in the standard Redis wire protocol.
9418
- * It returns multibulk responses as iterators that can stream bulk elements.
9419
- *
9420
- * Streamable multibulk responses are not globally supported by the abstractions
9421
- * built-in into Predis, such as transactions or pipelines. Use them with care!
9422
- *
9423
- * @link http://redis.io/topics/protocol
9424
- *
9425
- * @author Daniele Alessandri <suppakilla@gmail.com>
9426
- */
9427
- class StreamableMultiBulkResponse implements ResponseHandlerInterface
9428
- {
9429
  /**
9430
  * {@inheritdoc}
9431
  */
9432
- public function handle(CompositeConnectionInterface $connection, $payload)
9433
  {
9434
- $length = (int) $payload;
9435
 
9436
- if ("$length" != $payload) {
9437
- CommunicationException::handle(new ProtocolException(
9438
- $connection, "Cannot parse '$payload' as a valid length for a multi-bulk response."
9439
- ));
9440
  }
9441
 
9442
- return new MultiBulkIterator($connection, $length);
9443
  }
9444
- }
9445
 
9446
- /**
9447
- * Handler for the multibulk response type in the standard Redis wire protocol.
9448
- * It returns multibulk responses as PHP arrays.
9449
- *
9450
- * @link http://redis.io/topics/protocol
9451
- *
9452
- * @author Daniele Alessandri <suppakilla@gmail.com>
9453
- */
9454
- class MultiBulkResponse implements ResponseHandlerInterface
9455
- {
9456
  /**
9457
  * {@inheritdoc}
9458
  */
9459
- public function handle(CompositeConnectionInterface $connection, $payload)
9460
  {
9461
- $length = (int) $payload;
9462
-
9463
- if ("$length" !== $payload) {
9464
- CommunicationException::handle(new ProtocolException(
9465
- $connection, "Cannot parse '$payload' as a valid length of a multi-bulk response."
9466
- ));
9467
- }
9468
-
9469
- if ($length === -1) {
9470
- return;
9471
- }
9472
-
9473
- $list = array();
9474
-
9475
- if ($length > 0) {
9476
- $handlersCache = array();
9477
- $reader = $connection->getProtocol()->getResponseReader();
9478
-
9479
- for ($i = 0; $i < $length; ++$i) {
9480
- $header = $connection->readLine();
9481
- $prefix = $header[0];
9482
 
9483
- if (isset($handlersCache[$prefix])) {
9484
- $handler = $handlersCache[$prefix];
9485
- } else {
9486
- $handler = $reader->getHandler($prefix);
9487
- $handlersCache[$prefix] = $handler;
9488
- }
9489
 
9490
- $list[$i] = $handler->handle($connection, substr($header, 1));
9491
  }
9492
  }
9493
 
9494
- return $list;
9495
  }
9496
- }
9497
 
9498
- /**
9499
- * Handler for the error response type in the standard Redis wire protocol.
9500
- * It translates the payload to a complex response object for Predis.
9501
- *
9502
- * @link http://redis.io/topics/protocol
9503
- *
9504
- * @author Daniele Alessandri <suppakilla@gmail.com>
9505
- */
9506
- class ErrorResponse implements ResponseHandlerInterface
9507
- {
9508
  /**
9509
  * {@inheritdoc}
9510
  */
9511
- public function handle(CompositeConnectionInterface $connection, $payload)
9512
  {
9513
- return new Error($payload);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9514
  }
9515
- }
9516
 
9517
- /**
9518
- * Handler for the integer response type in the standard Redis wire protocol.
9519
- * It translates the payload an integer or NULL.
9520
- *
9521
- * @link http://redis.io/topics/protocol
9522
- *
9523
- * @author Daniele Alessandri <suppakilla@gmail.com>
9524
- */
9525
- class IntegerResponse implements ResponseHandlerInterface
9526
- {
9527
  /**
9528
  * {@inheritdoc}
9529
  */
9530
- public function handle(CompositeConnectionInterface $connection, $payload)
9531
  {
9532
- if (is_numeric($payload)) {
9533
- return (int) $payload;
9534
  }
9535
 
9536
- if ($payload !== 'nil') {
9537
- CommunicationException::handle(new ProtocolException(
9538
- $connection, "Cannot parse '$payload' as a valid numeric response."
9539
- ));
9540
  }
9541
 
9542
  return;
9543
- }
9544
- }
9545
-
9546
- /**
9547
- * Handler for the bulk response type in the standard Redis wire protocol.
9548
- * It translates the payload to a string or a NULL.
9549
- *
9550
- * @link http://redis.io/topics/protocol
9551
- *
9552
- * @author Daniele Alessandri <suppakilla@gmail.com>
9553
- */
9554
- class BulkResponse implements ResponseHandlerInterface
9555
- {
9556
  /**
9557
  * {@inheritdoc}
9558
  */
9559
- public function handle(CompositeConnectionInterface $connection, $payload)
9560
  {
9561
- $length = (int) $payload;
9562
-
9563
- if ("$length" !== $payload) {
9564
- CommunicationException::handle(new ProtocolException(
9565
- $connection, "Cannot parse '$payload' as a valid length for a bulk response."
9566
- ));
9567
  }
9568
 
9569
- if ($length >= 0) {
9570
- return substr($connection->readBuffer($length + 2), 0, -2);
9571
  }
9572
 
9573
- if ($length == -1) {
9574
- return;
9575
  }
9576
 
9577
- CommunicationException::handle(new ProtocolException(
9578
- $connection, "Value '$payload' is not a valid length for a bulk response."
9579
- ));
9580
-
9581
- return;
9582
  }
9583
- }
9584
-
9585
- /* --------------------------------------------------------------------------- */
9586
-
9587
- namespace Predis\Collection\Iterator;
9588
-
9589
- use Predis\ClientInterface;
9590
- use Predis\NotSupportedException;
9591
-
9592
- /**
9593
- * Provides the base implementation for a fully-rewindable PHP iterator that can
9594
- * incrementally iterate over cursor-based collections stored on Redis using the
9595
- * commands in the `SCAN` family.
9596
- *
9597
- * Given their incremental nature with multiple fetches, these kind of iterators
9598
- * offer limited guarantees about the returned elements because the collection
9599
- * can change several times during the iteration process.
9600
- *
9601
- * @see http://redis.io/commands/scan
9602
- *
9603
- * @author Daniele Alessandri <suppakilla@gmail.com>
9604
- */
9605
- abstract class CursorBasedIterator implements \Iterator
9606
- {
9607
- protected $client;
9608
- protected $match;
9609
- protected $count;
9610
-
9611
- protected $valid;
9612
- protected $fetchmore;
9613
- protected $elements;
9614
- protected $cursor;
9615
- protected $position;
9616
- protected $current;
9617
 
9618
  /**
9619
- * @param ClientInterface $client Client connected to Redis.
9620
- * @param string $match Pattern to match during the server-side iteration.
9621
- * @param int $count Hint used by Redis to compute the number of results per iteration.
9622
  */
9623
- public function __construct(ClientInterface $client, $match = null, $count = null)
9624
  {
9625
- $this->client = $client;
9626
- $this->match = $match;
9627
- $this->count = $count;
9628
 
9629
- $this->reset();
 
 
 
 
 
 
9630
  }
9631
 
9632
  /**
9633
- * Ensures that the client supports the specified Redis command required to
9634
- * fetch elements from the server to perform the iteration.
9635
- *
9636
- * @param ClientInterface $client Client connected to Redis.
9637
- * @param string $commandID Command ID.
9638
- *
9639
- * @throws NotSupportedException
9640
  */
9641
- protected function requiredCommand(ClientInterface $client, $commandID)
9642
  {
9643
- if (!$client->getProfile()->supportsCommand($commandID)) {
9644
- throw new NotSupportedException("The current profile does not support '$commandID'.");
9645
- }
9646
  }
9647
 
9648
  /**
9649
- * Resets the inner state of the iterator.
9650
  */
9651
- protected function reset()
9652
  {
9653
- $this->valid = true;
9654
- $this->fetchmore = true;
9655
- $this->elements = array();
9656
- $this->cursor = 0;
9657
- $this->position = -1;
9658
- $this->current = null;
9659
  }
9660
 
9661
  /**
9662
- * Returns an array of options for the `SCAN` command.
9663
  *
9664
- * @return array
9665
  */
9666
- protected function getScanOptions()
9667
  {
9668
- $options = array();
9669
-
9670
- if (strlen($this->match) > 0) {
9671
- $options['MATCH'] = $this->match;
9672
  }
9673
 
9674
- if ($this->count > 0) {
9675
- $options['COUNT'] = $this->count;
9676
- }
9677
 
9678
- return $options;
 
 
 
 
 
9679
  }
9680
 
9681
  /**
9682
- * Fetches a new set of elements from the remote collection, effectively
9683
- * advancing the iteration process.
9684
  *
9685
- * @return array
9686
  */
9687
- abstract protected function executeCommand();
 
 
 
9688
 
9689
  /**
9690
- * Populates the local buffer of elements fetched from the server during
9691
- * the iteration.
 
9692
  */
9693
- protected function fetch()
9694
  {
9695
- list($cursor, $elements) = $this->executeCommand();
9696
-
9697
- if (!$cursor) {
9698
- $this->fetchmore = false;
9699
  }
9700
-
9701
- $this->cursor = $cursor;
9702
- $this->elements = $elements;
9703
  }
9704
 
9705
  /**
9706
- * Extracts next values for key() and current().
9707
  */
9708
- protected function extractNext()
9709
  {
9710
- ++$this->position;
9711
- $this->current = array_shift($this->elements);
9712
  }
9713
 
9714
  /**
9715
  * {@inheritdoc}
9716
  */
9717
- public function rewind()
9718
  {
9719
- $this->reset();
9720
- $this->next();
 
 
 
 
 
 
 
9721
  }
9722
 
9723
  /**
9724
  * {@inheritdoc}
9725
  */
9726
- public function current()
9727
  {
9728
- return $this->current;
 
 
 
 
 
 
9729
  }
9730
 
9731
  /**
9732
- * {@inheritdoc}
 
 
 
 
9733
  */
9734
- public function key()
9735
  {
9736
- return $this->position;
 
 
 
 
 
 
 
 
 
 
 
9737
  }
9738
 
9739
  /**
9740
- * {@inheritdoc}
9741
  */
9742
- public function next()
9743
  {
9744
- tryFetch: {
9745
- if (!$this->elements && $this->fetchmore) {
9746
- $this->fetch();
9747
- }
9748
 
9749
- if ($this->elements) {
9750
- $this->extractNext();
9751
- } elseif ($this->cursor) {
9752
- goto tryFetch;
9753
- } else {
9754
- $this->valid = false;
 
 
 
 
 
 
9755
  }
9756
  }
9757
  }
9758
 
9759
  /**
9760
- * {@inheritdoc}
 
 
 
9761
  */
9762
- public function valid()
9763
  {
9764
- return $this->valid;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9765
  }
9766
- }
9767
 
9768
- /**
9769
- * Abstracts the iteration of members stored in a sorted set by leveraging the
9770
- * ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9771
- *
9772
- * @author Daniele Alessandri <suppakilla@gmail.com>
9773
- *
9774
- * @link http://redis.io/commands/scan
9775
- */
9776
- class SortedSetKey extends CursorBasedIterator
9777
- {
9778
- protected $key;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9779
 
9780
  /**
9781
- * {@inheritdoc}
 
 
 
 
 
9782
  */
9783
- public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9784
  {
9785
- $this->requiredCommand($client, 'ZSCAN');
 
 
 
9786
 
9787
- parent::__construct($client, $match, $count);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9788
 
9789
- $this->key = $key;
9790
  }
9791
 
9792
  /**
9793
  * {@inheritdoc}
9794
  */
9795
- protected function executeCommand()
9796
  {
9797
- return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
9798
  }
9799
 
9800
  /**
9801
  * {@inheritdoc}
9802
  */
9803
- protected function extractNext()
9804
  {
9805
- if ($kv = each($this->elements)) {
9806
- $this->position = $kv[0];
9807
- $this->current = $kv[1];
9808
-
9809
- unset($this->elements[$this->position]);
9810
- }
9811
  }
9812
- }
9813
-
9814
- /**
9815
- * Abstracts the iteration of members stored in a set by leveraging the SSCAN
9816
- * command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9817
- *
9818
- * @author Daniele Alessandri <suppakilla@gmail.com>
9819
- *
9820
- * @link http://redis.io/commands/scan
9821
- */
9822
- class SetKey extends CursorBasedIterator
9823
- {
9824
- protected $key;
9825
 
9826
  /**
9827
  * {@inheritdoc}
9828
  */
9829
- public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9830
  {
9831
- $this->requiredCommand($client, 'SSCAN');
9832
-
9833
- parent::__construct($client, $match, $count);
9834
-
9835
- $this->key = $key;
9836
  }
9837
 
9838
  /**
9839
  * {@inheritdoc}
9840
  */
9841
- protected function executeCommand()
9842
  {
9843
- return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
9844
  }
9845
  }
9846
 
9847
  /**
9848
- * Abstracts the iteration of the keyspace on a Redis instance by leveraging the
9849
- * SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9850
  *
9851
  * @author Daniele Alessandri <suppakilla@gmail.com>
9852
  *
9853
- * @link http://redis.io/commands/scan
9854
  */
9855
- class Keyspace extends CursorBasedIterator
9856
  {
 
 
 
 
9857
  /**
9858
- * {@inheritdoc}
9859
  */
9860
- public function __construct(ClientInterface $client, $match = null, $count = null)
9861
  {
9862
- $this->requiredCommand($client, 'SCAN');
9863
-
9864
- parent::__construct($client, $match, $count);
9865
  }
9866
 
9867
  /**
9868
  * {@inheritdoc}
9869
  */
9870
- protected function executeCommand()
9871
  {
9872
- return $this->client->scan($this->cursor, $this->getScanOptions());
9873
- }
9874
- }
 
 
9875
 
9876
- /**
9877
- * Abstracts the iteration of fields and values of an hash by leveraging the
9878
- * HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9879
- *
9880
- * @author Daniele Alessandri <suppakilla@gmail.com>
9881
- *
9882
- * @link http://redis.io/commands/scan
9883
- */
9884
- class HashKey extends CursorBasedIterator
9885
- {
9886
- protected $key;
9887
 
9888
  /**
9889
  * {@inheritdoc}
9890
  */
9891
- public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9892
  {
9893
- $this->requiredCommand($client, 'HSCAN');
9894
-
9895
- parent::__construct($client, $match, $count);
9896
-
9897
- $this->key = $key;
9898
  }
9899
 
9900
  /**
9901
  * {@inheritdoc}
9902
  */
9903
- protected function executeCommand()
9904
  {
9905
- return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
 
 
9906
  }
9907
 
9908
  /**
9909
  * {@inheritdoc}
9910
  */
9911
- protected function extractNext()
9912
  {
9913
- $this->position = key($this->elements);
9914
- $this->current = array_shift($this->elements);
9915
- }
9916
- }
9917
 
9918
- /**
9919
- * Abstracts the iteration of items stored in a list by leveraging the LRANGE
9920
- * command wrapped in a fully-rewindable PHP iterator.
9921
- *
9922
- * This iterator tries to emulate the behaviour of cursor-based iterators based
9923
- * on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
9924
- * to its incremental nature with multiple fetches it can only offer limited
9925
- * guarantees on the returned elements because the collection can change several
9926
- * times (trimmed, deleted, overwritten) during the iteration process.
9927
- *
9928
- * @author Daniele Alessandri <suppakilla@gmail.com>
9929
- *
9930
- * @link http://redis.io/commands/lrange
9931
- */
9932
- class ListKey implements \Iterator
9933
- {
9934
- protected $client;
9935
- protected $count;
9936
- protected $key;
9937
 
9938
- protected $valid;
9939
- protected $fetchmore;
9940
- protected $elements;
9941
- protected $position;
9942
- protected $current;
9943
 
9944
  /**
9945
- * @param ClientInterface $client Client connected to Redis.
9946
- * @param string $key Redis list key.
9947
- * @param int $count Number of items retrieved on each fetch operation.
9948
- *
9949
- * @throws \InvalidArgumentException
9950
  */
9951
- public function __construct(ClientInterface $client, $key, $count = 10)
9952
  {
9953
- $this->requiredCommand($client, 'LRANGE');
 
 
9954
 
9955
- if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
9956
- throw new \InvalidArgumentException('The $count argument must be a positive integer.');
9957
  }
9958
 
9959
- $this->client = $client;
9960
- $this->key = $key;
9961
- $this->count = $count;
9962
-
9963
- $this->reset();
9964
  }
9965
 
9966
  /**
9967
- * Ensures that the client instance supports the specified Redis command
9968
- * required to fetch elements from the server to perform the iteration.
9969
  *
9970
- * @param ClientInterface $client Client connected to Redis.
9971
- * @param string $commandID Command ID.
9972
  *
9973
- * @throws NotSupportedException
9974
  */
9975
- protected function requiredCommand(ClientInterface $client, $commandID)
9976
  {
9977
- if (!$client->getProfile()->supportsCommand($commandID)) {
9978
- throw new NotSupportedException("The current profile does not support '$commandID'.");
9979
  }
 
 
9980
  }
9981
 
9982
  /**
9983
- * Resets the inner state of the iterator.
9984
  */
9985
- protected function reset()
9986
  {
9987
- $this->valid = true;
9988
- $this->fetchmore = true;
9989
- $this->elements = array();
9990
- $this->position = -1;
9991
- $this->current = null;
 
 
 
 
 
 
9992
  }
9993
 
9994
  /**
9995
- * Fetches a new set of elements from the remote collection, effectively
9996
- * advancing the iteration process.
9997
- *
9998
- * @return array
9999
  */
10000
- protected function executeCommand()
10001
  {
10002
- return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
10003
  }
10004
 
10005
  /**
10006
- * Populates the local buffer of elements fetched from the server during the
10007
- * iteration.
 
 
 
10008
  */
10009
- protected function fetch()
10010
  {
10011
- $elements = $this->executeCommand();
10012
-
10013
- if (count($elements) < $this->count) {
10014
- $this->fetchmore = false;
10015
- }
10016
 
10017
- $this->elements = $elements;
10018
  }
10019
 
10020
  /**
10021
- * Extracts next values for key() and current().
 
 
 
10022
  */
10023
- protected function extractNext()
10024
  {
10025
- ++$this->position;
10026
- $this->current = array_shift($this->elements);
10027
  }
10028
 
10029
  /**
10030
  * {@inheritdoc}
10031
  */
10032
- public function rewind()
10033
  {
10034
- $this->reset();
10035
- $this->next();
10036
  }
10037
 
10038
  /**
10039
  * {@inheritdoc}
10040
  */
10041
- public function current()
10042
  {
10043
- return $this->current;
10044
  }
10045
 
10046
  /**
10047
  * {@inheritdoc}
10048
  */
10049
- public function key()
10050
  {
10051
- return $this->position;
10052
  }
10053
 
10054
  /**
10055
  * {@inheritdoc}
10056
  */
10057
- public function next()
10058
- {
10059
- if (!$this->elements && $this->fetchmore) {
10060
- $this->fetch();
10061
- }
10062
-
10063
- if ($this->elements) {
10064
- $this->extractNext();
10065
- } else {
10066
- $this->valid = false;
10067
- }
10068
  }
10069
 
10070
  /**
10071
  * {@inheritdoc}
10072
  */
10073
- public function valid()
10074
  {
10075
- return $this->valid;
10076
  }
10077
- }
10078
 
10079
- /* --------------------------------------------------------------------------- */
 
 
 
 
 
 
 
 
 
10080
 
10081
- namespace Predis\Cluster;
 
 
10082
 
10083
- use Predis\Command\CommandInterface;
10084
- use Predis\Command\ScriptCommand;
10085
- use Predis\Cluster\Distributor\DistributorInterface;
10086
- use Predis\Cluster\Distributor\HashRing;
10087
- use Predis\Cluster\Hash\CRC16;
10088
- use Predis\Cluster\Hash\HashGeneratorInterface;
10089
- use Predis\NotSupportedException;
10090
 
10091
  /**
10092
- * Interface for classes defining the strategy used to calculate an hash out of
10093
- * keys extracted from supported commands.
10094
  *
10095
- * This is mostly useful to support clustering via client-side sharding.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10096
  *
10097
  * @author Daniele Alessandri <suppakilla@gmail.com>
10098
  */
10099
- interface StrategyInterface
10100
  {
 
 
 
 
 
 
 
 
10101
  /**
10102
- * Returns a slot for the given command used for clustering distribution or
10103
- * NULL when this is not possible.
10104
- *
10105
- * @param CommandInterface $command Command instance.
10106
- *
10107
- * @return int
10108
  */
10109
- public function getSlot(CommandInterface $command);
 
 
 
 
 
 
10110
 
10111
  /**
10112
- * Returns a slot for the given key used for clustering distribution or NULL
10113
- * when this is not possible.
10114
  *
10115
- * @param string $key Key string.
 
 
10116
  *
10117
- * @return int
10118
  */
10119
- public function getSlotByKey($key);
 
 
 
10120
 
10121
  /**
10122
- * Returns a distributor instance to be used by the cluster.
10123
- *
10124
- * @return DistributorInterface
10125
  */
10126
- public function getDistributor();
10127
- }
 
 
 
 
 
10128
 
10129
- /**
10130
- * Common class implementing the logic needed to support clustering strategies.
10131
- *
10132
- * @author Daniele Alessandri <suppakilla@gmail.com>
10133
- */
10134
- abstract class ClusterStrategy implements StrategyInterface
10135
- {
10136
- protected $commands;
10137
 
10138
  /**
10139
- *
10140
  */
10141
- public function __construct()
10142
  {
10143
- $this->commands = $this->getDefaultCommands();
 
 
10144
  }
10145
 
10146
  /**
10147
- * Returns the default map of supported commands with their handlers.
10148
- *
10149
- * @return array
10150
  */
10151
- protected function getDefaultCommands()
10152
  {
10153
- $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
10154
- $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
10155
-
10156
- return array(
10157
- /* commands operating on the key space */
10158
- 'EXISTS' => $getKeyFromFirstArgument,
10159
- 'DEL' => $getKeyFromAllArguments,
10160
- 'TYPE' => $getKeyFromFirstArgument,
10161
- 'EXPIRE' => $getKeyFromFirstArgument,
10162
- 'EXPIREAT' => $getKeyFromFirstArgument,
10163
- 'PERSIST' => $getKeyFromFirstArgument,
10164
- 'PEXPIRE' => $getKeyFromFirstArgument,
10165
- 'PEXPIREAT' => $getKeyFromFirstArgument,
10166
- 'TTL' => $getKeyFromFirstArgument,
10167
- 'PTTL' => $getKeyFromFirstArgument,
10168
- 'SORT' => $getKeyFromFirstArgument, // TODO
10169
- 'DUMP' => $getKeyFromFirstArgument,
10170
- 'RESTORE' => $getKeyFromFirstArgument,
10171
 
10172
- /* commands operating on string values */
10173
- 'APPEND' => $getKeyFromFirstArgument,
10174
- 'DECR' => $getKeyFromFirstArgument,
10175
- 'DECRBY' => $getKeyFromFirstArgument,
10176
- 'GET' => $getKeyFromFirstArgument,
10177
- 'GETBIT' => $getKeyFromFirstArgument,
10178
- 'MGET' => $getKeyFromAllArguments,
10179
- 'SET' => $getKeyFromFirstArgument,
10180
- 'GETRANGE' => $getKeyFromFirstArgument,
10181
- 'GETSET' => $getKeyFromFirstArgument,
10182
- 'INCR' => $getKeyFromFirstArgument,
10183
- 'INCRBY' => $getKeyFromFirstArgument,
10184
- 'INCRBYFLOAT' => $getKeyFromFirstArgument,
10185
- 'SETBIT' => $getKeyFromFirstArgument,
10186
- 'SETEX' => $getKeyFromFirstArgument,
10187
- 'MSET' => array($this, 'getKeyFromInterleavedArguments'),
10188
- 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
10189
- 'SETNX' => $getKeyFromFirstArgument,
10190
- 'SETRANGE' => $getKeyFromFirstArgument,
10191
- 'STRLEN' => $getKeyFromFirstArgument,
10192
- 'SUBSTR' => $getKeyFromFirstArgument,
10193
- 'BITOP' => array($this, 'getKeyFromBitOp'),
10194
- 'BITCOUNT' => $getKeyFromFirstArgument,
10195
 
10196
- /* commands operating on lists */
10197
- 'LINSERT' => $getKeyFromFirstArgument,
10198
- 'LINDEX' => $getKeyFromFirstArgument,
10199
- 'LLEN' => $getKeyFromFirstArgument,
10200
- 'LPOP' => $getKeyFromFirstArgument,
10201
- 'RPOP' => $getKeyFromFirstArgument,
10202
- 'RPOPLPUSH' => $getKeyFromAllArguments,
10203
- 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
10204
- 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
10205
- 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
10206
- 'LPUSH' => $getKeyFromFirstArgument,
10207
- 'LPUSHX' => $getKeyFromFirstArgument,
10208
- 'RPUSH' => $getKeyFromFirstArgument,
10209
- 'RPUSHX' => $getKeyFromFirstArgument,
10210
- 'LRANGE' => $getKeyFromFirstArgument,
10211
- 'LREM' => $getKeyFromFirstArgument,
10212
- 'LSET' => $getKeyFromFirstArgument,
10213
- 'LTRIM' => $getKeyFromFirstArgument,
10214
 
10215
- /* commands operating on sets */
10216
- 'SADD' => $getKeyFromFirstArgument,
10217
- 'SCARD' => $getKeyFromFirstArgument,
10218
- 'SDIFF' => $getKeyFromAllArguments,
10219
- 'SDIFFSTORE' => $getKeyFromAllArguments,
10220
- 'SINTER' => $getKeyFromAllArguments,
10221
- 'SINTERSTORE' => $getKeyFromAllArguments,
10222
- 'SUNION' => $getKeyFromAllArguments,
10223
- 'SUNIONSTORE' => $getKeyFromAllArguments,
10224
- 'SISMEMBER' => $getKeyFromFirstArgument,
10225
- 'SMEMBERS' => $getKeyFromFirstArgument,
10226
- 'SSCAN' => $getKeyFromFirstArgument,
10227
- 'SPOP' => $getKeyFromFirstArgument,
10228
- 'SRANDMEMBER' => $getKeyFromFirstArgument,
10229
- 'SREM' => $getKeyFromFirstArgument,
10230
 
10231
- /* commands operating on sorted sets */
10232
- 'ZADD' => $getKeyFromFirstArgument,
10233
- 'ZCARD' => $getKeyFromFirstArgument,
10234
- 'ZCOUNT' => $getKeyFromFirstArgument,
10235
- 'ZINCRBY' => $getKeyFromFirstArgument,
10236
- 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
10237
- 'ZRANGE' => $getKeyFromFirstArgument,
10238
- 'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
10239
- 'ZRANK' => $getKeyFromFirstArgument,
10240
- 'ZREM' => $getKeyFromFirstArgument,
10241
- 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
10242
- 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
10243
- 'ZREVRANGE' => $getKeyFromFirstArgument,
10244
- 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
10245
- 'ZREVRANK' => $getKeyFromFirstArgument,
10246
- 'ZSCORE' => $getKeyFromFirstArgument,
10247
- 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
10248
- 'ZSCAN' => $getKeyFromFirstArgument,
10249
- 'ZLEXCOUNT' => $getKeyFromFirstArgument,
10250
- 'ZRANGEBYLEX' => $getKeyFromFirstArgument,
10251
- 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
10252
- 'ZREVRANGEBYLEX' => $getKeyFromFirstArgument,
10253
 
10254
- /* commands operating on hashes */
10255
- 'HDEL' => $getKeyFromFirstArgument,
10256
- 'HEXISTS' => $getKeyFromFirstArgument,
10257
- 'HGET' => $getKeyFromFirstArgument,
10258
- 'HGETALL' => $getKeyFromFirstArgument,
10259
- 'HMGET' => $getKeyFromFirstArgument,
10260
- 'HMSET' => $getKeyFromFirstArgument,
10261
- 'HINCRBY' => $getKeyFromFirstArgument,
10262
- 'HINCRBYFLOAT' => $getKeyFromFirstArgument,
10263
- 'HKEYS' => $getKeyFromFirstArgument,
10264
- 'HLEN' => $getKeyFromFirstArgument,
10265
- 'HSET' => $getKeyFromFirstArgument,
10266
- 'HSETNX' => $getKeyFromFirstArgument,
10267
- 'HVALS' => $getKeyFromFirstArgument,
10268
- 'HSCAN' => $getKeyFromFirstArgument,
10269
- 'HSTRLEN' => $getKeyFromFirstArgument,
10270
 
10271
- /* commands operating on HyperLogLog */
10272
- 'PFADD' => $getKeyFromFirstArgument,
10273
- 'PFCOUNT' => $getKeyFromAllArguments,
10274
- 'PFMERGE' => $getKeyFromAllArguments,
 
 
 
 
 
 
 
 
 
 
10275
 
10276
- /* scripting */
10277
- 'EVAL' => array($this, 'getKeyFromScriptingCommands'),
10278
- 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
10279
- );
10280
  }
10281
 
10282
  /**
10283
- * Returns the list of IDs for the supported commands.
 
 
 
 
 
 
10284
  *
10285
  * @return array
10286
  */
10287
- public function getSupportedCommands()
10288
  {
10289
- return array_keys($this->commands);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10290
  }
10291
 
10292
  /**
10293
- * Sets an handler for the specified command ID.
10294
  *
10295
- * The signature of the callback must have a single parameter of type
10296
- * Predis\Command\CommandInterface.
 
10297
  *
10298
- * When the callback argument is omitted or NULL, the previously associated
10299
- * handler for the specified command ID is removed.
10300
  *
10301
- * @param string $commandID Command ID.
10302
- * @param mixed $callback A valid callable object, or NULL to unset the handler.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10303
  *
10304
- * @throws \InvalidArgumentException
 
 
10305
  */
10306
- public function setCommandHandler($commandID, $callback = null)
10307
  {
10308
- $commandID = strtoupper($commandID);
 
 
10309
 
10310
- if (!isset($callback)) {
10311
- unset($this->commands[$commandID]);
10312
 
10313
- return;
 
 
 
 
 
 
 
 
 
 
 
10314
  }
10315
 
10316
- if (!is_callable($callback)) {
10317
- throw new \InvalidArgumentException(
10318
- 'The argument must be a callable object or NULL.'
10319
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10320
  }
10321
 
10322
- $this->commands[$commandID] = $callback;
10323
  }
10324
 
10325
  /**
10326
- * Extracts the key from the first argument of a command instance.
10327
  *
10328
- * @param CommandInterface $command Command instance.
 
 
10329
  *
10330
- * @return string
10331
  */
10332
- protected function getKeyFromFirstArgument(CommandInterface $command)
10333
  {
10334
- return $command->getArgument(0);
 
 
 
 
 
 
 
 
 
 
10335
  }
10336
 
10337
  /**
10338
- * Extracts the key from a command with multiple keys only when all keys in
10339
- * the arguments array produce the same hash.
 
10340
  *
10341
- * @param CommandInterface $command Command instance.
10342
  *
10343
- * @return string|null
10344
  */
10345
- protected function getKeyFromAllArguments(CommandInterface $command)
10346
  {
10347
- $arguments = $command->getArguments();
 
 
10348
 
10349
- if ($this->checkSameSlotForKeys($arguments)) {
10350
- return $arguments[0];
 
 
 
 
10351
  }
 
 
 
 
 
 
10352
  }
10353
 
10354
  /**
10355
- * Extracts the key from a command with multiple keys only when all keys in
10356
- * the arguments array produce the same hash.
10357
  *
10358
- * @param CommandInterface $command Command instance.
10359
  *
10360
- * @return string|null
10361
  */
10362
- protected function getKeyFromInterleavedArguments(CommandInterface $command)
10363
  {
10364
- $arguments = $command->getArguments();
10365
- $keys = array();
10366
 
10367
- for ($i = 0; $i < count($arguments); $i += 2) {
10368
- $keys[] = $arguments[$i];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10369
  }
10370
 
10371
- if ($this->checkSameSlotForKeys($keys)) {
10372
- return $arguments[0];
 
 
10373
  }
10374
  }
10375
 
10376
  /**
10377
- * Extracts the key from BLPOP and BRPOP commands.
10378
  *
10379
- * @param CommandInterface $command Command instance.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10380
  *
10381
- * @return string|null
10382
  */
10383
- protected function getKeyFromBlockingListCommands(CommandInterface $command)
10384
  {
10385
- $arguments = $command->getArguments();
10386
-
10387
- if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
10388
- return $arguments[0];
10389
  }
10390
  }
10391
 
10392
  /**
10393
- * Extracts the key from BITOP command.
10394
- *
10395
- * @param CommandInterface $command Command instance.
10396
  *
10397
- * @return string|null
 
10398
  */
10399
- protected function getKeyFromBitOp(CommandInterface $command)
10400
  {
10401
- $arguments = $command->getArguments();
10402
-
10403
- if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) {
10404
- return $arguments[1];
10405
- }
10406
  }
10407
 
10408
  /**
10409
- * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
10410
  *
10411
- * @param CommandInterface $command Command instance.
 
10412
  *
10413
- * @return string|null
10414
  */
10415
- protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
10416
  {
10417
- $arguments = $command->getArguments();
10418
- $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
10419
 
10420
- if ($this->checkSameSlotForKeys($keys)) {
10421
- return $arguments[0];
 
 
 
 
 
 
 
10422
  }
10423
  }
10424
 
10425
  /**
10426
- * Extracts the key from EVAL and EVALSHA commands.
 
10427
  *
10428
- * @param CommandInterface $command Command instance.
 
10429
  *
10430
- * @return string|null
10431
  */
10432
- protected function getKeyFromScriptingCommands(CommandInterface $command)
10433
  {
10434
- if ($command instanceof ScriptCommand) {
10435
- $keys = $command->getKeys();
10436
- } else {
10437
- $keys = array_slice($args = $command->getArguments(), 2, $args[1]);
10438
  }
10439
 
10440
- if ($keys && $this->checkSameSlotForKeys($keys)) {
10441
- return $keys[0];
10442
  }
 
 
 
 
 
10443
  }
10444
 
10445
  /**
10446
- * {@inheritdoc}
 
 
 
 
 
 
10447
  */
10448
- public function getSlot(CommandInterface $command)
10449
  {
10450
- $slot = $command->getSlot();
10451
-
10452
- if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) {
10453
- $key = call_user_func($this->commands[$cmdID], $command);
10454
 
10455
- if (isset($key)) {
10456
- $slot = $this->getSlotByKey($key);
10457
- $command->setSlot($slot);
10458
- }
10459
  }
10460
 
10461
- return $slot;
 
 
 
10462
  }
10463
 
10464
  /**
10465
- * Checks if the specified array of keys will generate the same hash.
10466
  *
10467
- * @param array $keys Array of keys.
 
 
 
 
10468
  *
10469
- * @return bool
 
 
 
10470
  */
10471
- protected function checkSameSlotForKeys(array $keys)
10472
  {
10473
- if (!$count = count($keys)) {
10474
- return false;
10475
- }
10476
 
10477
- $currentSlot = $this->getSlotByKey($keys[0]);
 
 
 
 
 
10478
 
10479
- for ($i = 1; $i < $count; ++$i) {
10480
- $nextSlot = $this->getSlotByKey($keys[$i]);
10481
 
10482
- if ($currentSlot !== $nextSlot) {
10483
- return false;
10484
- }
 
 
10485
 
10486
- $currentSlot = $nextSlot;
 
 
 
10487
  }
10488
 
10489
- return true;
10490
  }
10491
 
10492
  /**
10493
- * Returns only the hashable part of a key (delimited by "{...}"), or the
10494
- * whole key if a key tag is not found in the string.
10495
- *
10496
- * @param string $key A key.
10497
- *
10498
- * @return string
10499
  */
10500
- protected function extractKeyTag($key)
10501
  {
10502
- if (false !== $start = strpos($key, '{')) {
10503
- if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
10504
- $key = substr($key, $start, $end - $start);
10505
- }
10506
- }
10507
-
10508
- return $key;
10509
  }
10510
- }
10511
 
10512
- /**
10513
- * Default cluster strategy used by Predis to handle client-side sharding.
10514
- *
10515
- * @author Daniele Alessandri <suppakilla@gmail.com>
10516
- */
10517
- class PredisStrategy extends ClusterStrategy
10518
- {
10519
- protected $distributor;
10520
 
10521
  /**
10522
- * @param DistributorInterface $distributor Optional distributor instance.
10523
  */
10524
- public function __construct(DistributorInterface $distributor = null)
10525
  {
10526
- parent::__construct();
10527
 
10528
- $this->distributor = $distributor ?: new HashRing();
 
 
 
 
10529
  }
10530
 
10531
  /**
10532
  * {@inheritdoc}
10533
  */
10534
- public function getSlotByKey($key)
10535
  {
10536
- $key = $this->extractKeyTag($key);
10537
- $hash = $this->distributor->hash($key);
10538
- $slot = $this->distributor->getSlot($hash);
10539
-
10540
- return $slot;
10541
  }
10542
 
10543
  /**
10544
  * {@inheritdoc}
10545
  */
10546
- protected function checkSameSlotForKeys(array $keys)
10547
  {
10548
- if (!$count = count($keys)) {
10549
- return false;
 
 
10550
  }
10551
 
10552
- $currentKey = $this->extractKeyTag($keys[0]);
10553
-
10554
- for ($i = 1; $i < $count; ++$i) {
10555
- $nextKey = $this->extractKeyTag($keys[$i]);
10556
 
10557
- if ($currentKey !== $nextKey) {
10558
- return false;
 
10559
  }
10560
 
10561
- $currentKey = $nextKey;
10562
  }
10563
 
10564
- return true;
10565
  }
10566
 
10567
  /**
10568
- * {@inheritdoc}
 
 
 
10569
  */
10570
- public function getDistributor()
10571
  {
10572
- return $this->distributor;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10573
  }
10574
  }
10575
 
10576
  /**
10577
- * Default class used by Predis to calculate hashes out of keys of
10578
- * commands supported by redis-cluster.
10579
- *
10580
  * @author Daniele Alessandri <suppakilla@gmail.com>
 
10581
  */
10582
- class RedisStrategy extends ClusterStrategy
10583
  {
10584
- protected $hashGenerator;
10585
-
10586
  /**
10587
- * @param HashGeneratorInterface $hashGenerator Hash generator instance.
10588
  */
10589
- public function __construct(HashGeneratorInterface $hashGenerator = null)
10590
- {
10591
- parent::__construct();
10592
 
10593
- $this->hashGenerator = $hashGenerator ?: new CRC16();
10594
- }
 
 
10595
 
10596
  /**
10597
- * {@inheritdoc}
10598
  */
10599
- public function getSlotByKey($key)
10600
- {
10601
- $key = $this->extractKeyTag($key);
10602
- $slot = $this->hashGenerator->hash($key) & 0x3FFF;
10603
 
10604
- return $slot;
10605
- }
 
 
10606
 
10607
  /**
10608
- * {@inheritdoc}
10609
  */
10610
- public function getDistributor()
10611
- {
10612
- throw new NotSupportedException(
10613
- 'This cluster strategy does not provide an external distributor'
10614
- );
10615
- }
10616
- }
10617
 
10618
- /* --------------------------------------------------------------------------- */
 
 
 
10619
 
10620
- namespace Predis\Protocol;
 
 
 
10621
 
10622
- use Predis\CommunicationException;
10623
- use Predis\Command\CommandInterface;
10624
- use Predis\Connection\CompositeConnectionInterface;
 
10625
 
10626
- /**
10627
- * Defines a pluggable protocol processor capable of serializing commands and
10628
- * deserializing responses into PHP objects directly from a connection.
10629
- *
10630
- * @author Daniele Alessandri <suppakilla@gmail.com>
10631
- */
10632
- interface ProtocolProcessorInterface
10633
- {
10634
  /**
10635
- * Writes a request over a connection to Redis.
10636
- *
10637
- * @param CompositeConnectionInterface $connection Redis connection.
10638
- * @param CommandInterface $command Command instance.
10639
  */
10640
- public function write(CompositeConnectionInterface $connection, CommandInterface $command);
10641
 
10642
  /**
10643
- * Reads a response from a connection to Redis.
10644
  *
10645
- * @param CompositeConnectionInterface $connection Redis connection.
 
 
10646
  *
10647
- * @return mixed
10648
  */
10649
- public function read(CompositeConnectionInterface $connection);
10650
- }
10651
 
10652
- /**
10653
- * Defines a pluggable reader capable of parsing responses returned by Redis and
10654
- * deserializing them to PHP objects.
10655
- *
10656
- * @author Daniele Alessandri <suppakilla@gmail.com>
10657
- */
10658
- interface ResponseReaderInterface
10659
- {
10660
  /**
10661
- * Reads a response from a connection to Redis.
10662
- *
10663
- * @param CompositeConnectionInterface $connection Redis connection.
10664
  *
10665
- * @return mixed
10666
  */
10667
- public function read(CompositeConnectionInterface $connection);
10668
- }
10669
 
10670
- /**
10671
- * Defines a pluggable serializer for Redis commands.
10672
- *
10673
- * @author Daniele Alessandri <suppakilla@gmail.com>
10674
- */
10675
- interface RequestSerializerInterface
10676
- {
10677
  /**
10678
- * Serializes a Redis command.
10679
  *
10680
- * @param CommandInterface $command Redis command.
10681
- *
10682
- * @return string
10683
  */
10684
- public function serialize(CommandInterface $command);
10685
- }
10686
-
10687
- /**
10688
- * Exception used to indentify errors encountered while parsing the Redis wire
10689
- * protocol.
10690
- *
10691
- * @author Daniele Alessandri <suppakilla@gmail.com>
10692
- */
10693
- class ProtocolException extends CommunicationException
10694
- {
10695
- }
10696
-
10697
- /* --------------------------------------------------------------------------- */
10698
-
10699
- namespace Predis\Connection\Aggregate;
10700
-
10701
- use Predis\Connection\AggregateConnectionInterface;
10702
- use Predis\Command\CommandInterface;
10703
- use Predis\Connection\NodeConnectionInterface;
10704
- use Predis\Replication\ReplicationStrategy;
10705
- use Predis\Cluster\PredisStrategy;
10706
- use Predis\Cluster\StrategyInterface;
10707
- use Predis\NotSupportedException;
10708
- use Predis\Cluster\RedisStrategy as RedisClusterStrategy;
10709
- use Predis\Command\RawCommand;
10710
- use Predis\Connection\FactoryInterface;
10711
- use Predis\Response\ErrorInterface as ErrorResponseInterface;
10712
 
10713
- /**
10714
- * Defines a cluster of Redis servers formed by aggregating multiple connection
10715
- * instances to single Redis nodes.
10716
- *
10717
- * @author Daniele Alessandri <suppakilla@gmail.com>
10718
- */
10719
- interface ClusterInterface extends AggregateConnectionInterface
10720
- {
10721
- }
 
 
 
 
 
 
 
 
10722
 
10723
- /**
10724
- * Defines a group of Redis nodes in a master / slave replication setup.
10725
- *
10726
- * @author Daniele Alessandri <suppakilla@gmail.com>
10727
- */
10728
- interface ReplicationInterface extends AggregateConnectionInterface
10729
- {
10730
  /**
10731
- * Switches the internal connection instance in use.
10732
  *
10733
- * @param string $connection Alias of a connection
 
 
 
10734
  */
10735
- public function switchTo($connection);
 
 
 
10736
 
10737
  /**
10738
- * Returns the connection instance currently in use by the aggregate
10739
- * connection.
10740
  *
10741
- * @return NodeConnectionInterface
 
 
 
 
10742
  */
10743
- public function getCurrent();
 
 
 
10744
 
10745
  /**
10746
- * Returns the connection instance for the master Redis node.
 
10747
  *
10748
- * @return NodeConnectionInterface
10749
  */
10750
- public function getMaster();
 
 
 
10751
 
10752
  /**
10753
- * Returns a list of connection instances to slave nodes.
10754
  *
10755
- * @return NodeConnectionInterface
10756
  */
10757
- public function getSlaves();
10758
- }
 
 
10759
 
10760
- /**
10761
- * Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
10762
- *
10763
- * This connection backend offers smart support for redis-cluster by handling
10764
- * automatic slots map (re)generation upon -MOVED or -ASK responses returned by
10765
- * Redis when redirecting a client to a different node.
10766
- *
10767
- * The cluster can be pre-initialized using only a subset of the actual nodes in
10768
- * the cluster, Predis will do the rest by adjusting the slots map and creating
10769
- * the missing underlying connection instances on the fly.
10770
- *
10771
- * It is possible to pre-associate connections to a slots range with the "slots"
10772
- * parameter in the form "$first-$last". This can greatly reduce runtime node
10773
- * guessing and redirections.
10774
- *
10775
- * It is also possible to ask for the full and updated slots map directly to one
10776
- * of the nodes and optionally enable such a behaviour upon -MOVED redirections.
10777
- * Asking for the cluster configuration to Redis is actually done by issuing a
10778
- * CLUSTER SLOTS command to a random node in the pool.
10779
- *
10780
- * @author Daniele Alessandri <suppakilla@gmail.com>
10781
- */
10782
- class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
10783
- {
10784
- private $useClusterSlots = true;
10785
- private $defaultParameters = array();
10786
- private $pool = array();
10787
- private $slots = array();
10788
- private $slotsMap;
10789
- private $strategy;
10790
- private $connections;
10791
 
10792
  /**
10793
- * @param FactoryInterface $connections Optional connection factory.
10794
- * @param StrategyInterface $strategy Optional cluster strategy.
10795
  */
10796
- public function __construct(
10797
- FactoryInterface $connections,
10798
- StrategyInterface $strategy = null
10799
- ) {
10800
- $this->connections = $connections;
10801
- $this->strategy = $strategy ?: new RedisClusterStrategy();
10802
  }
10803
 
10804
  /**
10805
  * {@inheritdoc}
10806
  */
10807
- public function isConnected()
10808
  {
10809
- foreach ($this->pool as $connection) {
10810
- if ($connection->isConnected()) {
10811
- return true;
10812
- }
 
 
10813
  }
10814
 
10815
- return false;
10816
  }
10817
 
10818
  /**
10819
  * {@inheritdoc}
10820
  */
10821
- public function connect()
10822
  {
10823
- if ($connection = $this->getRandomConnection()) {
10824
- $connection->connect();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10825
  }
 
 
10826
  }
10827
 
10828
- /**
10829
- * {@inheritdoc}
10830
- */
10831
- public function disconnect()
10832
- {
10833
- foreach ($this->pool as $connection) {
10834
- $connection->disconnect();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10835
  }
10836
  }
10837
 
10838
  /**
10839
- * {@inheritdoc}
10840
  */
10841
- public function add(NodeConnectionInterface $connection)
10842
  {
10843
- $this->pool[(string) $connection] = $connection;
10844
- unset($this->slotsMap);
 
 
 
10845
  }
10846
 
10847
  /**
10848
- * {@inheritdoc}
 
 
 
10849
  */
10850
- public function remove(NodeConnectionInterface $connection)
10851
  {
10852
- if (false !== $id = array_search($connection, $this->pool, true)) {
10853
- unset(
10854
- $this->pool[$id],
10855
- $this->slotsMap
10856
- );
10857
-
10858
- return true;
10859
  }
10860
-
10861
- return false;
10862
  }
10863
 
10864
  /**
10865
- * Removes a connection instance by using its identifier.
10866
  *
10867
- * @param string $connectionID Connection identifier.
 
10868
  *
10869
- * @return bool True if the connection was in the pool.
10870
  */
10871
- public function removeById($connectionID)
10872
  {
10873
- if (isset($this->pool[$connectionID])) {
10874
- unset(
10875
- $this->pool[$connectionID],
10876
- $this->slotsMap
10877
- );
10878
 
10879
- return true;
 
10880
  }
10881
 
10882
- return false;
 
 
 
 
 
 
 
 
10883
  }
10884
 
10885
  /**
10886
- * Generates the current slots map by guessing the cluster configuration out
10887
- * of the connection parameters of the connections in the pool.
10888
  *
10889
- * Generation is based on the same algorithm used by Redis to generate the
10890
- * cluster, so it is most effective when all of the connections supplied on
10891
- * initialization have the "slots" parameter properly set accordingly to the
10892
- * current cluster configuration.
10893
  */
10894
- public function buildSlotsMap()
10895
  {
10896
- $this->slotsMap = array();
10897
 
10898
- foreach ($this->pool as $connectionID => $connection) {
10899
- $parameters = $connection->getParameters();
 
10900
 
10901
- if (!isset($parameters->slots)) {
 
 
 
 
 
 
 
10902
  continue;
10903
  }
10904
 
10905
- $slots = explode('-', $parameters->slots, 2);
10906
- $this->setSlots($slots[0], $slots[1], $connectionID);
 
 
 
10907
  }
 
 
10908
  }
10909
 
10910
  /**
10911
- * Generates an updated slots map fetching the cluster configuration using
10912
- * the CLUSTER SLOTS command against the specified node or a random one from
10913
- * the pool.
10914
- *
10915
- * @param NodeConnectionInterface $connection Optional connection instance.
10916
- *
10917
- * @return array
10918
  */
10919
- public function askSlotsMap(NodeConnectionInterface $connection = null)
10920
  {
10921
- if (!$connection && !$connection = $this->getRandomConnection()) {
10922
- return array();
 
 
 
 
 
 
 
 
10923
  }
10924
 
10925
- $command = RawCommand::create('CLUSTER', 'SLOTS');
10926
- $response = $connection->executeCommand($command);
 
10927
 
10928
- foreach ($response as $slots) {
10929
- // We only support master servers for now, so we ignore subsequent
10930
- // elements in the $slots array identifying slaves.
10931
- list($start, $end, $master) = $slots;
10932
 
10933
- if ($master[0] === '') {
10934
- $this->setSlots($start, $end, (string) $connection);
10935
- } else {
10936
- $this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
 
 
 
 
 
10937
  }
10938
  }
10939
 
10940
- return $this->slotsMap;
10941
  }
10942
 
10943
  /**
10944
- * Returns the current slots map for the cluster.
10945
- *
10946
- * @return array
10947
  */
10948
- public function getSlotsMap()
10949
  {
10950
- if (!isset($this->slotsMap)) {
10951
- $this->slotsMap = array();
10952
  }
10953
 
10954
- return $this->slotsMap;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10955
  }
10956
 
10957
  /**
10958
- * Pre-associates a connection to a slots range to avoid runtime guessing.
10959
- *
10960
- * @param int $first Initial slot of the range.
10961
- * @param int $last Last slot of the range.
10962
- * @param NodeConnectionInterface|string $connection ID or connection instance.
10963
  *
10964
- * @throws \OutOfBoundsException
10965
  */
10966
- public function setSlots($first, $last, $connection)
10967
  {
10968
- if ($first < 0x0000 || $first > 0x3FFF ||
10969
- $last < 0x0000 || $last > 0x3FFF ||
10970
- $last < $first
10971
- ) {
10972
- throw new \OutOfBoundsException(
10973
- "Invalid slot range for $connection: [$first-$last]."
10974
- );
10975
  }
10976
-
10977
- $slots = array_fill($first, $last - $first + 1, (string) $connection);
10978
- $this->slotsMap = $this->getSlotsMap() + $slots;
10979
  }
10980
 
10981
  /**
10982
- * Guesses the correct node associated to a given slot using a precalculated
10983
- * slots map, falling back to the same logic used by Redis to initialize a
10984
- * cluster (best-effort).
10985
  *
10986
- * @param int $slot Slot index.
10987
  *
10988
- * @return string Connection ID.
10989
  */
10990
- protected function guessNode($slot)
10991
  {
10992
- if (!isset($this->slotsMap)) {
10993
- $this->buildSlotsMap();
 
 
 
 
 
 
10994
  }
10995
 
10996
- if (isset($this->slotsMap[$slot])) {
10997
- return $this->slotsMap[$slot];
10998
  }
10999
 
11000
- $count = count($this->pool);
11001
- $index = min((int) ($slot / (int) (16384 / $count)), $count - 1);
11002
- $nodes = array_keys($this->pool);
11003
 
11004
- return $nodes[$index];
11005
  }
11006
 
11007
  /**
11008
- * Creates a new connection instance from the given connection ID.
11009
- *
11010
- * @param string $connectionID Identifier for the connection.
11011
  *
11012
- * @return NodeConnectionInterface
 
11013
  */
11014
- protected function createConnection($connectionID)
11015
  {
11016
- $separator = strrpos($connectionID, ':');
 
11017
 
11018
- $parameters = array_merge($this->defaultParameters, array(
11019
- 'host' => substr($connectionID, 0, $separator),
11020
- 'port' => substr($connectionID, $separator + 1),
11021
- ));
11022
 
11023
- $connection = $this->connections->create($parameters);
 
 
 
 
 
 
 
 
 
 
 
 
11024
 
11025
  return $connection;
11026
  }
@@ -11028,157 +12622,128 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
11028
  /**
11029
  * {@inheritdoc}
11030
  */
11031
- public function getConnection(CommandInterface $command)
11032
  {
11033
- $slot = $this->strategy->getSlot($command);
11034
-
11035
- if (!isset($slot)) {
11036
- throw new NotSupportedException(
11037
- "Cannot use '{$command->getId()}' with redis-cluster."
11038
- );
11039
  }
11040
 
11041
- if (isset($this->slots[$slot])) {
11042
- return $this->slots[$slot];
11043
- } else {
11044
- return $this->getConnectionBySlot($slot);
11045
  }
11046
  }
11047
 
11048
  /**
11049
- * Returns the connection currently associated to a given slot.
11050
- *
11051
- * @param int $slot Slot index.
11052
- *
11053
- * @throws \OutOfBoundsException
11054
- *
11055
- * @return NodeConnectionInterface
11056
  */
11057
- public function getConnectionBySlot($slot)
11058
  {
11059
- if ($slot < 0x0000 || $slot > 0x3FFF) {
11060
- throw new \OutOfBoundsException("Invalid slot [$slot].");
11061
  }
11062
 
11063
- if (isset($this->slots[$slot])) {
11064
- return $this->slots[$slot];
11065
  }
11066
 
11067
- $connectionID = $this->guessNode($slot);
 
 
11068
 
11069
- if (!$connection = $this->getConnectionById($connectionID)) {
11070
- $connection = $this->createConnection($connectionID);
11071
- $this->pool[$connectionID] = $connection;
 
11072
  }
11073
 
11074
- return $this->slots[$slot] = $connection;
11075
  }
11076
 
11077
  /**
11078
- * {@inheritdoc}
11079
  */
11080
- public function getConnectionById($connectionID)
11081
  {
11082
- if (isset($this->pool[$connectionID])) {
11083
- return $this->pool[$connectionID];
11084
- }
11085
  }
11086
 
11087
  /**
11088
- * Returns a random connection from the pool.
11089
- *
11090
- * @return NodeConnectionInterface|null
11091
  */
11092
- protected function getRandomConnection()
11093
  {
11094
- if ($this->pool) {
11095
- return $this->pool[array_rand($this->pool)];
11096
- }
11097
  }
11098
 
11099
  /**
11100
- * Permanently associates the connection instance to a new slot.
11101
- * The connection is added to the connections pool if not yet included.
11102
- *
11103
- * @param NodeConnectionInterface $connection Connection instance.
11104
- * @param int $slot Target slot index.
11105
  */
11106
- protected function move(NodeConnectionInterface $connection, $slot)
11107
  {
11108
- $this->pool[(string) $connection] = $connection;
11109
- $this->slots[(int) $slot] = $connection;
11110
  }
11111
 
11112
  /**
11113
- * Handles -ERR responses returned by Redis.
11114
- *
11115
- * @param CommandInterface $command Command that generated the -ERR response.
11116
- * @param ErrorResponseInterface $error Redis error response object.
11117
- *
11118
- * @return mixed
11119
  */
11120
- protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error)
11121
  {
11122
- $details = explode(' ', $error->getMessage(), 2);
11123
-
11124
- switch ($details[0]) {
11125
- case 'MOVED':
11126
- return $this->onMovedResponse($command, $details[1]);
11127
-
11128
- case 'ASK':
11129
- return $this->onAskResponse($command, $details[1]);
11130
-
11131
- default:
11132
- return $error;
11133
  }
 
 
11134
  }
11135
 
11136
  /**
11137
- * Handles -MOVED responses by executing again the command against the node
11138
- * indicated by the Redis response.
11139
- *
11140
- * @param CommandInterface $command Command that generated the -MOVED response.
11141
- * @param string $details Parameters of the -MOVED response.
11142
- *
11143
- * @return mixed
11144
  */
11145
- protected function onMovedResponse(CommandInterface $command, $details)
11146
  {
11147
- list($slot, $connectionID) = explode(' ', $details, 2);
11148
-
11149
- if (!$connection = $this->getConnectionById($connectionID)) {
11150
- $connection = $this->createConnection($connectionID);
11151
  }
11152
 
11153
- if ($this->useClusterSlots) {
11154
- $this->askSlotsMap($connection);
11155
  }
11156
-
11157
- $this->move($connection, $slot);
11158
- $response = $this->executeCommand($command);
11159
-
11160
- return $response;
11161
  }
11162
 
11163
  /**
11164
- * Handles -ASK responses by executing again the command against the node
11165
- * indicated by the Redis response.
11166
  *
11167
- * @param CommandInterface $command Command that generated the -ASK response.
11168
- * @param string $details Parameters of the -ASK response.
11169
  *
11170
  * @return mixed
11171
  */
11172
- protected function onAskResponse(CommandInterface $command, $details)
11173
  {
11174
- list($slot, $connectionID) = explode(' ', $details, 2);
11175
 
11176
- if (!$connection = $this->getConnectionById($connectionID)) {
11177
- $connection = $this->createConnection($connectionID);
11178
- }
 
 
 
11179
 
11180
- $connection->executeCommand(RawCommand::create('ASKING'));
11181
- $response = $connection->executeCommand($command);
 
 
 
 
 
 
 
 
11182
 
11183
  return $response;
11184
  }
@@ -11188,7 +12753,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
11188
  */
11189
  public function writeRequest(CommandInterface $command)
11190
  {
11191
- $this->getConnection($command)->writeRequest($command);
11192
  }
11193
 
11194
  /**
@@ -11196,7 +12761,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
11196
  */
11197
  public function readResponse(CommandInterface $command)
11198
  {
11199
- return $this->getConnection($command)->readResponse($command);
11200
  }
11201
 
11202
  /**
@@ -11204,554 +12769,717 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
11204
  */
11205
  public function executeCommand(CommandInterface $command)
11206
  {
11207
- $connection = $this->getConnection($command);
11208
- $response = $connection->executeCommand($command);
11209
-
11210
- if ($response instanceof ErrorResponseInterface) {
11211
- return $this->onErrorResponse($command, $response);
11212
- }
11213
-
11214
- return $response;
11215
  }
11216
 
11217
  /**
11218
- * {@inheritdoc}
 
 
11219
  */
11220
- public function count()
11221
  {
11222
- return count($this->pool);
11223
  }
11224
 
11225
  /**
11226
  * {@inheritdoc}
11227
  */
11228
- public function getIterator()
11229
  {
11230
- return new \ArrayIterator(array_values($this->pool));
 
 
11231
  }
 
11232
 
11233
- /**
11234
- * Returns the underlying command hash strategy used to hash commands by
11235
- * using keys found in their arguments.
11236
- *
11237
- * @return StrategyInterface
11238
- */
11239
- public function getClusterStrategy()
11240
- {
11241
- return $this->strategy;
11242
- }
 
11243
 
 
 
 
 
 
 
 
 
 
 
11244
  /**
11245
- * Returns the underlying connection factory used to create new connection
11246
- * instances to Redis nodes indicated by redis-cluster.
11247
  *
11248
- * @return FactoryInterface
 
 
11249
  */
11250
- public function getConnectionFactory()
11251
- {
11252
- return $this->connections;
11253
- }
11254
 
11255
  /**
11256
- * Enables automatic fetching of the current slots map from one of the nodes
11257
- * using the CLUSTER SLOTS command. This option is disabled by default but
11258
- * asking the current slots map to Redis upon -MOVED responses may reduce
11259
- * overhead by eliminating the trial-and-error nature of the node guessing
11260
- * procedure, mostly when targeting many keys that would end up in a lot of
11261
- * redirections.
11262
  *
11263
- * The slots map can still be manually fetched using the askSlotsMap()
11264
- * method whether or not this option is enabled.
11265
  *
11266
- * @param bool $value Enable or disable the use of CLUSTER SLOTS.
11267
  */
11268
- public function useClusterSlots($value)
11269
- {
11270
- $this->useClusterSlots = (bool) $value;
11271
- }
11272
 
11273
  /**
11274
- * Sets a default array of connection parameters to be applied when creating
11275
- * new connection instances on the fly when they are not part of the initial
11276
- * pool supplied upon cluster initialization.
11277
- *
11278
- * These parameters are not applied to connections added to the pool using
11279
- * the add() method.
11280
  *
11281
- * @param array $parameters Array of connection parameters.
11282
  */
11283
- public function setDefaultParameters(array $parameters)
11284
- {
11285
- $this->defaultParameters = array_merge(
11286
- $this->defaultParameters,
11287
- $parameters ?: array()
11288
- );
11289
- }
11290
  }
11291
 
11292
  /**
11293
- * Abstraction for a cluster of aggregate connections to various Redis servers
11294
- * implementing client-side sharding based on pluggable distribution strategies.
11295
  *
11296
  * @author Daniele Alessandri <suppakilla@gmail.com>
11297
- *
11298
- * @todo Add the ability to remove connections from pool.
11299
  */
11300
- class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
11301
  {
11302
- private $pool;
11303
- private $strategy;
11304
- private $distributor;
11305
 
11306
  /**
11307
- * @param StrategyInterface $strategy Optional cluster strategy.
11308
  */
11309
- public function __construct(StrategyInterface $strategy = null)
11310
  {
11311
- $this->pool = array();
11312
- $this->strategy = $strategy ?: new PredisStrategy();
11313
- $this->distributor = $this->strategy->getDistributor();
11314
  }
11315
 
11316
  /**
11317
- * {@inheritdoc}
 
 
11318
  */
11319
- public function isConnected()
11320
  {
11321
- foreach ($this->pool as $connection) {
11322
- if ($connection->isConnected()) {
11323
- return true;
11324
- }
11325
- }
11326
 
11327
- return false;
11328
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11329
 
11330
- /**
11331
- * {@inheritdoc}
11332
- */
11333
- public function connect()
11334
- {
11335
- foreach ($this->pool as $connection) {
11336
- $connection->connect();
11337
- }
11338
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
11339
 
11340
- /**
11341
- * {@inheritdoc}
11342
- */
11343
- public function disconnect()
11344
- {
11345
- foreach ($this->pool as $connection) {
11346
- $connection->disconnect();
11347
- }
11348
- }
 
 
 
 
 
 
 
11349
 
11350
- /**
11351
- * {@inheritdoc}
11352
- */
11353
- public function add(NodeConnectionInterface $connection)
11354
- {
11355
- $parameters = $connection->getParameters();
11356
 
11357
- if (isset($parameters->alias)) {
11358
- $this->pool[$parameters->alias] = $connection;
11359
- } else {
11360
- $this->pool[] = $connection;
11361
- }
11362
 
11363
- $weight = isset($parameters->weight) ? $parameters->weight : null;
11364
- $this->distributor->add($connection, $weight);
 
 
 
 
 
 
11365
  }
11366
 
11367
  /**
11368
- * {@inheritdoc}
 
 
11369
  */
11370
- public function remove(NodeConnectionInterface $connection)
11371
  {
11372
- if (($id = array_search($connection, $this->pool, true)) !== false) {
11373
- unset($this->pool[$id]);
11374
- $this->distributor->remove($connection);
11375
-
11376
- return true;
11377
- }
11378
-
11379
- return false;
11380
  }
11381
 
11382
  /**
11383
- * Removes a connection instance using its alias or index.
11384
  *
11385
- * @param string $connectionID Alias or index of a connection.
 
11386
  *
11387
- * @return bool Returns true if the connection was in the pool.
 
 
 
 
 
 
11388
  */
11389
- public function removeById($connectionID)
11390
  {
11391
- if ($connection = $this->getConnectionById($connectionID)) {
11392
- return $this->remove($connection);
11393
- }
11394
 
11395
- return false;
11396
- }
11397
 
11398
- /**
11399
- * {@inheritdoc}
11400
- */
11401
- public function getConnection(CommandInterface $command)
11402
- {
11403
- $slot = $this->strategy->getSlot($command);
11404
 
11405
- if (!isset($slot)) {
11406
- throw new NotSupportedException(
11407
- "Cannot use '{$command->getId()}' over clusters of connections."
11408
  );
11409
  }
11410
 
11411
- $node = $this->distributor->getBySlot($slot);
11412
-
11413
- return $node;
11414
  }
11415
 
11416
  /**
11417
- * {@inheritdoc}
 
 
 
 
11418
  */
11419
- public function getConnectionById($connectionID)
11420
  {
11421
- return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null;
11422
  }
11423
 
11424
  /**
11425
- * Retrieves a connection instance from the cluster using a key.
 
11426
  *
11427
- * @param string $key Key string.
11428
  *
11429
- * @return NodeConnectionInterface
11430
  */
11431
- public function getConnectionByKey($key)
11432
  {
11433
- $hash = $this->strategy->getSlotByKey($key);
11434
- $node = $this->distributor->getBySlot($hash);
11435
 
11436
- return $node;
 
 
11437
  }
11438
 
11439
  /**
11440
- * Returns the underlying command hash strategy used to hash commands by
11441
- * using keys found in their arguments.
11442
  *
11443
- * @return StrategyInterface
 
 
11444
  */
11445
- public function getClusterStrategy()
11446
  {
11447
- return $this->strategy;
11448
- }
11449
 
11450
- /**
11451
- * {@inheritdoc}
11452
- */
11453
- public function count()
11454
- {
11455
- return count($this->pool);
11456
- }
11457
 
11458
- /**
11459
- * {@inheritdoc}
11460
- */
11461
- public function getIterator()
11462
- {
11463
- return new \ArrayIterator($this->pool);
11464
  }
11465
 
11466
  /**
11467
- * {@inheritdoc}
 
 
 
 
11468
  */
11469
- public function writeRequest(CommandInterface $command)
11470
  {
11471
- $this->getConnection($command)->writeRequest($command);
11472
- }
11473
 
11474
- /**
11475
- * {@inheritdoc}
11476
- */
11477
- public function readResponse(CommandInterface $command)
11478
- {
11479
- return $this->getConnection($command)->readResponse($command);
11480
- }
11481
 
11482
- /**
11483
- * {@inheritdoc}
11484
- */
11485
- public function executeCommand(CommandInterface $command)
11486
- {
11487
- return $this->getConnection($command)->executeCommand($command);
 
 
 
 
 
11488
  }
11489
 
11490
  /**
11491
- * Executes the specified Redis command on all the nodes of a cluster.
11492
  *
11493
- * @param CommandInterface $command A Redis command.
11494
  *
11495
- * @return array
11496
  */
11497
- public function executeCommandOnNodes(CommandInterface $command)
11498
  {
11499
- $responses = array();
11500
 
11501
- foreach ($this->pool as $connection) {
11502
- $responses[] = $connection->executeCommand($command);
11503
  }
11504
-
11505
- return $responses;
11506
  }
11507
- }
11508
-
11509
- /**
11510
- * Aggregate connection handling replication of Redis nodes configured in a
11511
- * single master / multiple slaves setup.
11512
- *
11513
- * @author Daniele Alessandri <suppakilla@gmail.com>
11514
- */
11515
- class MasterSlaveReplication implements ReplicationInterface
11516
- {
11517
- protected $strategy;
11518
- protected $master;
11519
- protected $slaves;
11520
- protected $current;
11521
 
11522
  /**
11523
- * {@inheritdoc}
 
 
 
 
11524
  */
11525
- public function __construct(ReplicationStrategy $strategy = null)
11526
  {
11527
- $this->slaves = array();
11528
- $this->strategy = $strategy ?: new ReplicationStrategy();
 
 
 
11529
  }
11530
 
11531
  /**
11532
- * Checks if one master and at least one slave have been defined.
 
 
 
 
11533
  */
11534
- protected function check()
11535
  {
11536
- if (!isset($this->master) || !$this->slaves) {
11537
- throw new \RuntimeException('Replication needs one master and at least one slave.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11538
  }
 
 
11539
  }
11540
 
11541
  /**
11542
- * Resets the connection state.
 
 
 
 
11543
  */
11544
- protected function reset()
11545
  {
11546
- $this->current = null;
 
 
 
 
 
11547
  }
11548
 
11549
  /**
11550
- * {@inheritdoc}
 
 
 
 
11551
  */
11552
- public function add(NodeConnectionInterface $connection)
11553
  {
11554
- $alias = $connection->getParameters()->alias;
11555
-
11556
- if ($alias === 'master') {
11557
- $this->master = $connection;
11558
  } else {
11559
- $this->slaves[$alias ?: count($this->slaves)] = $connection;
11560
  }
11561
 
11562
- $this->reset();
 
 
11563
  }
11564
 
11565
  /**
11566
  * {@inheritdoc}
11567
  */
11568
- public function remove(NodeConnectionInterface $connection)
11569
  {
11570
- if ($connection->getParameters()->alias === 'master') {
11571
- $this->master = null;
11572
- $this->reset();
11573
 
11574
- return true;
11575
- } else {
11576
- if (($id = array_search($connection, $this->slaves, true)) !== false) {
11577
- unset($this->slaves[$id]);
11578
- $this->reset();
11579
 
11580
- return true;
 
 
11581
  }
11582
  }
11583
 
11584
- return false;
11585
  }
11586
 
11587
  /**
11588
- * {@inheritdoc}
 
 
 
 
11589
  */
11590
- public function getConnection(CommandInterface $command)
11591
  {
11592
- if ($this->current === null) {
11593
- $this->check();
11594
- $this->current = $this->strategy->isReadOperation($command)
11595
- ? $this->pickSlave()
11596
- : $this->master;
11597
-
11598
- return $this->current;
11599
  }
11600
 
11601
- if ($this->current === $this->master) {
11602
- return $this->current;
11603
- }
11604
 
11605
- if (!$this->strategy->isReadOperation($command)) {
11606
- $this->current = $this->master;
 
 
 
 
 
 
11607
  }
11608
 
11609
- return $this->current;
11610
  }
11611
 
11612
  /**
11613
- * {@inheritdoc}
 
 
 
 
 
11614
  */
11615
- public function getConnectionById($connectionId)
11616
  {
11617
- if ($connectionId === 'master') {
11618
- return $this->master;
11619
- }
11620
-
11621
- if (isset($this->slaves[$connectionId])) {
11622
- return $this->slaves[$connectionId];
11623
  }
11624
 
11625
- return;
11626
  }
 
 
 
 
 
 
 
 
 
 
11627
 
11628
  /**
11629
- * {@inheritdoc}
11630
  */
11631
- public function switchTo($connection)
11632
  {
11633
- $this->check();
11634
-
11635
- if (!$connection instanceof NodeConnectionInterface) {
11636
- $connection = $this->getConnectionById($connection);
11637
- }
11638
- if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
11639
- throw new \InvalidArgumentException('Invalid connection or connection not found.');
11640
- }
11641
 
11642
- $this->current = $connection;
11643
  }
11644
 
11645
  /**
11646
  * {@inheritdoc}
11647
  */
11648
- public function getCurrent()
11649
  {
11650
- return $this->current;
 
 
 
 
11651
  }
11652
 
11653
  /**
11654
  * {@inheritdoc}
11655
  */
11656
- public function getMaster()
11657
  {
11658
- return $this->master;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11659
  }
11660
 
11661
  /**
11662
  * {@inheritdoc}
11663
  */
11664
- public function getSlaves()
11665
  {
11666
- return array_values($this->slaves);
11667
  }
 
11668
 
11669
- /**
11670
- * Returns the underlying replication strategy.
11671
- *
11672
- * @return ReplicationStrategy
11673
- */
11674
- public function getReplicationStrategy()
11675
- {
11676
- return $this->strategy;
11677
- }
11678
 
11679
  /**
11680
- * Returns a random slave.
11681
- *
11682
- * @return NodeConnectionInterface
11683
  */
11684
- protected function pickSlave()
11685
  {
11686
- return $this->slaves[array_rand($this->slaves)];
11687
- }
11688
 
11689
- /**
11690
- * {@inheritdoc}
11691
- */
11692
- public function isConnected()
11693
- {
11694
- return $this->current ? $this->current->isConnected() : false;
11695
  }
11696
 
11697
  /**
11698
  * {@inheritdoc}
11699
  */
11700
- public function connect()
11701
  {
11702
- if ($this->current === null) {
11703
- $this->check();
11704
- $this->current = $this->pickSlave();
11705
- }
11706
 
11707
- $this->current->connect();
11708
  }
11709
 
11710
  /**
11711
  * {@inheritdoc}
11712
  */
11713
- public function disconnect()
11714
  {
11715
- if ($this->master) {
11716
- $this->master->disconnect();
11717
- }
11718
-
11719
- foreach ($this->slaves as $connection) {
11720
- $connection->disconnect();
11721
- }
11722
  }
 
 
 
 
 
 
 
 
 
11723
 
 
 
 
 
 
 
 
 
11724
  /**
11725
- * {@inheritdoc}
 
 
 
11726
  */
11727
- public function writeRequest(CommandInterface $command)
11728
- {
11729
- $this->getConnection($command)->writeRequest($command);
11730
- }
11731
 
11732
  /**
11733
- * {@inheritdoc}
 
 
 
 
11734
  */
11735
- public function readResponse(CommandInterface $command)
11736
- {
11737
- return $this->getConnection($command)->readResponse($command);
11738
- }
11739
 
 
 
 
 
 
 
 
11740
  /**
11741
- * {@inheritdoc}
 
 
 
 
11742
  */
11743
- public function executeCommand(CommandInterface $command)
11744
- {
11745
- return $this->getConnection($command)->executeCommand($command);
11746
- }
11747
 
 
 
 
 
 
 
 
 
11748
  /**
11749
- * {@inheritdoc}
 
 
 
 
11750
  */
11751
- public function __sleep()
11752
- {
11753
- return array('master', 'slaves', 'strategy');
11754
- }
 
 
 
 
 
 
 
11755
  }
11756
 
11757
  /* --------------------------------------------------------------------------- */
@@ -11998,24 +13726,101 @@ class Pipeline implements ClientContextInterface
11998
  }
11999
 
12000
  /**
12001
- * Command pipeline that writes commands to the servers but discards responses.
12002
  *
12003
  * @author Daniele Alessandri <suppakilla@gmail.com>
12004
  */
12005
- class FireAndForget extends Pipeline
12006
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12007
  /**
12008
  * {@inheritdoc}
12009
  */
12010
  protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands)
12011
  {
12012
- while (!$commands->isEmpty()) {
12013
- $connection->writeRequest($commands->dequeue());
 
 
 
12014
  }
12015
 
12016
- $connection->disconnect();
 
12017
 
12018
- return array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12019
  }
12020
  }
12021
 
@@ -12124,112 +13929,35 @@ class ConnectionErrorProof extends Pipeline
12124
  } catch (CommunicationException $exception) {
12125
  $responses[$i] = $exception;
12126
  $exceptions[$connectionHash] = $exception;
12127
- }
12128
- }
12129
-
12130
- return $responses;
12131
- }
12132
- }
12133
-
12134
- /**
12135
- * Command pipeline wrapped into a MULTI / EXEC transaction.
12136
- *
12137
- * @author Daniele Alessandri <suppakilla@gmail.com>
12138
- */
12139
- class Atomic extends Pipeline
12140
- {
12141
- /**
12142
- * {@inheritdoc}
12143
- */
12144
- public function __construct(ClientInterface $client)
12145
- {
12146
- if (!$client->getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) {
12147
- throw new ClientException(
12148
- "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'."
12149
- );
12150
- }
12151
-
12152
- parent::__construct($client);
12153
- }
12154
-
12155
- /**
12156
- * {@inheritdoc}
12157
- */
12158
- protected function getConnection()
12159
- {
12160
- $connection = $this->getClient()->getConnection();
12161
-
12162
- if (!$connection instanceof NodeConnectionInterface) {
12163
- $class = __CLASS__;
12164
-
12165
- throw new ClientException("The class '$class' does not support aggregate connections.");
12166
- }
12167
-
12168
- return $connection;
12169
- }
12170
-
12171
- /**
12172
- * {@inheritdoc}
12173
- */
12174
- protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands)
12175
- {
12176
- $profile = $this->getClient()->getProfile();
12177
- $connection->executeCommand($profile->createCommand('multi'));
12178
-
12179
- foreach ($commands as $command) {
12180
- $connection->writeRequest($command);
12181
- }
12182
-
12183
- foreach ($commands as $command) {
12184
- $response = $connection->readResponse($command);
12185
-
12186
- if ($response instanceof ErrorResponseInterface) {
12187
- $connection->executeCommand($profile->createCommand('discard'));
12188
- throw new ServerException($response->getMessage());
12189
- }
12190
- }
12191
-
12192
- $executed = $connection->executeCommand($profile->createCommand('exec'));
12193
-
12194
- if (!isset($executed)) {
12195
- // TODO: should be throwing a more appropriate exception.
12196
- throw new ClientException(
12197
- 'The underlying transaction has been aborted by the server.'
12198
- );
12199
- }
12200
-
12201
- if (count($executed) !== count($commands)) {
12202
- $expected = count($commands);
12203
- $received = count($executed);
12204
-
12205
- throw new ClientException(
12206
- "Invalid number of responses [expected $expected, received $received]."
12207
- );
12208
- }
12209
-
12210
- $responses = array();
12211
- $sizeOfPipe = count($commands);
12212
- $exceptions = $this->throwServerExceptions();
12213
-
12214
- for ($i = 0; $i < $sizeOfPipe; ++$i) {
12215
- $command = $commands->dequeue();
12216
- $response = $executed[$i];
12217
-
12218
- if (!$response instanceof ResponseInterface) {
12219
- $responses[] = $command->parseResponse($response);
12220
- } elseif ($response instanceof ErrorResponseInterface && $exceptions) {
12221
- $this->exception($connection, $response);
12222
- } else {
12223
- $responses[] = $response;
12224
- }
12225
-
12226
- unset($executed[$i]);
12227
  }
12228
 
12229
  return $responses;
12230
  }
12231
  }
12232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12233
  /* --------------------------------------------------------------------------- */
12234
 
12235
  namespace Predis\Cluster\Distributor;
@@ -12560,6 +14288,15 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
12560
  }
12561
  }
12562
 
 
 
 
 
 
 
 
 
 
12563
  /**
12564
  * This class implements an hashring-based distributor that uses the same
12565
  * algorithm of libketama to distribute keys in a cluster using client-side
@@ -12619,15 +14356,6 @@ class KetamaRing extends HashRing
12619
  }
12620
  }
12621
 
12622
- /**
12623
- * Exception class that identifies empty rings.
12624
- *
12625
- * @author Daniele Alessandri <suppakilla@gmail.com>
12626
- */
12627
- class EmptyRingException extends \Exception
12628
- {
12629
- }
12630
-
12631
  /* --------------------------------------------------------------------------- */
12632
 
12633
  namespace Predis\Response\Iterator;
@@ -12828,262 +14556,64 @@ class MultiBulkTuple extends MultiBulk implements \OuterIterator
12828
  throw new \InvalidArgumentException(
12829
  'Cannot initialize a tuple iterator using an already initiated iterator.'
12830
  );
12831
- }
12832
-
12833
- if (($size = count($iterator)) % 2 !== 0) {
12834
- throw new \UnexpectedValueException('Invalid response size for a tuple iterator.');
12835
- }
12836
- }
12837
-
12838
- /**
12839
- * {@inheritdoc}
12840
- */
12841
- public function getInnerIterator()
12842
- {
12843
- return $this->iterator;
12844
- }
12845
-
12846
- /**
12847
- * {@inheritdoc}
12848
- */
12849
- public function __destruct()
12850
- {
12851
- $this->iterator->drop(true);
12852
- }
12853
-
12854
- /**
12855
- * {@inheritdoc}
12856
- */
12857
- protected function getValue()
12858
- {
12859
- $k = $this->iterator->current();
12860
- $this->iterator->next();
12861
-
12862
- $v = $this->iterator->current();
12863
- $this->iterator->next();
12864
-
12865
- return array($k, $v);
12866
- }
12867
- }
12868
-
12869
- /* --------------------------------------------------------------------------- */
12870
-
12871
- namespace Predis\Cluster\Hash;
12872
-
12873
- /**
12874
- * An hash generator implements the logic used to calculate the hash of a key to
12875
- * distribute operations among Redis nodes.
12876
- *
12877
- * @author Daniele Alessandri <suppakilla@gmail.com>
12878
- */
12879
- interface HashGeneratorInterface
12880
- {
12881
- /**
12882
- * Generates an hash from a string to be used for distribution.
12883
- *
12884
- * @param string $value String value.
12885
- *
12886
- * @return int
12887
- */
12888
- public function hash($value);
12889
- }
12890
-
12891
- /**
12892
- * Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
12893
- *
12894
- * @author Daniele Alessandri <suppakilla@gmail.com>
12895
- */
12896
- class CRC16 implements HashGeneratorInterface
12897
- {
12898
- private static $CCITT_16 = array(
12899
- 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
12900
- 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
12901
- 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
12902
- 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
12903
- 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
12904
- 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
12905
- 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
12906
- 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
12907
- 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
12908
- 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
12909
- 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
12910
- 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
12911
- 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
12912
- 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
12913
- 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
12914
- 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
12915
- 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
12916
- 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
12917
- 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
12918
- 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
12919
- 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
12920
- 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
12921
- 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
12922
- 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
12923
- 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
12924
- 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
12925
- 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
12926
- 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
12927
- 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
12928
- 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
12929
- 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
12930
- 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
12931
- );
12932
-
12933
- /**
12934
- * {@inheritdoc}
12935
- */
12936
- public function hash($value)
12937
- {
12938
- // CRC-CCITT-16 algorithm
12939
- $crc = 0;
12940
- $CCITT_16 = self::$CCITT_16;
12941
- $strlen = strlen($value);
12942
-
12943
- for ($i = 0; $i < $strlen; ++$i) {
12944
- $crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
12945
- }
12946
-
12947
- return $crc;
12948
- }
12949
- }
12950
-
12951
- /* --------------------------------------------------------------------------- */
12952
-
12953
- namespace Predis\Command\Processor;
12954
-
12955
- use Predis\Command\CommandInterface;
12956
- use Predis\Command\PrefixableCommandInterface;
12957
-
12958
- /**
12959
- * A command processor processes Redis commands before they are sent to Redis.
12960
- *
12961
- * @author Daniele Alessandri <suppakilla@gmail.com>
12962
- */
12963
- interface ProcessorInterface
12964
- {
12965
- /**
12966
- * Processes the given Redis command.
12967
- *
12968
- * @param CommandInterface $command Command instance.
12969
- */
12970
- public function process(CommandInterface $command);
12971
- }
12972
-
12973
- /**
12974
- * Default implementation of a command processors chain.
12975
- *
12976
- * @author Daniele Alessandri <suppakilla@gmail.com>
12977
- */
12978
- class ProcessorChain implements \ArrayAccess, ProcessorInterface
12979
- {
12980
- private $processors = array();
12981
-
12982
- /**
12983
- * @param array $processors List of instances of ProcessorInterface.
12984
- */
12985
- public function __construct($processors = array())
12986
- {
12987
- foreach ($processors as $processor) {
12988
- $this->add($processor);
12989
- }
12990
- }
12991
-
12992
- /**
12993
- * {@inheritdoc}
12994
- */
12995
- public function add(ProcessorInterface $processor)
12996
- {
12997
- $this->processors[] = $processor;
12998
- }
12999
-
13000
- /**
13001
- * {@inheritdoc}
13002
- */
13003
- public function remove(ProcessorInterface $processor)
13004
- {
13005
- if (false !== $index = array_search($processor, $this->processors, true)) {
13006
- unset($this[$index]);
13007
- }
13008
- }
13009
-
13010
- /**
13011
- * {@inheritdoc}
13012
- */
13013
- public function process(CommandInterface $command)
13014
- {
13015
- for ($i = 0; $i < $count = count($this->processors); ++$i) {
13016
- $this->processors[$i]->process($command);
13017
- }
13018
- }
13019
-
13020
- /**
13021
- * {@inheritdoc}
13022
- */
13023
- public function getProcessors()
13024
- {
13025
- return $this->processors;
13026
- }
13027
-
13028
- /**
13029
- * Returns an iterator over the list of command processor in the chain.
13030
- *
13031
- * @return \ArrayIterator
13032
- */
13033
- public function getIterator()
13034
- {
13035
- return new \ArrayIterator($this->processors);
13036
- }
13037
-
13038
- /**
13039
- * Returns the number of command processors in the chain.
13040
- *
13041
- * @return int
13042
- */
13043
- public function count()
13044
- {
13045
- return count($this->processors);
13046
  }
13047
 
13048
  /**
13049
  * {@inheritdoc}
13050
  */
13051
- public function offsetExists($index)
13052
  {
13053
- return isset($this->processors[$index]);
13054
  }
13055
 
13056
  /**
13057
  * {@inheritdoc}
13058
  */
13059
- public function offsetGet($index)
13060
  {
13061
- return $this->processors[$index];
13062
  }
13063
 
13064
  /**
13065
  * {@inheritdoc}
13066
  */
13067
- public function offsetSet($index, $processor)
13068
  {
13069
- if (!$processor instanceof ProcessorInterface) {
13070
- throw new \InvalidArgumentException(
13071
- 'A processor chain accepts only instances of '.
13072
- "'Predis\Command\Processor\ProcessorInterface'."
13073
- );
13074
- }
13075
 
13076
- $this->processors[$index] = $processor;
 
 
 
13077
  }
 
 
 
 
 
 
 
 
13078
 
 
 
 
 
 
 
 
13079
  /**
13080
- * {@inheritdoc}
 
 
13081
  */
13082
- public function offsetUnset($index)
13083
- {
13084
- unset($this->processors[$index]);
13085
- $this->processors = array_values($this->processors);
13086
- }
13087
  }
13088
 
13089
  /**
@@ -13105,7 +14635,7 @@ class KeyPrefixProcessor implements ProcessorInterface
13105
  $this->prefix = $prefix;
13106
  $this->commands = array(
13107
  /* ---------------- Redis 1.2 ---------------- */
13108
- 'EXISTS' => 'static::first',
13109
  'DEL' => 'static::all',
13110
  'TYPE' => 'static::first',
13111
  'KEYS' => 'static::first',
@@ -13231,6 +14761,13 @@ class KeyPrefixProcessor implements ProcessorInterface
13231
  'BITPOS' => 'static::first',
13232
  /* ---------------- Redis 3.2 ---------------- */
13233
  'HSTRLEN' => 'static::first',
 
 
 
 
 
 
 
13234
  );
13235
  }
13236
 
@@ -13379,110 +14916,336 @@ class KeyPrefixProcessor implements ProcessorInterface
13379
  }
13380
 
13381
  /**
13382
- * Applies the specified prefix to all the arguments but the last one.
13383
- *
13384
- * @param CommandInterface $command Command instance.
13385
- * @param string $prefix Prefix string.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13386
  */
13387
- public static function skipLast(CommandInterface $command, $prefix)
13388
  {
13389
- if ($arguments = $command->getArguments()) {
13390
- $length = count($arguments);
13391
-
13392
- for ($i = 0; $i < $length - 1; ++$i) {
13393
- $arguments[$i] = "$prefix{$arguments[$i]}";
13394
- }
13395
-
13396
- $command->setRawArguments($arguments);
13397
- }
13398
  }
13399
 
13400
  /**
13401
- * Applies the specified prefix to the keys of a SORT command.
13402
- *
13403
- * @param CommandInterface $command Command instance.
13404
- * @param string $prefix Prefix string.
13405
  */
13406
- public static function sort(CommandInterface $command, $prefix)
13407
  {
13408
- if ($arguments = $command->getArguments()) {
13409
- $arguments[0] = "$prefix{$arguments[0]}";
13410
-
13411
- if (($count = count($arguments)) > 1) {
13412
- for ($i = 1; $i < $count; ++$i) {
13413
- switch ($arguments[$i]) {
13414
- case 'BY':
13415
- case 'STORE':
13416
- $arguments[$i] = "$prefix{$arguments[++$i]}";
13417
- break;
13418
-
13419
- case 'GET':
13420
- $value = $arguments[++$i];
13421
- if ($value !== '#') {
13422
- $arguments[$i] = "$prefix$value";
13423
- }
13424
- break;
13425
-
13426
- case 'LIMIT';
13427
- $i += 2;
13428
- break;
13429
- }
13430
- }
13431
- }
13432
-
13433
- $command->setRawArguments($arguments);
13434
- }
13435
  }
13436
 
13437
  /**
13438
- * Applies the specified prefix to the keys of an EVAL-based command.
13439
- *
13440
- * @param CommandInterface $command Command instance.
13441
- * @param string $prefix Prefix string.
13442
  */
13443
- public static function evalKeys(CommandInterface $command, $prefix)
13444
  {
13445
- if ($arguments = $command->getArguments()) {
13446
- for ($i = 2; $i < $arguments[1] + 2; ++$i) {
13447
- $arguments[$i] = "$prefix{$arguments[$i]}";
13448
- }
13449
-
13450
- $command->setRawArguments($arguments);
13451
  }
 
 
13452
  }
13453
 
13454
  /**
13455
- * Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE.
13456
- *
13457
- * @param CommandInterface $command Command instance.
13458
- * @param string $prefix Prefix string.
13459
  */
13460
- public static function zsetStore(CommandInterface $command, $prefix)
13461
  {
13462
- if ($arguments = $command->getArguments()) {
13463
- $arguments[0] = "$prefix{$arguments[0]}";
13464
- $length = ((int) $arguments[1]) + 2;
 
13465
 
13466
- for ($i = 2; $i < $length; ++$i) {
13467
- $arguments[$i] = "$prefix{$arguments[$i]}";
13468
- }
13469
 
13470
- $command->setRawArguments($arguments);
13471
- }
13472
- }
13473
 
 
 
 
 
 
 
 
 
13474
  /**
13475
- * Applies the specified prefix to the key of a MIGRATE command.
13476
  *
13477
- * @param CommandInterface $command Command instance.
13478
- * @param string $prefix Prefix string.
 
13479
  */
13480
- public static function migrate(CommandInterface $command, $prefix)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13481
  {
13482
- if ($arguments = $command->getArguments()) {
13483
- $arguments[2] = "$prefix{$arguments[2]}";
13484
- $command->setRawArguments($arguments);
 
 
 
 
13485
  }
 
 
13486
  }
13487
  }
13488
 
@@ -13502,133 +15265,91 @@ use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
13502
  use Predis\Response\Status as StatusResponse;
13503
 
13504
  /**
13505
- * Response reader for the standard Redis wire protocol.
 
13506
  *
13507
  * @link http://redis.io/topics/protocol
13508
  *
13509
  * @author Daniele Alessandri <suppakilla@gmail.com>
13510
  */
13511
- class ResponseReader implements ResponseReaderInterface
13512
  {
13513
- protected $handlers;
 
 
 
 
 
 
 
 
13514
 
13515
  /**
13516
- *
 
13517
  */
13518
- public function __construct()
13519
- {
13520
- $this->handlers = $this->getDefaultHandlers();
 
 
 
13521
  }
13522
 
13523
  /**
13524
- * Returns the default handlers for the supported type of responses.
13525
- *
13526
- * @return array
13527
  */
13528
- protected function getDefaultHandlers()
13529
  {
13530
- return array(
13531
- '+' => new Handler\StatusResponse(),
13532
- '-' => new Handler\ErrorResponse(),
13533
- ':' => new Handler\IntegerResponse(),
13534
- '$' => new Handler\BulkResponse(),
13535
- '*' => new Handler\MultiBulkResponse(),
13536
- );
13537
  }
13538
 
13539
  /**
13540
- * Sets the handler for the specified prefix identifying the response type.
13541
- *
13542
- * @param string $prefix Identifier of the type of response.
13543
- * @param Handler\ResponseHandlerInterface $handler Response handler.
13544
  */
13545
- public function setHandler($prefix, Handler\ResponseHandlerInterface $handler)
13546
  {
13547
- $this->handlers[$prefix] = $handler;
13548
  }
13549
 
13550
  /**
13551
- * Returns the response handler associated to a certain type of response.
13552
- *
13553
- * @param string $prefix Identifier of the type of response.
13554
  *
13555
- * @return Handler\ResponseHandlerInterface
13556
  */
13557
- public function getHandler($prefix)
13558
  {
13559
- if (isset($this->handlers[$prefix])) {
13560
- return $this->handlers[$prefix];
13561
- }
13562
-
13563
- return;
13564
  }
13565
 
13566
  /**
13567
- * {@inheritdoc}
 
 
13568
  */
13569
- public function read(CompositeConnectionInterface $connection)
13570
- {
13571
- $header = $connection->readLine();
13572
-
13573
- if ($header === '') {
13574
- $this->onProtocolError($connection, 'Unexpected empty reponse header.');
13575
- }
13576
-
13577
- $prefix = $header[0];
13578
-
13579
- if (!isset($this->handlers[$prefix])) {
13580
- $this->onProtocolError($connection, "Unknown response prefix: '$prefix'.");
13581
- }
13582
-
13583
- $payload = $this->handlers[$prefix]->handle($connection, substr($header, 1));
13584
-
13585
- return $payload;
13586
  }
13587
 
13588
  /**
13589
- * Handles protocol errors generated while reading responses from a
13590
- * connection.
13591
  *
13592
- * @param CompositeConnectionInterface $connection Redis connection that generated the error.
13593
- * @param string $message Error message.
13594
  */
13595
- protected function onProtocolError(CompositeConnectionInterface $connection, $message)
13596
  {
13597
- CommunicationException::handle(
13598
- new ProtocolException($connection, $message)
13599
- );
13600
  }
13601
- }
13602
 
13603
- /**
13604
- * Request serializer for the standard Redis wire protocol.
13605
- *
13606
- * @link http://redis.io/topics/protocol
13607
- *
13608
- * @author Daniele Alessandri <suppakilla@gmail.com>
13609
- */
13610
- class RequestSerializer implements RequestSerializerInterface
13611
- {
13612
  /**
13613
- * {@inheritdoc}
 
 
13614
  */
13615
- public function serialize(CommandInterface $command)
13616
  {
13617
- $commandID = $command->getId();
13618
- $arguments = $command->getArguments();
13619
-
13620
- $cmdlen = strlen($commandID);
13621
- $reqlen = count($arguments) + 1;
13622
-
13623
- $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
13624
-
13625
- for ($i = 0, $reqlen--; $i < $reqlen; ++$i) {
13626
- $argument = $arguments[$i];
13627
- $arglen = strlen($argument);
13628
- $buffer .= "\${$arglen}\r\n{$argument}\r\n";
13629
- }
13630
-
13631
- return $buffer;
13632
  }
13633
  }
13634
 
@@ -13702,7 +15423,8 @@ class ProtocolProcessor implements ProtocolProcessorInterface
13702
  return $multibulk;
13703
 
13704
  case ':':
13705
- return (int) $payload;
 
13706
 
13707
  case '-':
13708
  return new ErrorResponse($payload);
@@ -13734,91 +15456,132 @@ class ProtocolProcessor implements ProtocolProcessorInterface
13734
  }
13735
 
13736
  /**
13737
- * Composite protocol processor for the standard Redis wire protocol using
13738
- * pluggable handlers to serialize requests and deserialize responses.
13739
  *
13740
  * @link http://redis.io/topics/protocol
13741
  *
13742
  * @author Daniele Alessandri <suppakilla@gmail.com>
13743
  */
13744
- class CompositeProtocolProcessor implements ProtocolProcessorInterface
13745
  {
13746
- /*
13747
- * @var RequestSerializerInterface
13748
  */
13749
- protected $serializer;
 
 
 
13750
 
13751
- /*
13752
- * @var ResponseReaderInterface
13753
- */
13754
- protected $reader;
13755
 
13756
- /**
13757
- * @param RequestSerializerInterface $serializer Request serializer.
13758
- * @param ResponseReaderInterface $reader Response reader.
13759
- */
13760
- public function __construct(
13761
- RequestSerializerInterface $serializer = null,
13762
- ResponseReaderInterface $reader = null
13763
- ) {
13764
- $this->setRequestSerializer($serializer ?: new RequestSerializer());
13765
- $this->setResponseReader($reader ?: new ResponseReader());
13766
  }
 
 
 
 
 
 
 
 
 
 
 
 
13767
 
13768
  /**
13769
- * {@inheritdoc}
13770
  */
13771
- public function write(CompositeConnectionInterface $connection, CommandInterface $command)
13772
  {
13773
- $connection->writeBuffer($this->serializer->serialize($command));
13774
  }
13775
 
13776
  /**
13777
- * {@inheritdoc}
 
 
13778
  */
13779
- public function read(CompositeConnectionInterface $connection)
13780
  {
13781
- return $this->reader->read($connection);
 
 
 
 
 
 
13782
  }
13783
 
13784
  /**
13785
- * Sets the request serializer used by the protocol processor.
13786
  *
13787
- * @param RequestSerializerInterface $serializer Request serializer.
 
13788
  */
13789
- public function setRequestSerializer(RequestSerializerInterface $serializer)
13790
  {
13791
- $this->serializer = $serializer;
13792
  }
13793
 
13794
  /**
13795
- * Returns the request serializer used by the protocol processor.
13796
  *
13797
- * @return RequestSerializerInterface
 
 
13798
  */
13799
- public function getRequestSerializer()
13800
  {
13801
- return $this->serializer;
 
 
 
 
13802
  }
13803
 
13804
  /**
13805
- * Sets the response reader used by the protocol processor.
13806
- *
13807
- * @param ResponseReaderInterface $reader Response reader.
13808
  */
13809
- public function setResponseReader(ResponseReaderInterface $reader)
13810
  {
13811
- $this->reader = $reader;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13812
  }
13813
 
13814
  /**
13815
- * Returns the Response reader used by the protocol processor.
 
13816
  *
13817
- * @return ResponseReaderInterface
 
13818
  */
13819
- public function getResponseReader()
13820
  {
13821
- return $this->reader;
 
 
13822
  }
13823
  }
13824
 
@@ -13839,17 +15602,17 @@ use Predis\NotSupportedException;
13839
  */
13840
  abstract class AbstractConsumer implements \Iterator
13841
  {
13842
- const SUBSCRIBE = 'subscribe';
13843
- const UNSUBSCRIBE = 'unsubscribe';
13844
- const PSUBSCRIBE = 'psubscribe';
13845
  const PUNSUBSCRIBE = 'punsubscribe';
13846
- const MESSAGE = 'message';
13847
- const PMESSAGE = 'pmessage';
13848
- const PONG = 'pong';
13849
 
13850
- const STATUS_VALID = 1; // 0b0001
13851
- const STATUS_SUBSCRIBED = 2; // 0b0010
13852
- const STATUS_PSUBSCRIBED = 4; // 0b0100
13853
 
13854
  private $position = null;
13855
  private $statusFlags = self::STATUS_VALID;
@@ -13978,65 +15741,205 @@ abstract class AbstractConsumer implements \Iterator
13978
  }
13979
 
13980
  /**
13981
- * Returns the last message payload retrieved from the server and generated
13982
- * by one of the active subscriptions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13983
  *
13984
- * @return array
13985
  */
13986
- public function current()
13987
  {
13988
- return $this->getValue();
13989
  }
13990
 
13991
  /**
13992
- * {@inheritdoc}
 
 
 
 
 
13993
  */
13994
- public function key()
13995
  {
13996
- return $this->position;
 
 
 
 
 
 
 
 
 
 
 
 
13997
  }
13998
 
13999
  /**
14000
- * {@inheritdoc}
 
 
14001
  */
14002
- public function next()
14003
  {
14004
- if ($this->valid()) {
14005
- ++$this->position;
14006
  }
14007
-
14008
- return $this->position;
14009
  }
14010
 
14011
  /**
14012
- * Checks if the the consumer is still in a valid state to continue.
14013
- *
14014
- * @return bool
14015
  */
14016
- public function valid()
14017
  {
14018
- $isValid = $this->isFlagSet(self::STATUS_VALID);
14019
- $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED;
14020
- $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0;
14021
-
14022
- return $isValid && $hasSubscriptions;
14023
  }
14024
 
14025
  /**
14026
- * Resets the state of the consumer.
14027
  */
14028
- protected function invalidate()
14029
  {
14030
- $this->statusFlags = 0; // 0b0000;
14031
  }
14032
 
14033
  /**
14034
- * Waits for a new message from the server generated by one of the active
14035
- * subscriptions and returns it when available.
14036
- *
14037
- * @return array
14038
  */
14039
- abstract protected function getValue();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14040
  }
14041
 
14042
  /**
@@ -14119,7 +16022,7 @@ class DispatcherLoop
14119
  * Binds a callback to a channel.
14120
  *
14121
  * @param string $channel Channel name.
14122
- * @param Callable $callback A callback.
14123
  */
14124
  public function attachCallback($channel, $callback)
14125
  {
@@ -14197,314 +16100,368 @@ class DispatcherLoop
14197
  }
14198
  }
14199
 
14200
- /**
14201
- * PUB/SUB consumer abstraction.
14202
- *
14203
- * @author Daniele Alessandri <suppakilla@gmail.com>
14204
- */
14205
- class Consumer extends AbstractConsumer
14206
- {
14207
- private $client;
14208
- private $options;
14209
-
14210
- /**
14211
- * @param ClientInterface $client Client instance used by the consumer.
14212
- * @param array $options Options for the consumer initialization.
14213
- */
14214
- public function __construct(ClientInterface $client, array $options = null)
14215
- {
14216
- $this->checkCapabilities($client);
14217
-
14218
- $this->options = $options ?: array();
14219
- $this->client = $client;
14220
-
14221
- $this->genericSubscribeInit('subscribe');
14222
- $this->genericSubscribeInit('psubscribe');
14223
- }
14224
-
14225
- /**
14226
- * Returns the underlying client instance used by the pub/sub iterator.
14227
- *
14228
- * @return ClientInterface
14229
- */
14230
- public function getClient()
14231
- {
14232
- return $this->client;
14233
- }
14234
-
14235
- /**
14236
- * Checks if the client instance satisfies the required conditions needed to
14237
- * initialize a PUB/SUB consumer.
14238
- *
14239
- * @param ClientInterface $client Client instance used by the consumer.
14240
- *
14241
- * @throws NotSupportedException
14242
- */
14243
- private function checkCapabilities(ClientInterface $client)
14244
- {
14245
- if ($client->getConnection() instanceof AggregateConnectionInterface) {
14246
- throw new NotSupportedException(
14247
- 'Cannot initialize a PUB/SUB consumer over aggregate connections.'
14248
- );
14249
- }
14250
-
14251
- $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
14252
-
14253
- if ($client->getProfile()->supportsCommands($commands) === false) {
14254
- throw new NotSupportedException(
14255
- 'The current profile does not support PUB/SUB related commands.'
14256
- );
14257
- }
14258
- }
14259
-
14260
- /**
14261
- * This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE.
14262
- *
14263
- * @param string $subscribeAction Type of subscription.
14264
- */
14265
- private function genericSubscribeInit($subscribeAction)
14266
- {
14267
- if (isset($this->options[$subscribeAction])) {
14268
- $this->$subscribeAction($this->options[$subscribeAction]);
14269
- }
14270
- }
14271
-
14272
- /**
14273
- * {@inheritdoc}
14274
- */
14275
- protected function writeRequest($method, $arguments)
14276
- {
14277
- $this->client->getConnection()->writeRequest(
14278
- $this->client->createCommand($method,
14279
- Command::normalizeArguments($arguments)
14280
- )
14281
- );
14282
- }
14283
-
14284
- /**
14285
- * {@inheritdoc}
14286
- */
14287
- protected function disconnect()
14288
- {
14289
- $this->client->disconnect();
14290
- }
14291
-
14292
- /**
14293
- * {@inheritdoc}
14294
- */
14295
- protected function getValue()
14296
- {
14297
- $response = $this->client->getConnection()->read();
14298
-
14299
- switch ($response[0]) {
14300
- case self::SUBSCRIBE:
14301
- case self::UNSUBSCRIBE:
14302
- case self::PSUBSCRIBE:
14303
- case self::PUNSUBSCRIBE:
14304
- if ($response[2] === 0) {
14305
- $this->invalidate();
14306
- }
14307
- // The missing break here is intentional as we must process
14308
- // subscriptions and unsubscriptions as standard messages.
14309
- // no break
14310
-
14311
- case self::MESSAGE:
14312
- return (object) array(
14313
- 'kind' => $response[0],
14314
- 'channel' => $response[1],
14315
- 'payload' => $response[2],
14316
- );
14317
-
14318
- case self::PMESSAGE:
14319
- return (object) array(
14320
- 'kind' => $response[0],
14321
- 'pattern' => $response[1],
14322
- 'channel' => $response[2],
14323
- 'payload' => $response[3],
14324
- );
14325
-
14326
- case self::PONG:
14327
- return (object) array(
14328
- 'kind' => $response[0],
14329
- 'payload' => $response[1],
14330
- );
14331
-
14332
- default:
14333
- throw new ClientException(
14334
- "Unknown message type '{$response[0]}' received in the PUB/SUB context."
14335
- );
14336
- }
14337
- }
14338
- }
14339
-
14340
  /* --------------------------------------------------------------------------- */
14341
 
14342
- namespace Predis\Transaction;
14343
 
14344
- use Predis\PredisException;
14345
- use Predis\ClientContextInterface;
14346
  use Predis\ClientException;
14347
- use Predis\ClientInterface;
14348
  use Predis\Command\CommandInterface;
14349
- use Predis\CommunicationException;
14350
- use Predis\Connection\AggregateConnectionInterface;
14351
  use Predis\NotSupportedException;
14352
- use Predis\Protocol\ProtocolException;
14353
- use Predis\Response\ErrorInterface as ErrorResponseInterface;
14354
- use Predis\Response\ServerException;
14355
- use Predis\Response\Status as StatusResponse;
14356
 
14357
  /**
14358
- * Utility class used to track the state of a MULTI / EXEC transaction.
14359
  *
14360
  * @author Daniele Alessandri <suppakilla@gmail.com>
14361
  */
14362
- class MultiExecState
14363
  {
14364
- const INITIALIZED = 1; // 0b00001
14365
- const INSIDEBLOCK = 2; // 0b00010
14366
- const DISCARDED = 4; // 0b00100
14367
- const CAS = 8; // 0b01000
14368
- const WATCH = 16; // 0b10000
14369
 
14370
- private $flags;
 
 
 
 
 
 
 
 
 
14371
 
14372
  /**
14373
  *
14374
  */
14375
  public function __construct()
14376
  {
14377
- $this->flags = 0;
 
 
14378
  }
14379
 
14380
  /**
14381
- * Sets the internal state flags.
 
14382
  *
14383
- * @param int $flags Set of flags
 
 
 
 
14384
  */
14385
- public function set($flags)
14386
  {
14387
- $this->flags = $flags;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14388
  }
14389
 
14390
  /**
14391
- * Gets the internal state flags.
 
14392
  *
14393
- * @return int
 
 
14394
  */
14395
- public function get()
14396
  {
14397
- return $this->flags;
14398
  }
14399
 
14400
  /**
14401
- * Sets one or more flags.
 
14402
  *
14403
- * @param int $flags Set of flags
 
 
14404
  */
14405
- public function flag($flags)
14406
  {
14407
- $this->flags |= $flags;
 
 
 
 
 
 
 
 
 
 
 
 
14408
  }
14409
 
14410
  /**
14411
- * Resets one or more flags.
 
14412
  *
14413
- * @param int $flags Set of flags
 
 
14414
  */
14415
- public function unflag($flags)
14416
  {
14417
- $this->flags &= ~$flags;
 
 
 
 
 
 
 
 
 
 
 
 
14418
  }
14419
 
14420
  /**
14421
- * Returns if the specified flag or set of flags is set.
 
14422
  *
14423
- * @param int $flags Flag
14424
  *
14425
  * @return bool
14426
  */
14427
- public function check($flags)
14428
  {
14429
- return ($this->flags & $flags) === $flags;
 
 
 
 
 
 
 
 
 
 
 
 
 
14430
  }
14431
 
14432
  /**
14433
- * Resets the state of a transaction.
 
 
 
 
 
 
 
14434
  */
14435
- public function reset()
14436
  {
14437
- $this->flags = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14438
  }
 
14439
 
14440
- /**
14441
- * Returns the state of the RESET flag.
14442
- *
14443
- * @return bool
14444
- */
14445
- public function isReset()
14446
- {
14447
- return $this->flags === 0;
14448
- }
14449
 
14450
- /**
14451
- * Returns the state of the INITIALIZED flag.
14452
- *
14453
- * @return bool
14454
- */
14455
- public function isInitialized()
14456
- {
14457
- return $this->check(self::INITIALIZED);
14458
- }
14459
 
14460
- /**
14461
- * Returns the state of the INSIDEBLOCK flag.
14462
- *
14463
- * @return bool
14464
- */
14465
- public function isExecuting()
14466
- {
14467
- return $this->check(self::INSIDEBLOCK);
14468
- }
14469
 
14470
- /**
14471
- * Returns the state of the CAS flag.
14472
- *
14473
- * @return bool
14474
- */
14475
- public function isCAS()
14476
- {
14477
- return $this->check(self::CAS);
14478
- }
 
 
 
14479
 
14480
- /**
14481
- * Returns if WATCH is allowed in the current state.
14482
- *
14483
- * @return bool
14484
- */
14485
- public function isWatchAllowed()
14486
- {
14487
- return $this->check(self::INITIALIZED) && !$this->check(self::CAS);
14488
- }
14489
 
14490
  /**
14491
- * Returns the state of the WATCH flag.
14492
- *
14493
- * @return bool
14494
  */
14495
- public function isWatching()
14496
  {
14497
- return $this->check(self::WATCH);
 
14498
  }
14499
 
14500
  /**
14501
- * Returns the state of the DISCARDED flag.
14502
  *
14503
- * @return bool
14504
  */
14505
- public function isDiscarded()
14506
  {
14507
- return $this->check(self::DISCARDED);
14508
  }
14509
  }
14510
 
@@ -14946,167 +16903,156 @@ class MultiExec implements ClientContextInterface
14946
  }
14947
 
14948
  /**
14949
- * Exception class that identifies a MULTI / EXEC transaction aborted by Redis.
14950
  *
14951
  * @author Daniele Alessandri <suppakilla@gmail.com>
14952
  */
14953
- class AbortedMultiExecException extends PredisException
14954
  {
14955
- private $transaction;
 
 
 
 
 
 
14956
 
14957
  /**
14958
- * @param MultiExec $transaction Transaction that generated the exception.
14959
- * @param string $message Error message.
14960
- * @param int $code Error code.
14961
  */
14962
- public function __construct(MultiExec $transaction, $message, $code = null)
14963
  {
14964
- parent::__construct($message, $code);
14965
- $this->transaction = $transaction;
14966
  }
14967
 
14968
  /**
14969
- * Returns the transaction that generated the exception.
14970
  *
14971
- * @return MultiExec
14972
  */
14973
- public function getTransaction()
14974
  {
14975
- return $this->transaction;
14976
  }
14977
- }
14978
-
14979
- /* --------------------------------------------------------------------------- */
14980
-
14981
- namespace Predis\Session;
14982
-
14983
- use Predis\ClientInterface;
14984
-
14985
- /**
14986
- * Session handler class that relies on Predis\Client to store PHP's sessions
14987
- * data into one or multiple Redis servers.
14988
- *
14989
- * This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3
14990
- * provided that a polyfill for `SessionHandlerInterface` is defined by either
14991
- * you or an external package such as `symfony/http-foundation`.
14992
- *
14993
- * @author Daniele Alessandri <suppakilla@gmail.com>
14994
- */
14995
- class Handler implements \SessionHandlerInterface
14996
- {
14997
- protected $client;
14998
- protected $ttl;
14999
 
15000
  /**
15001
- * @param ClientInterface $client Fully initialized client instance.
15002
- * @param array $options Session handler options.
 
15003
  */
15004
- public function __construct(ClientInterface $client, array $options = array())
15005
  {
15006
- $this->client = $client;
15007
-
15008
- if (isset($options['gc_maxlifetime'])) {
15009
- $this->ttl = (int) $options['gc_maxlifetime'];
15010
- } else {
15011
- $this->ttl = ini_get('session.gc_maxlifetime');
15012
- }
15013
  }
15014
 
15015
  /**
15016
- * Registers this instance as the current session handler.
 
 
15017
  */
15018
- public function register()
15019
  {
15020
- if (PHP_VERSION_ID >= 50400) {
15021
- session_set_save_handler($this, true);
15022
- } else {
15023
- session_set_save_handler(
15024
- array($this, 'open'),
15025
- array($this, 'close'),
15026
- array($this, 'read'),
15027
- array($this, 'write'),
15028
- array($this, 'destroy'),
15029
- array($this, 'gc')
15030
- );
15031
- }
15032
  }
15033
 
15034
  /**
15035
- * {@inheritdoc}
 
 
15036
  */
15037
- public function open($save_path, $session_id)
15038
  {
15039
- // NOOP
15040
- return true;
15041
  }
15042
 
15043
  /**
15044
- * {@inheritdoc}
 
 
 
 
15045
  */
15046
- public function close()
15047
  {
15048
- // NOOP
15049
- return true;
15050
  }
15051
 
15052
  /**
15053
- * {@inheritdoc}
15054
  */
15055
- public function gc($maxlifetime)
15056
  {
15057
- // NOOP
15058
- return true;
15059
  }
15060
 
15061
  /**
15062
- * {@inheritdoc}
 
 
15063
  */
15064
- public function read($session_id)
15065
  {
15066
- if ($data = $this->client->get($session_id)) {
15067
- return $data;
15068
- }
15069
-
15070
- return '';
15071
  }
 
15072
  /**
15073
- * {@inheritdoc}
 
 
15074
  */
15075
- public function write($session_id, $session_data)
15076
  {
15077
- $this->client->setex($session_id, $this->ttl, $session_data);
 
15078
 
15079
- return true;
 
 
 
 
 
 
 
15080
  }
15081
 
15082
  /**
15083
- * {@inheritdoc}
 
 
15084
  */
15085
- public function destroy($session_id)
15086
  {
15087
- $this->client->del($session_id);
 
15088
 
15089
- return true;
 
 
 
 
 
 
 
15090
  }
15091
 
15092
  /**
15093
- * Returns the underlying client instance.
15094
  *
15095
- * @return ClientInterface
15096
  */
15097
- public function getClient()
15098
  {
15099
- return $this->client;
15100
  }
15101
 
15102
  /**
15103
- * Returns the session max lifetime value.
15104
  *
15105
- * @return int
15106
  */
15107
- public function getMaxLifeTime()
15108
  {
15109
- return $this->ttl;
15110
  }
15111
  }
15112
 
@@ -15202,7 +17148,7 @@ class Consumer implements \Iterator
15202
  /**
15203
  * Returns the last message payload retrieved from the server.
15204
  *
15205
- * @return Object
15206
  */
15207
  public function current()
15208
  {
@@ -15239,7 +17185,7 @@ class Consumer implements \Iterator
15239
  * Waits for a new message from the server generated by MONITOR and returns
15240
  * it when available.
15241
  *
15242
- * @return Object
15243
  */
15244
  private function getValue()
15245
  {
@@ -15277,230 +17223,135 @@ class Consumer implements \Iterator
15277
 
15278
  /* --------------------------------------------------------------------------- */
15279
 
15280
- namespace Predis\Replication;
15281
 
15282
- use Predis\Command\CommandInterface;
15283
- use Predis\NotSupportedException;
15284
 
15285
  /**
15286
- * Defines a strategy for master/slave replication.
 
 
 
 
 
15287
  *
15288
  * @author Daniele Alessandri <suppakilla@gmail.com>
15289
  */
15290
- class ReplicationStrategy
15291
  {
15292
- protected $disallowed;
15293
- protected $readonly;
15294
- protected $readonlySHA1;
15295
 
15296
  /**
15297
- *
 
15298
  */
15299
- public function __construct()
15300
  {
15301
- $this->disallowed = $this->getDisallowedOperations();
15302
- $this->readonly = $this->getReadOnlyOperations();
15303
- $this->readonlySHA1 = array();
 
 
 
 
15304
  }
15305
 
15306
  /**
15307
- * Returns if the specified command will perform a read-only operation
15308
- * on Redis or not.
15309
- *
15310
- * @param CommandInterface $command Command instance.
15311
- *
15312
- * @throws NotSupportedException
15313
- *
15314
- * @return bool
15315
  */
15316
- public function isReadOperation(CommandInterface $command)
15317
  {
15318
- if (isset($this->disallowed[$id = $command->getId()])) {
15319
- throw new NotSupportedException(
15320
- "The command '$id' is not allowed in replication mode."
 
 
 
 
 
 
 
15321
  );
15322
  }
 
15323
 
15324
- if (isset($this->readonly[$id])) {
15325
- if (true === $readonly = $this->readonly[$id]) {
15326
- return true;
15327
- }
15328
-
15329
- return call_user_func($readonly, $command);
15330
- }
15331
-
15332
- if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
15333
- $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
15334
-
15335
- if (isset($this->readonlySHA1[$sha1])) {
15336
- if (true === $readonly = $this->readonlySHA1[$sha1]) {
15337
- return true;
15338
- }
15339
-
15340
- return call_user_func($readonly, $command);
15341
- }
15342
- }
15343
 
15344
- return false;
 
 
 
 
 
 
15345
  }
15346
 
15347
  /**
15348
- * Returns if the specified command is not allowed for execution in a master
15349
- * / slave replication context.
15350
- *
15351
- * @param CommandInterface $command Command instance.
15352
- *
15353
- * @return bool
15354
  */
15355
- public function isDisallowedOperation(CommandInterface $command)
15356
  {
15357
- return isset($this->disallowed[$command->getId()]);
 
15358
  }
15359
 
15360
  /**
15361
- * Checks if a SORT command is a readable operation by parsing the arguments
15362
- * array of the specified commad instance.
15363
- *
15364
- * @param CommandInterface $command Command instance.
15365
- *
15366
- * @return bool
15367
  */
15368
- protected function isSortReadOnly(CommandInterface $command)
15369
  {
15370
- $arguments = $command->getArguments();
 
 
15371
 
15372
- return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
15373
  }
15374
-
15375
  /**
15376
- * Marks a command as a read-only operation.
15377
- *
15378
- * When the behavior of a command can be decided only at runtime depending
15379
- * on its arguments, a callable object can be provided to dynamically check
15380
- * if the specified command performs a read or a write operation.
15381
- *
15382
- * @param string $commandID Command ID.
15383
- * @param mixed $readonly A boolean value or a callable object.
15384
  */
15385
- public function setCommandReadOnly($commandID, $readonly = true)
15386
  {
15387
- $commandID = strtoupper($commandID);
15388
 
15389
- if ($readonly) {
15390
- $this->readonly[$commandID] = $readonly;
15391
- } else {
15392
- unset($this->readonly[$commandID]);
15393
- }
15394
  }
15395
 
15396
  /**
15397
- * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
15398
- * the behaviour of a script can be decided only at runtime depending on
15399
- * its arguments, a callable object can be provided to dynamically check
15400
- * if the passed instance of EVAL or EVALSHA performs write operations or
15401
- * not.
15402
- *
15403
- * @param string $script Body of the Lua script.
15404
- * @param mixed $readonly A boolean value or a callable object.
15405
  */
15406
- public function setScriptReadOnly($script, $readonly = true)
15407
  {
15408
- $sha1 = sha1($script);
15409
 
15410
- if ($readonly) {
15411
- $this->readonlySHA1[$sha1] = $readonly;
15412
- } else {
15413
- unset($this->readonlySHA1[$sha1]);
15414
- }
15415
  }
15416
 
15417
  /**
15418
- * Returns the default list of disallowed commands.
15419
  *
15420
- * @return array
15421
  */
15422
- protected function getDisallowedOperations()
15423
  {
15424
- return array(
15425
- 'SHUTDOWN' => true,
15426
- 'INFO' => true,
15427
- 'DBSIZE' => true,
15428
- 'LASTSAVE' => true,
15429
- 'CONFIG' => true,
15430
- 'MONITOR' => true,
15431
- 'SLAVEOF' => true,
15432
- 'SAVE' => true,
15433
- 'BGSAVE' => true,
15434
- 'BGREWRITEAOF' => true,
15435
- 'SLOWLOG' => true,
15436
- );
15437
  }
15438
 
15439
  /**
15440
- * Returns the default list of commands performing read-only operations.
15441
  *
15442
- * @return array
15443
  */
15444
- protected function getReadOnlyOperations()
15445
  {
15446
- return array(
15447
- 'EXISTS' => true,
15448
- 'TYPE' => true,
15449
- 'KEYS' => true,
15450
- 'SCAN' => true,
15451
- 'RANDOMKEY' => true,
15452
- 'TTL' => true,
15453
- 'GET' => true,
15454
- 'MGET' => true,
15455
- 'SUBSTR' => true,
15456
- 'STRLEN' => true,
15457
- 'GETRANGE' => true,
15458
- 'GETBIT' => true,
15459
- 'LLEN' => true,
15460
- 'LRANGE' => true,
15461
- 'LINDEX' => true,
15462
- 'SCARD' => true,
15463
- 'SISMEMBER' => true,
15464
- 'SINTER' => true,
15465
- 'SUNION' => true,
15466
- 'SDIFF' => true,
15467
- 'SMEMBERS' => true,
15468
- 'SSCAN' => true,
15469
- 'SRANDMEMBER' => true,
15470
- 'ZRANGE' => true,
15471
- 'ZREVRANGE' => true,
15472
- 'ZRANGEBYSCORE' => true,
15473
- 'ZREVRANGEBYSCORE' => true,
15474
- 'ZCARD' => true,
15475
- 'ZSCORE' => true,
15476
- 'ZCOUNT' => true,
15477
- 'ZRANK' => true,
15478
- 'ZREVRANK' => true,
15479
- 'ZSCAN' => true,
15480
- 'ZLEXCOUNT' => true,
15481
- 'ZRANGEBYLEX' => true,
15482
- 'ZREVRANGEBYLEX' => true,
15483
- 'HGET' => true,
15484
- 'HMGET' => true,
15485
- 'HEXISTS' => true,
15486
- 'HLEN' => true,
15487
- 'HKEYS' => true,
15488
- 'HVALS' => true,
15489
- 'HGETALL' => true,
15490
- 'HSCAN' => true,
15491
- 'HSTRLEN' => true,
15492
- 'PING' => true,
15493
- 'AUTH' => true,
15494
- 'SELECT' => true,
15495
- 'ECHO' => true,
15496
- 'QUIT' => true,
15497
- 'OBJECT' => true,
15498
- 'BITCOUNT' => true,
15499
- 'BITPOS' => true,
15500
- 'TIME' => true,
15501
- 'PFCOUNT' => true,
15502
- 'SORT' => array($this, 'isSortReadOnly'),
15503
- );
15504
  }
15505
  }
15506
 
319
  }
320
 
321
  /**
322
+ * @link http://redis.io/commands/sinter
323
  *
324
  * @author Daniele Alessandri <suppakilla@gmail.com>
325
  */
326
+ class SetIntersection extends Command
327
  {
328
  /**
329
  * {@inheritdoc}
330
  */
331
  public function getId()
332
  {
333
+ return 'SINTER';
334
  }
335
 
336
  /**
337
+ * {@inheritdoc}
 
 
338
  */
339
+ protected function filterArguments(array $arguments)
340
  {
341
+ return self::normalizeArguments($arguments);
342
  }
343
  }
344
 
345
  /**
346
+ * @link http://redis.io/commands/eval
347
  *
348
  * @author Daniele Alessandri <suppakilla@gmail.com>
349
  */
350
+ class ServerEval extends Command
351
  {
352
  /**
353
  * {@inheritdoc}
354
  */
355
  public function getId()
356
  {
357
+ return 'EVAL';
358
  }
359
 
360
  /**
361
+ * Calculates the SHA1 hash of the body of the script.
362
+ *
363
+ * @return string SHA1 hash.
364
  */
365
+ public function getScriptHash()
366
  {
367
+ return sha1($this->getArgument(0));
368
  }
369
  }
370
 
371
  /**
372
+ * @link http://redis.io/commands/rename
373
  *
374
  * @author Daniele Alessandri <suppakilla@gmail.com>
375
  */
376
+ class KeyRename extends Command
377
  {
378
  /**
379
  * {@inheritdoc}
380
  */
381
  public function getId()
382
  {
383
+ return 'RENAME';
 
 
 
 
 
 
 
 
384
  }
385
  }
386
 
387
  /**
388
+ * @link http://redis.io/commands/ttl
389
  *
390
  * @author Daniele Alessandri <suppakilla@gmail.com>
391
  */
392
+ class KeyTimeToLive extends Command
393
  {
394
  /**
395
  * {@inheritdoc}
396
  */
397
  public function getId()
398
  {
399
+ return 'TTL';
 
 
 
 
 
 
 
 
400
  }
401
  }
402
 
430
  }
431
 
432
  /**
433
+ * @link http://redis.io/commands/rpush
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  *
435
  * @author Daniele Alessandri <suppakilla@gmail.com>
436
  */
437
+ class ListPushTail extends Command
438
  {
439
  /**
440
  * {@inheritdoc}
441
  */
442
  public function getId()
443
  {
444
+ return 'RPUSH';
 
 
 
 
 
 
 
 
445
  }
 
446
 
 
 
 
 
 
 
 
447
  /**
448
  * {@inheritdoc}
449
  */
450
+ protected function filterArguments(array $arguments)
451
  {
452
+ return self::normalizeVariadic($arguments);
453
  }
454
  }
455
 
456
  /**
457
+ * @link http://redis.io/commands/subscribe
458
  *
459
  * @author Daniele Alessandri <suppakilla@gmail.com>
460
  */
461
+ class PubSubSubscribe extends Command
462
  {
463
  /**
464
  * {@inheritdoc}
465
  */
466
  public function getId()
467
  {
468
+ return 'SUBSCRIBE';
469
  }
470
 
471
  /**
478
  }
479
 
480
  /**
481
+ * @link http://redis.io/commands/unsubscribe
482
  *
483
  * @author Daniele Alessandri <suppakilla@gmail.com>
484
  */
485
+ class PubSubUnsubscribe extends Command
486
  {
487
  /**
488
  * {@inheritdoc}
489
  */
490
  public function getId()
491
  {
492
+ return 'UNSUBSCRIBE';
493
  }
494
 
495
  /**
497
  */
498
  protected function filterArguments(array $arguments)
499
  {
500
+ return self::normalizeArguments($arguments);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  }
502
  }
503
 
504
  /**
505
+ * @link http://redis.io/commands/evalsha
506
  *
507
  * @author Daniele Alessandri <suppakilla@gmail.com>
508
  */
509
+ class ServerEvalSHA extends ServerEval
510
  {
511
  /**
512
  * {@inheritdoc}
513
  */
514
  public function getId()
515
  {
516
+ return 'EVALSHA';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  }
 
518
 
 
 
 
 
 
 
 
519
  /**
520
+ * Returns the SHA1 hash of the body of the script.
521
+ *
522
+ * @return string SHA1 hash.
523
  */
524
+ public function getScriptHash()
525
  {
526
+ return $this->getArgument(0);
527
  }
528
  }
529
 
541
  {
542
  return 'EXPIRE';
543
  }
 
 
 
 
 
 
 
 
544
  }
545
 
546
  /**
643
  }
644
 
645
  /**
646
+ * @link http://redis.io/commands/setex
647
  *
648
  * @author Daniele Alessandri <suppakilla@gmail.com>
649
  */
650
+ class StringSetExpire extends Command
651
  {
652
  /**
653
  * {@inheritdoc}
654
  */
655
  public function getId()
656
  {
657
+ return 'SETEX';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
658
  }
659
  }
660
 
695
  }
696
 
697
  /**
698
+ * @link http://redis.io/commands/zrangebylex
699
  *
700
  * @author Daniele Alessandri <suppakilla@gmail.com>
701
  */
702
+ class ZSetRangeByLex extends ZSetRange
703
  {
704
  /**
705
  * {@inheritdoc}
706
  */
707
  public function getId()
708
  {
709
+ return 'ZRANGEBYLEX';
710
  }
711
 
712
  /**
713
+ * {@inheritdoc}
 
 
714
  */
715
+ protected function prepareOptions($options)
716
  {
717
+ $opts = array_change_key_case($options, CASE_UPPER);
718
+ $finalizedOpts = array();
719
+
720
+ if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
721
+ $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
722
+
723
+ $finalizedOpts[] = 'LIMIT';
724
+ $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
725
+ $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
726
+ }
727
+
728
+ return $finalizedOpts;
729
+ }
730
+
731
+ /**
732
+ * {@inheritdoc}
733
+ */
734
+ protected function withScores()
735
+ {
736
+ return false;
737
  }
738
  }
739
 
740
  /**
741
+ * @link http://redis.io/commands/zrangebyscore
742
  *
743
  * @author Daniele Alessandri <suppakilla@gmail.com>
744
  */
745
+ class ZSetRangeByScore extends ZSetRange
746
  {
747
  /**
748
  * {@inheritdoc}
749
  */
750
  public function getId()
751
  {
752
+ return 'ZRANGEBYSCORE';
753
+ }
754
+
755
+ /**
756
+ * {@inheritdoc}
757
+ */
758
+ protected function prepareOptions($options)
759
+ {
760
+ $opts = array_change_key_case($options, CASE_UPPER);
761
+ $finalizedOpts = array();
762
+
763
+ if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
764
+ $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
765
+
766
+ $finalizedOpts[] = 'LIMIT';
767
+ $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
768
+ $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
769
+ }
770
+
771
+ return array_merge($finalizedOpts, parent::prepareOptions($options));
772
+ }
773
+
774
+ /**
775
+ * {@inheritdoc}
776
+ */
777
+ protected function withScores()
778
+ {
779
+ $arguments = $this->getArguments();
780
+
781
+ for ($i = 3; $i < count($arguments); ++$i) {
782
+ switch (strtoupper($arguments[$i])) {
783
+ case 'WITHSCORES':
784
+ return true;
785
+
786
+ case 'LIMIT':
787
+ $i += 2;
788
+ break;
789
+ }
790
+ }
791
+
792
+ return false;
793
  }
794
  }
795
 
796
  /**
797
+ * @link http://redis.io/commands/expireat
798
  *
799
  * @author Daniele Alessandri <suppakilla@gmail.com>
800
  */
801
+ class KeyExpireAt extends Command
802
  {
803
  /**
804
  * {@inheritdoc}
805
  */
806
  public function getId()
807
  {
808
+ return 'EXPIREAT';
809
  }
810
  }
811
 
812
  /**
813
+ * @link http://redis.io/commands/zunionstore
814
  *
815
  * @author Daniele Alessandri <suppakilla@gmail.com>
816
  */
817
+ class ZSetUnionStore extends Command
818
  {
819
  /**
820
  * {@inheritdoc}
821
  */
822
  public function getId()
823
  {
824
+ return 'ZUNIONSTORE';
825
+ }
826
+
827
+ /**
828
+ * {@inheritdoc}
829
+ */
830
+ protected function filterArguments(array $arguments)
831
+ {
832
+ $options = array();
833
+ $argc = count($arguments);
834
+
835
+ if ($argc > 2 && is_array($arguments[$argc - 1])) {
836
+ $options = $this->prepareOptions(array_pop($arguments));
837
+ }
838
+
839
+ if (is_array($arguments[1])) {
840
+ $arguments = array_merge(
841
+ array($arguments[0], count($arguments[1])),
842
+ $arguments[1]
843
+ );
844
+ }
845
+
846
+ return array_merge($arguments, $options);
847
+ }
848
+
849
+ /**
850
+ * Returns a list of options and modifiers compatible with Redis.
851
+ *
852
+ * @param array $options List of options.
853
+ *
854
+ * @return array
855
+ */
856
+ private function prepareOptions($options)
857
+ {
858
+ $opts = array_change_key_case($options, CASE_UPPER);
859
+ $finalizedOpts = array();
860
+
861
+ if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) {
862
+ $finalizedOpts[] = 'WEIGHTS';
863
+
864
+ foreach ($opts['WEIGHTS'] as $weight) {
865
+ $finalizedOpts[] = $weight;
866
+ }
867
+ }
868
+
869
+ if (isset($opts['AGGREGATE'])) {
870
+ $finalizedOpts[] = 'AGGREGATE';
871
+ $finalizedOpts[] = $opts['AGGREGATE'];
872
+ }
873
+
874
+ return $finalizedOpts;
875
  }
876
  }
877
 
878
  /**
879
+ * @link http://redis.io/commands/georadius
880
  *
881
  * @author Daniele Alessandri <suppakilla@gmail.com>
882
  */
883
+ class GeospatialGeoRadius extends Command
884
  {
885
  /**
886
  * {@inheritdoc}
887
  */
888
  public function getId()
889
  {
890
+ return 'GEORADIUS';
891
+ }
892
+
893
+ /**
894
+ * {@inheritdoc}
895
+ */
896
+ protected function filterArguments(array $arguments)
897
+ {
898
+ if ($arguments && is_array(end($arguments))) {
899
+ $options = array_change_key_case(array_pop($arguments), CASE_UPPER);
900
+
901
+ if (isset($options['WITHCOORD']) && $options['WITHCOORD'] == true) {
902
+ $arguments[] = 'WITHCOORD';
903
+ }
904
+
905
+ if (isset($options['WITHDIST']) && $options['WITHDIST'] == true) {
906
+ $arguments[] = 'WITHDIST';
907
+ }
908
+
909
+ if (isset($options['WITHHASH']) && $options['WITHHASH'] == true) {
910
+ $arguments[] = 'WITHHASH';
911
+ }
912
+
913
+ if (isset($options['COUNT'])) {
914
+ $arguments[] = 'COUNT';
915
+ $arguments[] = $options['COUNT'];
916
+ }
917
+
918
+ if (isset($options['SORT'])) {
919
+ $arguments[] = strtoupper($options['SORT']);
920
+ }
921
+
922
+ if (isset($options['STORE'])) {
923
+ $arguments[] = 'STORE';
924
+ $arguments[] = $options['STORE'];
925
+ }
926
+
927
+ if (isset($options['STOREDIST'])) {
928
+ $arguments[] = 'STOREDIST';
929
+ $arguments[] = $options['STOREDIST'];
930
+ }
931
+ }
932
+
933
+ return $arguments;
934
  }
935
  }
936
 
937
  /**
938
+ * @link http://redis.io/commands/hlen
939
  *
940
  * @author Daniele Alessandri <suppakilla@gmail.com>
941
  */
942
+ class HashLength extends Command
943
  {
944
  /**
945
  * {@inheritdoc}
946
  */
947
  public function getId()
948
  {
949
+ return 'HLEN';
950
  }
951
+ }
952
 
953
+ /**
954
+ * @link http://redis.io/commands/decr
955
+ *
956
+ * @author Daniele Alessandri <suppakilla@gmail.com>
957
+ */
958
+ class StringDecrement extends Command
959
+ {
960
  /**
961
  * {@inheritdoc}
962
  */
963
+ public function getId()
964
  {
965
+ return 'DECR';
 
 
 
 
 
 
966
  }
967
  }
968
 
999
  }
1000
 
1001
  /**
1002
+ * @link http://redis.io/commands/bitfield
1003
  *
1004
  * @author Daniele Alessandri <suppakilla@gmail.com>
1005
  */
1006
+ class StringBitField extends Command
1007
  {
1008
  /**
1009
  * {@inheritdoc}
1010
  */
1011
  public function getId()
1012
  {
1013
+ return 'BITFIELD';
1014
  }
1015
  }
1016
 
1017
  /**
1018
+ * @link http://redis.io/commands/bitop
1019
  *
1020
  * @author Daniele Alessandri <suppakilla@gmail.com>
1021
  */
1022
+ class StringBitOp extends Command
1023
  {
1024
  /**
1025
  * {@inheritdoc}
1026
  */
1027
  public function getId()
1028
  {
1029
+ return 'BITOP';
1030
  }
1031
 
1032
  /**
1034
  */
1035
  protected function filterArguments(array $arguments)
1036
  {
1037
+ if (count($arguments) === 3 && is_array($arguments[2])) {
1038
+ list($operation, $destination) = $arguments;
1039
+ $arguments = $arguments[2];
1040
+ array_unshift($arguments, $operation, $destination);
1041
+ }
1042
+
1043
+ return $arguments;
1044
  }
1045
  }
1046
 
1047
  /**
1048
+ * @link http://redis.io/commands/bitpos
1049
  *
1050
  * @author Daniele Alessandri <suppakilla@gmail.com>
1051
  */
1052
+ class StringBitPos extends Command
1053
  {
1054
  /**
1055
  * {@inheritdoc}
1056
  */
1057
  public function getId()
1058
  {
1059
+ return 'BITPOS';
1060
  }
1061
  }
1062
 
1063
  /**
1064
+ * @link http://redis.io/commands/decrby
1065
  *
1066
  * @author Daniele Alessandri <suppakilla@gmail.com>
1067
  */
1068
+ class StringDecrementBy extends Command
1069
  {
1070
  /**
1071
  * {@inheritdoc}
1072
  */
1073
  public function getId()
1074
  {
1075
+ return 'DECRBY';
1076
  }
1077
  }
1078
 
1079
  /**
1080
+ * @link http://redis.io/commands/sunion
1081
  *
1082
  * @author Daniele Alessandri <suppakilla@gmail.com>
1083
  */
1084
+ class SetUnion extends SetIntersection
1085
  {
1086
  /**
1087
  * {@inheritdoc}
1088
  */
1089
  public function getId()
1090
  {
1091
+ return 'SUNION';
1092
  }
1093
  }
1094
 
1095
  /**
1096
+ * @link http://redis.io/commands/get
1097
  *
1098
  * @author Daniele Alessandri <suppakilla@gmail.com>
1099
  */
1100
+ class StringGet extends Command
1101
  {
1102
  /**
1103
  * {@inheritdoc}
1104
  */
1105
  public function getId()
1106
  {
1107
+ return 'GET';
1108
  }
1109
  }
1110
 
1111
  /**
1112
+ * @link http://redis.io/commands/getbit
1113
  *
1114
  * @author Daniele Alessandri <suppakilla@gmail.com>
1115
  */
1116
+ class StringGetBit extends Command
1117
  {
1118
  /**
1119
  * {@inheritdoc}
1120
  */
1121
  public function getId()
1122
  {
1123
+ return 'GETBIT';
1124
  }
1125
  }
1126
 
1127
  /**
1128
+ * @link http://redis.io/commands/mget
1129
  *
1130
  * @author Daniele Alessandri <suppakilla@gmail.com>
1131
  */
1132
+ class StringGetMultiple extends Command
1133
  {
1134
  /**
1135
  * {@inheritdoc}
1136
  */
1137
  public function getId()
1138
  {
1139
+ return 'MGET';
1140
+ }
1141
+
1142
+ /**
1143
+ * {@inheritdoc}
1144
+ */
1145
+ protected function filterArguments(array $arguments)
1146
+ {
1147
+ return self::normalizeArguments($arguments);
1148
  }
1149
  }
1150
 
1151
  /**
1152
+ * @link http://redis.io/commands/getrange
1153
  *
1154
  * @author Daniele Alessandri <suppakilla@gmail.com>
1155
  */
1156
+ class StringGetRange extends Command
1157
  {
1158
  /**
1159
  * {@inheritdoc}
1160
  */
1161
  public function getId()
1162
  {
1163
+ return 'GETRANGE';
1164
  }
1165
  }
1166
 
1167
  /**
1168
+ * @link http://redis.io/commands/getset
1169
  *
1170
  * @author Daniele Alessandri <suppakilla@gmail.com>
1171
  */
1172
+ class StringGetSet extends Command
1173
  {
1174
  /**
1175
  * {@inheritdoc}
1176
  */
1177
  public function getId()
1178
  {
1179
+ return 'GETSET';
1180
  }
1181
  }
1182
 
1183
  /**
1184
+ * @link http://redis.io/commands/incr
1185
  *
1186
  * @author Daniele Alessandri <suppakilla@gmail.com>
1187
  */
1188
+ class StringIncrement extends Command
1189
  {
1190
  /**
1191
  * {@inheritdoc}
1192
  */
1193
  public function getId()
1194
  {
1195
+ return 'INCR';
1196
  }
1197
  }
1198
 
1199
  /**
1200
+ * @link http://redis.io/commands/sunionstore
1201
  *
1202
  * @author Daniele Alessandri <suppakilla@gmail.com>
1203
  */
1204
+ class SetUnionStore extends SetIntersectionStore
1205
  {
1206
  /**
1207
  * {@inheritdoc}
1208
  */
1209
  public function getId()
1210
  {
1211
+ return 'SUNIONSTORE';
1212
  }
1213
  }
1214
 
1215
  /**
1216
+ * @link http://redis.io/commands/srem
1217
  *
1218
  * @author Daniele Alessandri <suppakilla@gmail.com>
1219
  */
1220
+ class SetRemove extends Command
1221
  {
1222
  /**
1223
  * {@inheritdoc}
1224
  */
1225
  public function getId()
1226
  {
1227
+ return 'SREM';
1228
+ }
1229
+
1230
+ /**
1231
+ * {@inheritdoc}
1232
+ */
1233
+ protected function filterArguments(array $arguments)
1234
+ {
1235
+ return self::normalizeVariadic($arguments);
1236
  }
1237
  }
1238
 
1239
  /**
1240
+ * @link http://redis.io/commands/sscan
1241
  *
1242
  * @author Daniele Alessandri <suppakilla@gmail.com>
1243
  */
1244
+ class SetScan extends Command
1245
  {
1246
  /**
1247
  * {@inheritdoc}
1248
  */
1249
  public function getId()
1250
  {
1251
+ return 'SSCAN';
1252
+ }
1253
+
1254
+ /**
1255
+ * {@inheritdoc}
1256
+ */
1257
+ protected function filterArguments(array $arguments)
1258
+ {
1259
+ if (count($arguments) === 3 && is_array($arguments[2])) {
1260
+ $options = $this->prepareOptions(array_pop($arguments));
1261
+ $arguments = array_merge($arguments, $options);
1262
+ }
1263
+
1264
+ return $arguments;
1265
+ }
1266
+
1267
+ /**
1268
+ * Returns a list of options and modifiers compatible with Redis.
1269
+ *
1270
+ * @param array $options List of options.
1271
+ *
1272
+ * @return array
1273
+ */
1274
+ protected function prepareOptions($options)
1275
+ {
1276
+ $options = array_change_key_case($options, CASE_UPPER);
1277
+ $normalized = array();
1278
+
1279
+ if (!empty($options['MATCH'])) {
1280
+ $normalized[] = 'MATCH';
1281
+ $normalized[] = $options['MATCH'];
1282
+ }
1283
+
1284
+ if (!empty($options['COUNT'])) {
1285
+ $normalized[] = 'COUNT';
1286
+ $normalized[] = $options['COUNT'];
1287
+ }
1288
+
1289
+ return $normalized;
1290
  }
1291
  }
1292
 
1293
  /**
1294
+ * @link http://redis.io/commands/sdiffstore
1295
  *
1296
  * @author Daniele Alessandri <suppakilla@gmail.com>
1297
  */
1298
+ class SetDifferenceStore extends SetIntersectionStore
1299
  {
1300
  /**
1301
  * {@inheritdoc}
1302
  */
1303
  public function getId()
1304
  {
1305
+ return 'SDIFFSTORE';
1306
  }
1307
  }
1308
 
1309
  /**
1310
+ * @link http://redis.io/commands/slaveof
1311
  *
1312
  * @author Daniele Alessandri <suppakilla@gmail.com>
1313
  */
1314
+ class ServerSlaveOf extends Command
1315
  {
1316
  /**
1317
  * {@inheritdoc}
1318
  */
1319
  public function getId()
1320
  {
1321
+ return 'SLAVEOF';
1322
  }
1323
 
1324
  /**
1326
  */
1327
  protected function filterArguments(array $arguments)
1328
  {
1329
+ if (count($arguments) === 0 || $arguments[0] === 'NO ONE') {
1330
+ return array('NO', 'ONE');
1331
+ }
1332
+
1333
+ return $arguments;
1334
  }
1335
  }
1336
 
1390
  }
1391
 
1392
  /**
1393
+ * @link http://redis.io/commands/sadd
1394
  *
1395
  * @author Daniele Alessandri <suppakilla@gmail.com>
1396
  */
1397
+ class SetAdd extends Command
1398
  {
1399
  /**
1400
  * {@inheritdoc}
1401
  */
1402
  public function getId()
1403
  {
1404
+ return 'SADD';
1405
  }
1406
 
1407
  /**
1408
  * {@inheritdoc}
1409
  */
1410
+ protected function filterArguments(array $arguments)
1411
  {
1412
+ return self::normalizeVariadic($arguments);
1413
  }
1414
  }
1415
 
1416
  /**
1417
+ * @link http://redis.io/commands/scard
1418
  *
1419
  * @author Daniele Alessandri <suppakilla@gmail.com>
1420
  */
1421
+ class SetCardinality extends Command
1422
  {
1423
  /**
1424
  * {@inheritdoc}
1425
  */
1426
  public function getId()
1427
  {
1428
+ return 'SCARD';
1429
  }
1430
+ }
1431
 
1432
+ /**
1433
+ * @link http://redis.io/commands/sdiff
1434
+ *
1435
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1436
+ */
1437
+ class SetDifference extends SetIntersection
1438
+ {
1439
  /**
1440
  * {@inheritdoc}
1441
  */
1442
+ public function getId()
1443
  {
1444
+ return 'SDIFF';
1445
  }
1446
  }
1447
 
1448
  /**
1449
+ * @link http://redis.io/commands/geodist
1450
  *
1451
  * @author Daniele Alessandri <suppakilla@gmail.com>
1452
  */
1453
+ class GeospatialGeoDist extends Command
1454
  {
1455
  /**
1456
  * {@inheritdoc}
1457
  */
1458
  public function getId()
1459
  {
1460
+ return 'GEODIST';
1461
  }
1462
+ }
1463
 
1464
+ /**
1465
+ * @link http://redis.io/commands/incrbyfloat
1466
+ *
1467
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1468
+ */
1469
+ class StringIncrementByFloat extends Command
1470
+ {
1471
  /**
1472
  * {@inheritdoc}
1473
  */
1474
+ public function getId()
1475
  {
1476
+ return 'INCRBYFLOAT';
1477
  }
1478
  }
1479
 
1480
  /**
1481
+ * @link http://redis.io/commands/geoadd
1482
  *
1483
  * @author Daniele Alessandri <suppakilla@gmail.com>
1484
  */
1485
+ class GeospatialGeoAdd extends Command
1486
  {
1487
  /**
1488
  * {@inheritdoc}
1489
  */
1490
  public function getId()
1491
  {
1492
+ return 'GEOADD';
1493
  }
1494
 
1495
  /**
1497
  */
1498
  protected function filterArguments(array $arguments)
1499
  {
1500
+ if (count($arguments) === 2 && is_array($arguments[1])) {
1501
+ foreach (array_pop($arguments) as $item) {
1502
+ $arguments = array_merge($arguments, $item);
1503
+ }
1504
  }
1505
 
1506
  return $arguments;
1507
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1508
  }
1509
 
1510
  /**
1511
+ * @link http://redis.io/commands/sismember
1512
  *
1513
  * @author Daniele Alessandri <suppakilla@gmail.com>
1514
  */
1515
+ class SetIsMember extends Command
1516
  {
1517
  /**
1518
  * {@inheritdoc}
1519
  */
1520
  public function getId()
1521
  {
1522
+ return 'SISMEMBER';
1523
  }
1524
  }
1525
 
1553
  {
1554
  return 'SMOVE';
1555
  }
 
 
 
 
 
 
 
 
1556
  }
1557
 
1558
  /**
1559
+ * @link http://redis.io/commands/spop
1560
  *
1561
  * @author Daniele Alessandri <suppakilla@gmail.com>
1562
  */
1563
+ class SetPop extends Command
1564
  {
1565
  /**
1566
  * {@inheritdoc}
1567
  */
1568
  public function getId()
1569
  {
1570
+ return 'SPOP';
1571
  }
1572
  }
1573
 
1574
  /**
1575
+ * @link http://redis.io/commands/srandmember
1576
  *
1577
  * @author Daniele Alessandri <suppakilla@gmail.com>
1578
  */
1579
+ class SetRandomMember extends Command
1580
  {
1581
  /**
1582
  * {@inheritdoc}
1583
  */
1584
  public function getId()
1585
  {
1586
+ return 'SRANDMEMBER';
 
 
 
 
 
 
 
 
1587
  }
1588
  }
1589
 
1590
  /**
1591
+ * @link http://redis.io/commands/incrby
1592
  *
1593
  * @author Daniele Alessandri <suppakilla@gmail.com>
1594
  */
1595
+ class StringIncrementBy extends Command
1596
  {
1597
  /**
1598
  * {@inheritdoc}
1599
  */
1600
  public function getId()
1601
  {
1602
+ return 'INCRBY';
 
 
 
 
 
 
 
 
1603
  }
1604
  }
1605
 
1606
  /**
1607
+ * @link http://redis.io/commands/psetex
1608
  *
1609
  * @author Daniele Alessandri <suppakilla@gmail.com>
1610
  */
1611
+ class StringPreciseSetExpire extends StringSetExpire
1612
  {
1613
  /**
1614
  * {@inheritdoc}
1615
  */
1616
  public function getId()
1617
  {
1618
+ return 'PSETEX';
1619
  }
1620
  }
1621
 
1622
  /**
1623
+ * @link http://redis.io/topics/sentinel
1624
  *
1625
  * @author Daniele Alessandri <suppakilla@gmail.com>
1626
  */
1627
+ class ServerSentinel extends Command
1628
  {
1629
  /**
1630
  * {@inheritdoc}
1631
  */
1632
  public function getId()
1633
  {
1634
+ return 'SENTINEL';
1635
  }
 
1636
 
 
 
 
 
 
 
 
1637
  /**
1638
  * {@inheritdoc}
1639
  */
1640
+ public function parseResponse($data)
1641
  {
1642
+ switch (strtolower($this->getArgument(0))) {
1643
+ case 'masters':
1644
+ case 'slaves':
1645
+ return self::processMastersOrSlaves($data);
1646
+
1647
+ default:
1648
+ return $data;
1649
+ }
1650
+ }
1651
+
1652
+ /**
1653
+ * Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES.
1654
+ *
1655
+ * @param array $servers List of Redis servers.
1656
+ *
1657
+ * @return array
1658
+ */
1659
+ protected static function processMastersOrSlaves(array $servers)
1660
+ {
1661
+ foreach ($servers as $idx => $node) {
1662
+ $processed = array();
1663
+ $count = count($node);
1664
+
1665
+ for ($i = 0; $i < $count; ++$i) {
1666
+ $processed[$node[$i]] = $node[++$i];
1667
+ }
1668
+
1669
+ $servers[$idx] = $processed;
1670
+ }
1671
+
1672
+ return $servers;
1673
+ }
1674
+ }
1675
+
1676
+ /**
1677
+ * @link http://redis.io/commands/zremrangebyrank
1678
+ *
1679
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1680
+ */
1681
+ class ZSetRemoveRangeByRank extends Command
1682
+ {
1683
+ /**
1684
+ * {@inheritdoc}
1685
+ */
1686
+ public function getId()
1687
+ {
1688
+ return 'ZREMRANGEBYRANK';
1689
+ }
1690
+ }
1691
+
1692
+ /**
1693
+ * @link http://redis.io/commands/ping
1694
+ *
1695
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1696
+ */
1697
+ class ConnectionPing extends Command
1698
+ {
1699
+ /**
1700
+ * {@inheritdoc}
1701
+ */
1702
+ public function getId()
1703
+ {
1704
+ return 'PING';
1705
  }
1706
  }
1707
 
1722
  }
1723
 
1724
  /**
1725
+ * @link http://redis.io/commands/auth
1726
  *
1727
  * @author Daniele Alessandri <suppakilla@gmail.com>
1728
  */
1729
+ class ConnectionAuth extends Command
1730
  {
1731
  /**
1732
  * {@inheritdoc}
1733
  */
1734
  public function getId()
1735
  {
1736
+ return 'AUTH';
1737
  }
1738
  }
1739
 
1740
  /**
1741
+ * @link http://redis.io/commands/zrank
1742
  *
1743
  * @author Daniele Alessandri <suppakilla@gmail.com>
1744
  */
1745
+ class ZSetRank extends Command
1746
  {
1747
  /**
1748
  * {@inheritdoc}
1749
  */
1750
  public function getId()
1751
  {
1752
+ return 'ZRANK';
1753
+ }
1754
+ }
1755
+
1756
+ /**
1757
+ * @link http://redis.io/commands/zrem
1758
+ *
1759
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1760
+ */
1761
+ class ZSetRemove extends Command
1762
+ {
1763
+ /**
1764
+ * {@inheritdoc}
1765
+ */
1766
+ public function getId()
1767
+ {
1768
+ return 'ZREM';
1769
+ }
1770
+
1771
+ /**
1772
+ * {@inheritdoc}
1773
+ */
1774
+ protected function filterArguments(array $arguments)
1775
+ {
1776
+ return self::normalizeVariadic($arguments);
1777
+ }
1778
+ }
1779
+
1780
+ /**
1781
+ * @link http://redis.io/commands/zremrangebylex
1782
+ *
1783
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1784
+ */
1785
+ class ZSetRemoveRangeByLex extends Command
1786
+ {
1787
+ /**
1788
+ * {@inheritdoc}
1789
+ */
1790
+ public function getId()
1791
+ {
1792
+ return 'ZREMRANGEBYLEX';
1793
  }
1794
  }
1795
 
1809
  }
1810
  }
1811
 
1812
+ /**
1813
+ * @link http://redis.io/commands/zinterstore
1814
+ *
1815
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1816
+ */
1817
+ class ZSetIntersectionStore extends ZSetUnionStore
1818
+ {
1819
+ /**
1820
+ * {@inheritdoc}
1821
+ */
1822
+ public function getId()
1823
+ {
1824
+ return 'ZINTERSTORE';
1825
+ }
1826
+ }
1827
+
1828
  /**
1829
  * @link http://redis.io/commands/zrevrange
1830
  *
1842
  }
1843
 
1844
  /**
1845
+ * @link http://redis.io/commands/zrevrangebylex
1846
  *
1847
  * @author Daniele Alessandri <suppakilla@gmail.com>
1848
  */
1849
+ class ZSetReverseRangeByLex extends ZSetRangeByLex
1850
  {
1851
  /**
1852
  * {@inheritdoc}
1853
  */
1854
  public function getId()
1855
  {
1856
+ return 'ZREVRANGEBYLEX';
1857
  }
1858
  }
1859
 
1860
  /**
1861
+ * @link http://redis.io/commands/zrevrangebyscore
1862
  *
1863
  * @author Daniele Alessandri <suppakilla@gmail.com>
1864
  */
1865
+ class ZSetReverseRangeByScore extends ZSetRangeByScore
1866
  {
1867
  /**
1868
  * {@inheritdoc}
1869
  */
1870
  public function getId()
1871
  {
1872
+ return 'ZREVRANGEBYSCORE';
1873
+ }
1874
+ }
1875
+
1876
+ /**
1877
+ * @link http://redis.io/commands/zrevrank
1878
+ *
1879
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1880
+ */
1881
+ class ZSetReverseRank extends Command
1882
+ {
1883
+ /**
1884
+ * {@inheritdoc}
1885
+ */
1886
+ public function getId()
1887
+ {
1888
+ return 'ZREVRANK';
1889
  }
1890
  }
1891
 
1963
  }
1964
 
1965
  /**
1966
+ * @link http://redis.io/commands/zscore
1967
  *
1968
  * @author Daniele Alessandri <suppakilla@gmail.com>
1969
  */
1970
+ class ZSetScore extends Command
1971
  {
1972
  /**
1973
  * {@inheritdoc}
1974
  */
1975
  public function getId()
1976
  {
1977
+ return 'ZSCORE';
1978
  }
1979
  }
1980
 
1981
+ /**
1982
+ * @link http://redis.io/commands/zlexcount
1983
+ *
1984
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1985
+ */
1986
+ class ZSetLexCount extends Command
1987
  {
1988
  /**
1989
  * {@inheritdoc}
1990
  */
1991
  public function getId()
1992
  {
1993
+ return 'ZLEXCOUNT';
1994
  }
1995
  }
1996
 
1997
  /**
1998
+ * @link http://redis.io/commands/zincrby
1999
  *
2000
  * @author Daniele Alessandri <suppakilla@gmail.com>
2001
  */
2002
+ class ZSetIncrementBy extends Command
2003
  {
2004
  /**
2005
  * {@inheritdoc}
2006
  */
2007
  public function getId()
2008
  {
2009
+ return 'ZINCRBY';
2010
  }
2011
  }
2012
 
2013
  /**
2014
+ * @link http://redis.io/commands/set
2015
  *
2016
  * @author Daniele Alessandri <suppakilla@gmail.com>
2017
  */
2018
+ class StringSet extends Command
2019
  {
2020
  /**
2021
  * {@inheritdoc}
2022
  */
2023
  public function getId()
2024
  {
2025
+ return 'SET';
2026
  }
2027
  }
2028
 
2029
  /**
2030
+ * @link http://redis.io/commands/strlen
2031
  *
2032
  * @author Daniele Alessandri <suppakilla@gmail.com>
2033
  */
2034
+ class StringStrlen extends Command
2035
  {
2036
  /**
2037
  * {@inheritdoc}
2038
  */
2039
  public function getId()
2040
  {
2041
+ return 'STRLEN';
2042
  }
2043
  }
2044
 
2045
  /**
2046
+ * @link http://redis.io/commands/setbit
2047
  *
2048
  * @author Daniele Alessandri <suppakilla@gmail.com>
2049
  */
2050
+ class StringSetBit extends Command
2051
  {
2052
  /**
2053
  * {@inheritdoc}
2054
  */
2055
  public function getId()
2056
  {
2057
+ return 'SETBIT';
2058
  }
2059
  }
2060
 
2061
  /**
2062
+ * @link http://redis.io/commands/select
2063
  *
2064
  * @author Daniele Alessandri <suppakilla@gmail.com>
2065
  */
2066
+ class ConnectionSelect extends Command
2067
  {
2068
  /**
2069
  * {@inheritdoc}
2070
  */
2071
  public function getId()
2072
  {
2073
+ return 'SELECT';
2074
  }
2075
  }
2076
 
2077
  /**
2078
+ * @link http://redis.io/commands/quit
2079
  *
2080
  * @author Daniele Alessandri <suppakilla@gmail.com>
2081
  */
2082
+ class ConnectionQuit extends Command
2083
  {
2084
  /**
2085
  * {@inheritdoc}
2086
  */
2087
  public function getId()
2088
  {
2089
+ return 'QUIT';
2090
  }
2091
  }
2092
 
2093
  /**
2094
+ * @link http://redis.io/commands/msetnx
2095
  *
2096
  * @author Daniele Alessandri <suppakilla@gmail.com>
2097
  */
2098
+ class StringSetMultiplePreserve extends StringSetMultiple
2099
  {
2100
  /**
2101
  * {@inheritdoc}
2102
  */
2103
  public function getId()
2104
  {
2105
+ return 'MSETNX';
2106
  }
2107
  }
2108
 
2120
  {
2121
  return 'SETNX';
2122
  }
2123
+ }
2124
 
2125
+ /**
2126
+ * @link http://redis.io/commands/setrange
2127
+ *
2128
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2129
+ */
2130
+ class StringSetRange extends Command
2131
+ {
2132
  /**
2133
  * {@inheritdoc}
2134
  */
2135
+ public function getId()
2136
  {
2137
+ return 'SETRANGE';
2138
  }
2139
  }
2140
 
2141
  /**
2142
+ * @link http://redis.io/commands/substr
2143
  *
2144
  * @author Daniele Alessandri <suppakilla@gmail.com>
2145
  */
2146
+ class StringSubstr extends Command
2147
  {
2148
  /**
2149
  * {@inheritdoc}
2150
  */
2151
  public function getId()
2152
  {
2153
+ return 'SUBSTR';
2154
  }
2155
  }
2156
 
2157
  /**
2158
+ * @link http://redis.io/commands/zcount
2159
  *
2160
  * @author Daniele Alessandri <suppakilla@gmail.com>
2161
  */
2162
+ class ZSetCount extends Command
2163
  {
2164
  /**
2165
  * {@inheritdoc}
2166
  */
2167
  public function getId()
2168
  {
2169
+ return 'ZCOUNT';
2170
  }
2171
+ }
2172
 
2173
+ /**
2174
+ * @link http://redis.io/commands/discard
2175
+ *
2176
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2177
+ */
2178
+ class TransactionDiscard extends Command
2179
+ {
2180
  /**
2181
  * {@inheritdoc}
2182
  */
2183
+ public function getId()
2184
  {
2185
+ return 'DISCARD';
2186
  }
2187
  }
2188
 
2219
  }
2220
 
2221
  /**
2222
+ * @link http://redis.io/commands/unwatch
2223
  *
2224
  * @author Daniele Alessandri <suppakilla@gmail.com>
2225
  */
2226
+ class TransactionUnwatch extends Command
2227
  {
2228
  /**
2229
  * {@inheritdoc}
2230
  */
2231
  public function getId()
2232
  {
2233
+ return 'UNWATCH';
2234
  }
2235
  }
2236
 
2237
  /**
2238
+ * @link http://redis.io/commands/watch
2239
  *
2240
  * @author Daniele Alessandri <suppakilla@gmail.com>
2241
  */
2242
+ class TransactionWatch extends Command
2243
  {
2244
  /**
2245
  * {@inheritdoc}
2246
  */
2247
  public function getId()
2248
  {
2249
+ return 'WATCH';
2250
  }
 
2251
 
 
 
 
 
 
 
 
2252
  /**
2253
  * {@inheritdoc}
2254
  */
2255
+ protected function filterArguments(array $arguments)
2256
  {
2257
+ if (isset($arguments[0]) && is_array($arguments[0])) {
2258
+ return $arguments[0];
2259
+ }
2260
+
2261
+ return $arguments;
2262
  }
2263
  }
2264
 
2294
  }
2295
 
2296
  /**
2297
+ * @link http://redis.io/commands/zcard
2298
  *
2299
  * @author Daniele Alessandri <suppakilla@gmail.com>
2300
  */
2301
+ class ZSetCardinality extends Command
2302
  {
2303
  /**
2304
  * {@inheritdoc}
2305
  */
2306
  public function getId()
2307
  {
2308
+ return 'ZCARD';
2309
  }
2310
  }
2311
 
2312
  /**
2313
+ * @link http://redis.io/commands/shutdown
2314
  *
2315
  * @author Daniele Alessandri <suppakilla@gmail.com>
2316
  */
2317
+ class ServerShutdown extends Command
2318
  {
2319
  /**
2320
  * {@inheritdoc}
2321
  */
2322
  public function getId()
2323
  {
2324
+ return 'SHUTDOWN';
2325
+ }
2326
+ }
2327
+
2328
+ /**
2329
+ * @link http://redis.io/commands/script
2330
+ *
2331
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2332
+ */
2333
+ class ServerScript extends Command
2334
+ {
2335
+ /**
2336
+ * {@inheritdoc}
2337
+ */
2338
+ public function getId()
2339
+ {
2340
+ return 'SCRIPT';
2341
+ }
2342
+ }
2343
+
2344
+ /**
2345
+ * @link http://redis.io/commands/hscan
2346
+ *
2347
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2348
+ */
2349
+ class HashScan extends Command
2350
+ {
2351
+ /**
2352
+ * {@inheritdoc}
2353
+ */
2354
+ public function getId()
2355
+ {
2356
+ return 'HSCAN';
2357
  }
2358
 
2359
  /**
2361
  */
2362
  protected function filterArguments(array $arguments)
2363
  {
2364
+ if (count($arguments) === 3 && is_array($arguments[2])) {
2365
+ $options = $this->prepareOptions(array_pop($arguments));
2366
+ $arguments = array_merge($arguments, $options);
2367
  }
2368
 
2369
  return $arguments;
2370
  }
2371
+
2372
+ /**
2373
+ * Returns a list of options and modifiers compatible with Redis.
2374
+ *
2375
+ * @param array $options List of options.
2376
+ *
2377
+ * @return array
2378
+ */
2379
+ protected function prepareOptions($options)
2380
+ {
2381
+ $options = array_change_key_case($options, CASE_UPPER);
2382
+ $normalized = array();
2383
+
2384
+ if (!empty($options['MATCH'])) {
2385
+ $normalized[] = 'MATCH';
2386
+ $normalized[] = $options['MATCH'];
2387
+ }
2388
+
2389
+ if (!empty($options['COUNT'])) {
2390
+ $normalized[] = 'COUNT';
2391
+ $normalized[] = $options['COUNT'];
2392
+ }
2393
+
2394
+ return $normalized;
2395
+ }
2396
+
2397
+ /**
2398
+ * {@inheritdoc}
2399
+ */
2400
+ public function parseResponse($data)
2401
+ {
2402
+ if (is_array($data)) {
2403
+ $fields = $data[1];
2404
+ $result = array();
2405
+
2406
+ for ($i = 0; $i < count($fields); ++$i) {
2407
+ $result[$fields[$i]] = $fields[++$i];
2408
+ }
2409
+
2410
+ $data[1] = $result;
2411
+ }
2412
+
2413
+ return $data;
2414
+ }
2415
  }
2416
 
2417
  /**
2418
+ * @link http://redis.io/commands/scan
2419
  *
2420
  * @author Daniele Alessandri <suppakilla@gmail.com>
2421
  */
2422
+ class KeyScan extends Command
2423
  {
2424
  /**
2425
  * {@inheritdoc}
2426
  */
2427
  public function getId()
2428
  {
2429
+ return 'SCAN';
2430
  }
2431
 
2432
  /**
2434
  */
2435
  protected function filterArguments(array $arguments)
2436
  {
2437
+ if (count($arguments) === 2 && is_array($arguments[1])) {
2438
+ $options = $this->prepareOptions(array_pop($arguments));
2439
+ $arguments = array_merge($arguments, $options);
2440
  }
2441
 
2442
  return $arguments;
2443
  }
2444
+
2445
+ /**
2446
+ * Returns a list of options and modifiers compatible with Redis.
2447
+ *
2448
+ * @param array $options List of options.
2449
+ *
2450
+ * @return array
2451
+ */
2452
+ protected function prepareOptions($options)
2453
+ {
2454
+ $options = array_change_key_case($options, CASE_UPPER);
2455
+ $normalized = array();
2456
+
2457
+ if (!empty($options['MATCH'])) {
2458
+ $normalized[] = 'MATCH';
2459
+ $normalized[] = $options['MATCH'];
2460
+ }
2461
+
2462
+ if (!empty($options['COUNT'])) {
2463
+ $normalized[] = 'COUNT';
2464
+ $normalized[] = $options['COUNT'];
2465
+ }
2466
+
2467
+ return $normalized;
2468
+ }
2469
  }
2470
 
2471
  /**
2472
+ * @link http://redis.io/commands/pexpireat
2473
  *
2474
  * @author Daniele Alessandri <suppakilla@gmail.com>
2475
  */
2476
+ class KeyPreciseExpireAt extends KeyExpireAt
2477
  {
2478
  /**
2479
  * {@inheritdoc}
2480
  */
2481
  public function getId()
2482
  {
2483
+ return 'PEXPIREAT';
2484
  }
2485
  }
2486
 
2487
  /**
2488
+ * @link http://redis.io/commands/pttl
2489
  *
2490
  * @author Daniele Alessandri <suppakilla@gmail.com>
2491
  */
2492
+ class KeyPreciseTimeToLive extends KeyTimeToLive
2493
  {
2494
  /**
2495
  * {@inheritdoc}
2496
  */
2497
  public function getId()
2498
  {
2499
+ return 'PTTL';
2500
+ }
2501
+ }
2502
+
2503
+ /**
2504
+ * @link http://redis.io/commands/randomkey
2505
+ *
2506
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2507
+ */
2508
+ class KeyRandom extends Command
2509
+ {
2510
+ /**
2511
+ * {@inheritdoc}
2512
+ */
2513
+ public function getId()
2514
+ {
2515
+ return 'RANDOMKEY';
2516
  }
2517
 
2518
  /**
2520
  */
2521
  public function parseResponse($data)
2522
  {
2523
+ return $data !== '' ? $data : null;
2524
  }
2525
  }
2526
 
2527
  /**
2528
+ * @link http://redis.io/commands/hincrby
2529
  *
2530
  * @author Daniele Alessandri <suppakilla@gmail.com>
2531
  */
2532
+ class HashIncrementBy extends Command
2533
  {
2534
  /**
2535
  * {@inheritdoc}
2536
  */
2537
  public function getId()
2538
  {
2539
+ return 'HINCRBY';
2540
  }
2541
  }
2542
 
2543
  /**
2544
+ * @link http://redis.io/commands/renamenx
2545
  *
2546
  * @author Daniele Alessandri <suppakilla@gmail.com>
2547
  */
2548
+ class KeyRenamePreserve extends KeyRename
2549
  {
2550
  /**
2551
  * {@inheritdoc}
2552
  */
2553
  public function getId()
2554
  {
2555
+ return 'RENAMENX';
2556
+ }
2557
+ }
2558
+
2559
+ /**
2560
+ * @link http://redis.io/commands/restore
2561
+ *
2562
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2563
+ */
2564
+ class KeyRestore extends Command
2565
+ {
2566
+ /**
2567
+ * {@inheritdoc}
2568
+ */
2569
+ public function getId()
2570
+ {
2571
+ return 'RESTORE';
2572
  }
2573
  }
2574
 
2644
  }
2645
 
2646
  /**
2647
+ * @link http://redis.io/commands/persist
2648
  *
2649
  * @author Daniele Alessandri <suppakilla@gmail.com>
2650
  */
2651
+ class KeyPersist extends Command
2652
  {
2653
  /**
2654
  * {@inheritdoc}
2655
  */
2656
  public function getId()
2657
  {
2658
+ return 'PERSIST';
2659
  }
2660
+ }
2661
 
2662
+ /**
2663
+ * @link http://redis.io/commands/hmget
2664
+ *
2665
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2666
+ */
2667
+ class HashGetMultiple extends Command
2668
+ {
2669
  /**
2670
  * {@inheritdoc}
2671
  */
2672
+ public function getId()
2673
  {
2674
+ return 'HMGET';
 
 
 
 
 
2675
  }
2676
 
2677
  /**
2678
+ * {@inheritdoc}
 
 
 
 
2679
  */
2680
+ protected function filterArguments(array $arguments)
2681
  {
2682
+ return self::normalizeVariadic($arguments);
 
 
 
 
 
 
 
 
 
 
 
 
 
2683
  }
2684
  }
2685
 
2686
  /**
2687
+ * @link http://redis.io/commands/type
2688
  *
2689
  * @author Daniele Alessandri <suppakilla@gmail.com>
2690
  */
2691
+ class KeyType extends Command
2692
  {
2693
  /**
2694
  * {@inheritdoc}
2695
  */
2696
  public function getId()
2697
  {
2698
+ return 'TYPE';
 
 
 
 
 
 
 
 
2699
  }
2700
  }
2701
 
2702
  /**
2703
+ * @link http://redis.io/commands/lindex
2704
  *
2705
  * @author Daniele Alessandri <suppakilla@gmail.com>
2706
  */
2707
+ class ListIndex extends Command
2708
  {
2709
  /**
2710
  * {@inheritdoc}
2711
  */
2712
  public function getId()
2713
  {
2714
+ return 'LINDEX';
2715
  }
2716
  }
2717
 
2748
  }
2749
 
2750
  /**
2751
+ * @link http://redis.io/commands/lpop
2752
  *
2753
  * @author Daniele Alessandri <suppakilla@gmail.com>
2754
  */
2755
+ class ListPopFirst extends Command
2756
  {
2757
  /**
2758
  * {@inheritdoc}
2759
  */
2760
  public function getId()
2761
  {
2762
+ return 'LPOP';
2763
  }
2764
  }
2765
 
2766
  /**
2767
+ * @link http://redis.io/commands/pexpire
2768
  *
2769
  * @author Daniele Alessandri <suppakilla@gmail.com>
2770
  */
2771
+ class KeyPreciseExpire extends KeyExpire
2772
  {
2773
  /**
2774
  * {@inheritdoc}
2775
  */
2776
  public function getId()
2777
  {
2778
+ return 'PEXPIRE';
2779
  }
2780
  }
2781
 
2782
  /**
2783
+ * @link http://redis.io/commands/move
2784
  *
2785
  * @author Daniele Alessandri <suppakilla@gmail.com>
2786
  */
2787
+ class KeyMove extends Command
2788
  {
2789
  /**
2790
  * {@inheritdoc}
2791
  */
2792
  public function getId()
2793
  {
2794
+ return 'MOVE';
2795
  }
2796
  }
2797
 
2798
  /**
2799
+ * @link http://redis.io/commands/rpop
2800
  *
2801
  * @author Daniele Alessandri <suppakilla@gmail.com>
2802
  */
2803
+ class ListPopLast extends Command
2804
  {
2805
  /**
2806
  * {@inheritdoc}
2807
  */
2808
  public function getId()
2809
  {
2810
+ return 'RPOP';
2811
  }
2812
  }
2813
 
2814
  /**
2815
+ * @link http://redis.io/commands/pfcount
2816
  *
2817
  * @author Daniele Alessandri <suppakilla@gmail.com>
2818
  */
2819
+ class HyperLogLogCount extends Command
2820
  {
2821
  /**
2822
  * {@inheritdoc}
2823
  */
2824
  public function getId()
2825
  {
2826
+ return 'PFCOUNT';
2827
  }
 
2828
 
 
 
 
 
 
 
 
2829
  /**
2830
  * {@inheritdoc}
2831
  */
2832
+ protected function filterArguments(array $arguments)
2833
  {
2834
+ return self::normalizeArguments($arguments);
2835
  }
2836
  }
2837
 
2838
  /**
2839
+ * @link http://redis.io/commands/hset
2840
  *
2841
  * @author Daniele Alessandri <suppakilla@gmail.com>
2842
  */
2843
+ class HashSet extends Command
2844
  {
2845
  /**
2846
  * {@inheritdoc}
2847
  */
2848
  public function getId()
2849
  {
2850
+ return 'HSET';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2851
  }
2852
  }
2853
 
2888
  }
2889
 
2890
  /**
2891
+ * @link http://redis.io/commands/hsetnx
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2892
  *
2893
  * @author Daniele Alessandri <suppakilla@gmail.com>
2894
  */
2895
+ class HashSetPreserve extends Command
2896
  {
2897
  /**
2898
  * {@inheritdoc}
2899
  */
2900
  public function getId()
2901
  {
2902
+ return 'HSETNX';
2903
  }
2904
  }
2905
 
2906
  /**
2907
+ * @link http://redis.io/commands/hstrlen
2908
  *
2909
  * @author Daniele Alessandri <suppakilla@gmail.com>
2910
  */
2911
+ class HashStringLength extends Command
2912
  {
2913
  /**
2914
  * {@inheritdoc}
2915
  */
2916
  public function getId()
2917
  {
2918
+ return 'HSTRLEN';
 
 
 
 
 
 
 
 
2919
  }
2920
  }
2921
 
2922
  /**
2923
+ * @link http://redis.io/commands/hvals
2924
  *
2925
  * @author Daniele Alessandri <suppakilla@gmail.com>
2926
  */
2927
+ class HashValues extends Command
2928
  {
2929
  /**
2930
  * {@inheritdoc}
2931
  */
2932
  public function getId()
2933
  {
2934
+ return 'HVALS';
2935
  }
2936
  }
2937
 
2938
  /**
2939
+ * @link http://redis.io/commands/pfadd
2940
  *
2941
  * @author Daniele Alessandri <suppakilla@gmail.com>
2942
  */
2943
+ class HyperLogLogAdd extends Command
2944
  {
2945
  /**
2946
  * {@inheritdoc}
2947
  */
2948
  public function getId()
2949
  {
2950
+ return 'PFADD';
2951
  }
2952
 
2953
  /**
2955
  */
2956
  protected function filterArguments(array $arguments)
2957
  {
2958
+ return self::normalizeVariadic($arguments);
2959
  }
2960
  }
2961
 
2984
  }
2985
 
2986
  /**
2987
+ * @link http://redis.io/commands/migrate
2988
  *
2989
  * @author Daniele Alessandri <suppakilla@gmail.com>
2990
  */
2991
+ class KeyMigrate extends Command
2992
  {
2993
  /**
2994
  * {@inheritdoc}
2995
  */
2996
  public function getId()
2997
  {
2998
+ return 'MIGRATE';
2999
  }
3000
 
3001
  /**
3003
  */
3004
  protected function filterArguments(array $arguments)
3005
  {
3006
+ if (is_array(end($arguments))) {
3007
+ foreach (array_pop($arguments) as $modifier => $value) {
3008
+ $modifier = strtoupper($modifier);
3009
 
3010
+ if ($modifier === 'COPY' && $value == true) {
3011
+ $arguments[] = $modifier;
3012
+ }
3013
+
3014
+ if ($modifier === 'REPLACE' && $value == true) {
3015
+ $arguments[] = $modifier;
3016
+ }
3017
+ }
3018
+ }
3019
+
3020
+ return $arguments;
3021
  }
3022
  }
3023
 
3024
  /**
3025
+ * @link http://redis.io/commands/del
3026
  *
3027
  * @author Daniele Alessandri <suppakilla@gmail.com>
3028
  */
3029
+ class KeyDelete extends Command
3030
  {
3031
  /**
3032
  * {@inheritdoc}
3033
  */
3034
  public function getId()
3035
  {
3036
+ return 'DEL';
3037
  }
3038
 
3039
  /**
3046
  }
3047
 
3048
  /**
3049
+ * @link http://redis.io/commands/dump
3050
  *
3051
  * @author Daniele Alessandri <suppakilla@gmail.com>
3052
  */
3053
+ class KeyDump extends Command
3054
  {
3055
  /**
3056
  * {@inheritdoc}
3057
  */
3058
  public function getId()
3059
  {
3060
+ return 'DUMP';
 
 
 
 
 
 
 
 
3061
  }
3062
  }
3063
 
3064
  /**
3065
+ * @link http://redis.io/commands/exists
3066
  *
3067
  * @author Daniele Alessandri <suppakilla@gmail.com>
3068
  */
3069
+ class KeyExists extends Command
3070
  {
3071
  /**
3072
  * {@inheritdoc}
3073
  */
3074
  public function getId()
3075
  {
3076
+ return 'EXISTS';
3077
  }
3078
  }
3079
 
3080
  /**
3081
+ * @link http://redis.io/commands/hkeys
3082
  *
3083
  * @author Daniele Alessandri <suppakilla@gmail.com>
3084
  */
3085
+ class HashKeys extends Command
3086
  {
3087
  /**
3088
  * {@inheritdoc}
3089
  */
3090
  public function getId()
3091
  {
3092
+ return 'HKEYS';
3093
  }
3094
  }
3095
 
3096
  /**
3097
+ * @link http://redis.io/commands/hincrbyfloat
3098
  *
3099
  * @author Daniele Alessandri <suppakilla@gmail.com>
3100
  */
3101
+ class HashIncrementByFloat extends Command
3102
  {
3103
  /**
3104
  * {@inheritdoc}
3105
  */
3106
  public function getId()
3107
  {
3108
+ return 'HINCRBYFLOAT';
3109
  }
3110
  }
3111
 
3112
  /**
3113
+ * @link http://redis.io/commands/keys
3114
  *
3115
  * @author Daniele Alessandri <suppakilla@gmail.com>
3116
  */
3117
+ class KeyKeys extends Command
3118
  {
3119
  /**
3120
  * {@inheritdoc}
3121
  */
3122
  public function getId()
3123
  {
3124
+ return 'KEYS';
3125
  }
3126
  }
3127
 
3128
  /**
3129
+ * @link http://redis.io/commands/hgetall
3130
  *
3131
  * @author Daniele Alessandri <suppakilla@gmail.com>
3132
  */
3133
+ class HashGetAll extends Command
3134
  {
3135
  /**
3136
  * {@inheritdoc}
3137
  */
3138
  public function getId()
3139
  {
3140
+ return 'HGETALL';
3141
  }
3142
 
3143
  /**
3145
  */
3146
  public function parseResponse($data)
3147
  {
3148
+ $result = array();
3149
+
3150
+ for ($i = 0; $i < count($data); ++$i) {
3151
+ $result[$data[$i]] = $data[++$i];
3152
+ }
3153
+
3154
+ return $result;
3155
+ }
3156
+ }
3157
 
3158
  /**
3159
+ * @link http://redis.io/commands/brpop
3160
  *
3161
  * @author Daniele Alessandri <suppakilla@gmail.com>
3162
  */
3163
+ class ListPopLastBlocking extends ListPopFirstBlocking
3164
  {
3165
  /**
3166
  * {@inheritdoc}
3167
  */
3168
  public function getId()
3169
  {
3170
+ return 'BRPOP';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3171
  }
3172
  }
3173
 
3174
  /**
3175
+ * @link http://redis.io/commands/save
3176
  *
3177
  * @author Daniele Alessandri <suppakilla@gmail.com>
3178
  */
3179
+ class ServerSave extends Command
3180
  {
3181
  /**
3182
  * {@inheritdoc}
3183
  */
3184
  public function getId()
3185
  {
3186
+ return 'SAVE';
 
 
 
 
 
 
 
 
3187
  }
3188
  }
3189
 
3190
  /**
3191
+ * @link http://redis.io/commands/geopos
3192
  *
3193
  * @author Daniele Alessandri <suppakilla@gmail.com>
3194
  */
3195
+ class GeospatialGeoPos extends Command
3196
  {
3197
  /**
3198
  * {@inheritdoc}
3199
  */
3200
  public function getId()
3201
  {
3202
+ return 'GEOPOS';
3203
  }
 
3204
 
 
 
 
 
 
 
 
3205
  /**
3206
  * {@inheritdoc}
3207
  */
3208
+ protected function filterArguments(array $arguments)
3209
  {
3210
+ if (count($arguments) === 2 && is_array($arguments[1])) {
3211
+ $members = array_pop($arguments);
3212
+ $arguments = array_merge($arguments, $members);
3213
+ }
3214
+
3215
+ return $arguments;
3216
  }
3217
  }
3218
 
3219
  /**
3220
+ * @link http://redis.io/commands/bgrewriteaof
3221
  *
3222
  * @author Daniele Alessandri <suppakilla@gmail.com>
3223
  */
3224
+ class ServerBackgroundRewriteAOF extends Command
3225
  {
3226
  /**
3227
  * {@inheritdoc}
3228
  */
3229
  public function getId()
3230
  {
3231
+ return 'BGREWRITEAOF';
3232
  }
3233
 
3234
  /**
3235
  * {@inheritdoc}
3236
  */
3237
+ public function parseResponse($data)
3238
  {
3239
+ return $data == 'Background append only file rewriting started';
3240
  }
3241
  }
3242
 
3243
  /**
3244
+ * @link http://redis.io/commands/bgsave
3245
  *
3246
  * @author Daniele Alessandri <suppakilla@gmail.com>
3247
  */
3248
+ class ServerBackgroundSave extends Command
3249
  {
3250
  /**
3251
  * {@inheritdoc}
3252
  */
3253
  public function getId()
3254
  {
3255
+ return 'BGSAVE';
3256
  }
 
3257
 
 
 
 
 
 
 
 
3258
  /**
3259
  * {@inheritdoc}
3260
  */
3261
+ public function parseResponse($data)
3262
  {
3263
+ return $data === 'Background saving started' ? true : $data;
3264
  }
3265
  }
3266
 
3267
  /**
3268
+ * @link http://redis.io/commands/client-list
3269
+ * @link http://redis.io/commands/client-kill
3270
+ * @link http://redis.io/commands/client-getname
3271
+ * @link http://redis.io/commands/client-setname
3272
  *
3273
  * @author Daniele Alessandri <suppakilla@gmail.com>
3274
  */
3275
+ class ServerClient extends Command
3276
  {
3277
  /**
3278
  * {@inheritdoc}
3279
  */
3280
  public function getId()
3281
  {
3282
+ return 'CLIENT';
3283
  }
 
3284
 
 
 
 
 
 
 
 
3285
  /**
3286
  * {@inheritdoc}
3287
  */
3288
+ public function parseResponse($data)
3289
  {
3290
+ $args = array_change_key_case($this->getArguments(), CASE_UPPER);
3291
+
3292
+ switch (strtoupper($args[0])) {
3293
+ case 'LIST':
3294
+ return $this->parseClientList($data);
3295
+ case 'KILL':
3296
+ case 'GETNAME':
3297
+ case 'SETNAME':
3298
+ default:
3299
+ return $data;
3300
+ }
3301
+ }
3302
+
3303
+ /**
3304
+ * Parses the response to CLIENT LIST and returns a structured list.
3305
+ *
3306
+ * @param string $data Response buffer.
3307
+ *
3308
+ * @return array
3309
+ */
3310
+ protected function parseClientList($data)
3311
+ {
3312
+ $clients = array();
3313
+
3314
+ foreach (explode("\n", $data, -1) as $clientData) {
3315
+ $client = array();
3316
+
3317
+ foreach (explode(' ', $clientData) as $kv) {
3318
+ @list($k, $v) = explode('=', $kv);
3319
+ $client[$k] = $v;
3320
+ }
3321
+
3322
+ $clients[] = $client;
3323
+ }
3324
+
3325
+ return $clients;
3326
  }
3327
  }
3328
 
3380
  }
3381
 
3382
  /**
3383
+ * @link http://redis.io/commands/georadiusbymember
3384
  *
3385
  * @author Daniele Alessandri <suppakilla@gmail.com>
3386
  */
3387
+ class GeospatialGeoRadiusByMember extends GeospatialGeoRadius
3388
  {
3389
  /**
3390
  * {@inheritdoc}
3391
  */
3392
  public function getId()
3393
  {
3394
+ return 'GEORADIUSBYMEMBER';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3395
  }
3396
  }
3397
 
3398
  /**
3399
+ * @link http://redis.io/commands/flushall
3400
  *
3401
  * @author Daniele Alessandri <suppakilla@gmail.com>
3402
  */
3403
+ class ServerFlushAll extends Command
3404
  {
3405
  /**
3406
  * {@inheritdoc}
3407
  */
3408
+ public function getId()
3409
  {
3410
+ return 'FLUSHALL';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3411
  }
3412
  }
3413
 
3414
  /**
3415
+ * Class for generic "anonymous" Redis commands.
3416
+ *
3417
+ * This command class does not filter input arguments or parse responses, but
3418
+ * can be used to leverage the standard Predis API to execute any command simply
3419
+ * by providing the needed arguments following the command signature as defined
3420
+ * by Redis in its documentation.
3421
  *
3422
  * @author Daniele Alessandri <suppakilla@gmail.com>
3423
  */
3424
+ class RawCommand implements CommandInterface
3425
  {
3426
+ private $slot;
3427
+ private $commandID;
3428
+ private $arguments;
3429
+
3430
  /**
3431
+ * @param array $arguments Command ID and its arguments.
3432
+ *
3433
+ * @throws \InvalidArgumentException
3434
  */
3435
+ public function __construct(array $arguments)
3436
  {
3437
+ if (!$arguments) {
3438
+ throw new \InvalidArgumentException(
3439
+ 'The arguments array must contain at least the command ID.'
3440
+ );
3441
+ }
3442
+
3443
+ $this->commandID = strtoupper(array_shift($arguments));
3444
+ $this->arguments = $arguments;
3445
  }
 
3446
 
 
 
 
 
 
 
 
3447
  /**
3448
+ * Creates a new raw command using a variadic method.
3449
+ *
3450
+ * @param string $commandID Redis command ID.
3451
+ * @param string ... Arguments list for the command.
3452
+ *
3453
+ * @return CommandInterface
3454
  */
3455
+ public static function create($commandID /* [ $arg, ... */)
3456
  {
3457
+ $arguments = func_get_args();
3458
+ $command = new self($arguments);
3459
+
3460
+ return $command;
3461
  }
3462
 
3463
  /**
3464
  * {@inheritdoc}
3465
  */
3466
+ public function getId()
3467
  {
3468
+ return $this->commandID;
3469
+ }
 
 
3470
 
3471
+ /**
3472
+ * {@inheritdoc}
3473
+ */
3474
+ public function setArguments(array $arguments)
3475
+ {
3476
+ $this->arguments = $arguments;
3477
+ unset($this->slot);
3478
  }
3479
 
3480
  /**
3481
+ * {@inheritdoc}
 
 
 
 
3482
  */
3483
+ public function setRawArguments(array $arguments)
3484
  {
3485
+ $this->setArguments($arguments);
3486
+ }
 
3487
 
3488
+ /**
3489
+ * {@inheritdoc}
3490
+ */
3491
+ public function getArguments()
3492
+ {
3493
+ return $this->arguments;
3494
+ }
3495
 
3496
+ /**
3497
+ * {@inheritdoc}
3498
+ */
3499
+ public function getArgument($index)
3500
+ {
3501
+ if (isset($this->arguments[$index])) {
3502
+ return $this->arguments[$index];
3503
  }
 
 
3504
  }
 
3505
 
 
 
 
 
 
 
 
3506
  /**
3507
  * {@inheritdoc}
3508
  */
3509
+ public function setSlot($slot)
3510
  {
3511
+ $this->slot = $slot;
3512
  }
 
3513
 
 
 
 
 
 
 
 
3514
  /**
3515
  * {@inheritdoc}
3516
  */
3517
+ public function getSlot()
3518
  {
3519
+ if (isset($this->slot)) {
3520
+ return $this->slot;
3521
+ }
3522
  }
 
3523
 
 
 
 
 
 
 
 
3524
  /**
3525
  * {@inheritdoc}
3526
  */
3527
+ public function parseResponse($data)
3528
  {
3529
+ return $data;
3530
  }
3531
  }
3532
 
3533
  /**
3534
+ * @link http://redis.io/commands/flushdb
3535
  *
3536
  * @author Daniele Alessandri <suppakilla@gmail.com>
3537
  */
3538
+ class ServerFlushDatabase extends Command
3539
  {
3540
  /**
3541
  * {@inheritdoc}
3542
  */
3543
  public function getId()
3544
  {
3545
+ return 'FLUSHDB';
3546
  }
3547
  }
3548
 
3549
  /**
3550
+ * @link http://redis.io/commands/geohash
 
 
 
3551
  *
3552
  * @author Daniele Alessandri <suppakilla@gmail.com>
3553
  */
3554
+ class GeospatialGeoHash extends Command
3555
  {
3556
  /**
3557
  * {@inheritdoc}
3558
  */
3559
  public function getId()
3560
  {
3561
+ return 'GEOHASH';
3562
  }
3563
 
3564
  /**
3565
  * {@inheritdoc}
3566
  */
3567
+ protected function filterArguments(array $arguments)
3568
  {
3569
+ if (count($arguments) === 2 && is_array($arguments[1])) {
3570
+ $members = array_pop($arguments);
3571
+ $arguments = array_merge($arguments, $members);
 
 
 
 
 
 
 
3572
  }
3573
+
3574
+ return $arguments;
3575
  }
3576
+ }
3577
 
3578
+ /**
3579
+ * @link http://redis.io/commands/info
3580
+ *
3581
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3582
+ */
3583
+ class ServerInfoV26x extends ServerInfo
3584
+ {
3585
  /**
3586
+ * {@inheritdoc}
 
 
 
 
3587
  */
3588
+ public function parseResponse($data)
3589
  {
3590
+ if ($data === '') {
3591
+ return array();
3592
+ }
3593
 
3594
+ $info = array();
 
3595
 
3596
+ $current = null;
3597
+ $infoLines = preg_split('/\r?\n/', $data);
3598
+
3599
+ if (isset($infoLines[0]) && $infoLines[0][0] !== '#') {
3600
+ return parent::parseResponse($data);
3601
+ }
3602
+
3603
+ foreach ($infoLines as $row) {
3604
+ if ($row === '') {
3605
+ continue;
3606
  }
3607
 
3608
+ if (preg_match('/^# (\w+)$/', $row, $matches)) {
3609
+ $info[$matches[1]] = array();
3610
+ $current = &$info[$matches[1]];
3611
+ continue;
3612
+ }
3613
+
3614
+ list($k, $v) = $this->parseRow($row);
3615
+ $current[$k] = $v;
3616
  }
3617
 
3618
+ return $info;
3619
  }
3620
  }
3621
 
3622
  /**
3623
+ * @link http://redis.io/commands/lastsave
3624
  *
3625
  * @author Daniele Alessandri <suppakilla@gmail.com>
3626
  */
3627
+ class ServerLastSave extends Command
3628
  {
3629
  /**
3630
  * {@inheritdoc}
3631
  */
3632
  public function getId()
3633
  {
3634
+ return 'LASTSAVE';
3635
  }
3636
+ }
3637
 
3638
+ /**
3639
+ * @link http://redis.io/commands/monitor
3640
+ *
3641
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3642
+ */
3643
+ class ServerMonitor extends Command
3644
+ {
3645
  /**
3646
  * {@inheritdoc}
3647
  */
3648
+ public function getId()
3649
  {
3650
+ return 'MONITOR';
3651
  }
3652
  }
3653
 
3654
  /**
3655
+ * @link http://redis.io/commands/object
3656
  *
3657
  * @author Daniele Alessandri <suppakilla@gmail.com>
3658
  */
3659
+ class ServerObject extends Command
3660
  {
3661
  /**
3662
  * {@inheritdoc}
3663
  */
3664
  public function getId()
3665
  {
3666
+ return 'OBJECT';
3667
  }
3668
  }
3669
 
3670
  /**
3671
+ * Base class used to implement an higher level abstraction for commands based
3672
+ * on Lua scripting with EVAL and EVALSHA.
3673
+ *
3674
+ * @link http://redis.io/commands/eval
3675
  *
3676
  * @author Daniele Alessandri <suppakilla@gmail.com>
3677
  */
3678
+ abstract class ScriptCommand extends ServerEvalSHA
3679
  {
3680
  /**
3681
+ * Gets the body of a Lua script.
3682
  *
3683
+ * @return string
3684
  */
3685
+ abstract public function getScript();
3686
+
3687
+ /**
3688
+ * Specifies the number of arguments that should be considered as keys.
3689
+ *
3690
+ * The default behaviour for the base class is to return 0 to indicate that
3691
+ * all the elements of the arguments array should be considered as keys, but
3692
+ * subclasses can enforce a static number of keys.
3693
+ *
3694
+ * @return int
3695
+ */
3696
+ protected function getKeysCount()
3697
+ {
3698
+ return 0;
3699
+ }
3700
+
3701
+ /**
3702
+ * Returns the elements from the arguments that are identified as keys.
3703
+ *
3704
+ * @return array
3705
+ */
3706
+ public function getKeys()
3707
+ {
3708
+ return array_slice($this->getArguments(), 2, $this->getKeysCount());
3709
+ }
3710
+
3711
+ /**
3712
+ * {@inheritdoc}
3713
+ */
3714
+ protected function filterArguments(array $arguments)
3715
+ {
3716
+ if (($numkeys = $this->getKeysCount()) && $numkeys < 0) {
3717
+ $numkeys = count($arguments) + $numkeys;
3718
+ }
3719
+
3720
+ return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments);
3721
+ }
3722
+
3723
+ /**
3724
+ * @return array
3725
+ */
3726
+ public function getEvalArguments()
3727
+ {
3728
+ $arguments = $this->getArguments();
3729
+ $arguments[0] = $this->getScript();
3730
+
3731
+ return $arguments;
3732
+ }
3733
  }
3734
 
3735
  /**
3736
+ * @link http://redis.io/commands/punsubscribe
3737
  *
3738
  * @author Daniele Alessandri <suppakilla@gmail.com>
3739
  */
3740
+ class PubSubUnsubscribeByPattern extends PubSubUnsubscribe
3741
  {
3742
  /**
3743
  * {@inheritdoc}
3744
  */
3745
  public function getId()
3746
  {
3747
+ return 'PUNSUBSCRIBE';
3748
  }
3749
  }
3750
 
3751
  /**
3752
+ * @link http://redis.io/commands/rpoplpush
3753
  *
3754
  * @author Daniele Alessandri <suppakilla@gmail.com>
3755
  */
3756
+ class ListPopLastPushHead extends Command
3757
  {
3758
  /**
3759
  * {@inheritdoc}
3760
  */
3761
  public function getId()
3762
  {
3763
+ return 'RPOPLPUSH';
3764
  }
3765
  }
3766
 
3781
  }
3782
 
3783
  /**
3784
+ * @link http://redis.io/commands/brpoplpush
3785
  *
3786
  * @author Daniele Alessandri <suppakilla@gmail.com>
3787
  */
3788
+ class ListPopLastPushHeadBlocking extends Command
3789
  {
3790
  /**
3791
  * {@inheritdoc}
3792
  */
3793
  public function getId()
3794
  {
3795
+ return 'BRPOPLPUSH';
3796
  }
3797
  }
3798
 
3799
  /**
3800
+ * @link http://redis.io/commands/lpush
3801
  *
3802
  * @author Daniele Alessandri <suppakilla@gmail.com>
3803
  */
3804
+ class ListPushHead extends ListPushTail
3805
  {
3806
  /**
3807
  * {@inheritdoc}
3808
  */
3809
  public function getId()
3810
  {
3811
+ return 'LPUSH';
3812
  }
3813
  }
3814
 
3815
  /**
3816
+ * @link http://redis.io/commands/lpushx
3817
  *
3818
  * @author Daniele Alessandri <suppakilla@gmail.com>
3819
  */
3820
+ class ListPushHeadX extends Command
3821
  {
3822
  /**
3823
  * {@inheritdoc}
3824
  */
3825
  public function getId()
3826
  {
3827
+ return 'LPUSHX';
3828
  }
3829
+ }
3830
 
3831
+ /**
3832
+ * @link http://redis.io/commands/hget
3833
+ *
3834
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3835
+ */
3836
+ class HashGet extends Command
3837
+ {
3838
  /**
3839
  * {@inheritdoc}
3840
  */
3841
+ public function getId()
3842
  {
3843
+ return 'HGET';
 
 
 
 
 
 
3844
  }
3845
+ }
3846
 
3847
+ /**
3848
+ * @link http://redis.io/commands/rpushx
3849
+ *
3850
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3851
+ */
3852
+ class ListPushTailX extends Command
3853
+ {
3854
  /**
3855
+ * {@inheritdoc}
 
 
 
 
3856
  */
3857
+ public function getId()
3858
  {
3859
+ return 'RPUSHX';
 
 
 
 
 
 
 
3860
  }
3861
  }
3862
 
3863
  /**
3864
+ * @link http://redis.io/commands/lrange
3865
  *
3866
  * @author Daniele Alessandri <suppakilla@gmail.com>
3867
  */
3868
+ class ListRange extends Command
3869
  {
3870
  /**
3871
  * {@inheritdoc}
3872
  */
3873
  public function getId()
3874
  {
3875
+ return 'LRANGE';
3876
  }
3877
  }
3878
 
3879
  /**
3880
+ * @link http://redis.io/commands/lset
 
 
 
3881
  *
3882
  * @author Daniele Alessandri <suppakilla@gmail.com>
3883
  */
3884
+ class ListSet extends Command
3885
  {
3886
  /**
3887
+ * {@inheritdoc}
 
 
 
 
 
 
 
 
 
 
 
 
 
3888
  */
3889
+ public function getId()
3890
  {
3891
+ return 'LSET';
3892
  }
3893
+ }
3894
 
3895
+ /**
3896
+ * @link http://redis.io/commands/hdel
3897
+ *
3898
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3899
+ */
3900
+ class HashDelete extends Command
3901
+ {
3902
  /**
3903
+ * {@inheritdoc}
 
 
3904
  */
3905
+ public function getId()
3906
  {
3907
+ return 'HDEL';
3908
  }
3909
 
3910
  /**
3912
  */
3913
  protected function filterArguments(array $arguments)
3914
  {
3915
+ return self::normalizeVariadic($arguments);
 
 
 
 
3916
  }
3917
+ }
3918
 
3919
+ /**
3920
+ * @link http://redis.io/commands/ltrim
3921
+ *
3922
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3923
+ */
3924
+ class ListTrim extends Command
3925
+ {
3926
  /**
3927
+ * {@inheritdoc}
3928
  */
3929
+ public function getId()
3930
  {
3931
+ return 'LTRIM';
 
 
 
3932
  }
3933
  }
3934
 
3935
  /**
3936
+ * Defines a command whose keys can be prefixed.
3937
  *
3938
  * @author Daniele Alessandri <suppakilla@gmail.com>
3939
  */
3940
+ interface PrefixableCommandInterface extends CommandInterface
3941
  {
3942
  /**
3943
+ * Prefixes all the keys found in the arguments of the command.
3944
+ *
3945
+ * @param string $prefix String used to prefix the keys.
3946
  */
3947
+ public function prefixKeys($prefix);
3948
+ }
 
 
3949
 
3950
+ /**
3951
+ * @link http://redis.io/commands/publish
3952
+ *
3953
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3954
+ */
3955
+ class PubSubPublish extends Command
3956
+ {
3957
  /**
3958
  * {@inheritdoc}
3959
  */
3960
+ public function getId()
3961
  {
3962
+ return 'PUBLISH';
3963
  }
3964
  }
3965
 
3966
  /**
3967
+ * @link http://redis.io/commands/pubsub
 
 
 
 
 
3968
  *
3969
  * @author Daniele Alessandri <suppakilla@gmail.com>
3970
  */
3971
+ class PubSubPubsub extends Command
3972
  {
3973
+ /**
3974
+ * {@inheritdoc}
3975
+ */
3976
+ public function getId()
3977
+ {
3978
+ return 'PUBSUB';
3979
+ }
3980
 
3981
  /**
3982
+ * {@inheritdoc}
 
 
3983
  */
3984
+ public function parseResponse($data)
3985
  {
3986
+ switch (strtolower($this->getArgument(0))) {
3987
+ case 'numsub':
3988
+ return self::processNumsub($data);
 
 
3989
 
3990
+ default:
3991
+ return $data;
3992
+ }
3993
  }
3994
 
3995
  /**
3996
+ * Returns the processed response to PUBSUB NUMSUB.
3997
  *
3998
+ * @param array $channels List of channels
 
3999
  *
4000
+ * @return array
4001
  */
4002
+ protected static function processNumsub(array $channels)
4003
  {
4004
+ $processed = array();
4005
+ $count = count($channels);
4006
 
4007
+ for ($i = 0; $i < $count; ++$i) {
4008
+ $processed[$channels[$i]] = $channels[++$i];
4009
+ }
4010
+
4011
+ return $processed;
4012
  }
4013
+ }
4014
 
4015
+ /**
4016
+ * @link http://redis.io/commands/hexists
4017
+ *
4018
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4019
+ */
4020
+ class HashExists extends Command
4021
+ {
4022
  /**
4023
  * {@inheritdoc}
4024
  */
4025
  public function getId()
4026
  {
4027
+ return 'HEXISTS';
4028
  }
4029
+ }
4030
 
4031
+ /**
4032
+ * @link http://redis.io/commands/psubscribe
4033
+ *
4034
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4035
+ */
4036
+ class PubSubSubscribeByPattern extends PubSubSubscribe
4037
+ {
4038
  /**
4039
  * {@inheritdoc}
4040
  */
4041
+ public function getId()
4042
  {
4043
+ return 'PSUBSCRIBE';
 
4044
  }
4045
+ }
4046
 
4047
+ /**
4048
+ * @link http://redis.io/commands/dbsize
4049
+ *
4050
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4051
+ */
4052
+ class ServerDatabaseSize extends Command
4053
+ {
4054
  /**
4055
  * {@inheritdoc}
4056
  */
4057
+ public function getId()
4058
  {
4059
+ return 'DBSIZE';
4060
  }
4061
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4062
 
4063
  /* --------------------------------------------------------------------------- */
4064
 
4072
  use Predis\Command\RawCommand;
4073
  use Predis\NotSupportedException;
4074
  use Predis\Response\Error as ErrorResponse;
4075
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
4076
  use Predis\Response\Status as StatusResponse;
4077
 
4078
  /**
4254
  *
4255
  * @return ParametersInterface
4256
  */
4257
+ abstract protected function assertParameters(ParametersInterface $parameters);
 
 
 
 
 
 
 
 
 
 
 
 
 
4258
 
4259
  /**
4260
  * Creates the underlying resource used to communicate with Redis.
4428
  * Standard connection to Redis servers implemented on top of PHP's streams.
4429
  * The connection parameters supported by this class are:.
4430
  *
4431
+ * - scheme: it can be either 'redis', 'tcp', 'rediss', 'tls' or 'unix'.
4432
  * - host: hostname or IP address of the server.
4433
  * - port: TCP port of the server.
4434
  * - path: path of a UNIX domain socket when scheme is 'unix'.
4435
+ * - timeout: timeout to perform the connection (default is 5 seconds).
4436
  * - read_write_timeout: timeout of read / write operations.
4437
  * - async_connect: performs the connection asynchronously.
4438
  * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
4439
  * - persistent: the connection is left intact after a GC collection.
4440
+ * - ssl: context options array (see http://php.net/manual/en/context.ssl.php)
4441
  *
4442
  * @author Daniele Alessandri <suppakilla@gmail.com>
4443
  */
4460
  /**
4461
  * {@inheritdoc}
4462
  */
4463
+ protected function assertParameters(ParametersInterface $parameters)
4464
  {
4465
+ switch ($parameters->scheme) {
4466
  case 'tcp':
4467
  case 'redis':
 
 
4468
  case 'unix':
4469
+ break;
4470
+
4471
+ case 'tls':
4472
+ case 'rediss':
4473
+ $this->assertSslSupport($parameters);
4474
+ break;
4475
 
4476
  default:
4477
+ throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'.");
4478
  }
4479
+
4480
+ return $parameters;
4481
  }
4482
 
4483
  /**
4484
+ * Checks needed conditions for SSL-encrypted connections.
4485
  *
4486
  * @param ParametersInterface $parameters Initialization parameters for the connection.
4487
  *
4488
+ * @throws \InvalidArgumentException
4489
  */
4490
+ protected function assertSslSupport(ParametersInterface $parameters)
4491
  {
4492
+ if (
4493
+ filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN) &&
4494
+ version_compare(PHP_VERSION, '7.0.0beta') < 0
4495
+ ) {
4496
+ throw new \InvalidArgumentException('Persistent SSL connections require PHP >= 7.0.0.');
4497
  }
4498
+ }
4499
 
4500
+ /**
4501
+ * {@inheritdoc}
4502
+ */
4503
+ protected function createResource()
4504
+ {
4505
+ switch ($this->parameters->scheme) {
4506
+ case 'tcp':
4507
+ case 'redis':
4508
+ return $this->tcpStreamInitializer($this->parameters);
4509
 
4510
+ case 'unix':
4511
+ return $this->unixStreamInitializer($this->parameters);
4512
+
4513
+ case 'tls':
4514
+ case 'rediss':
4515
+ return $this->tlsStreamInitializer($this->parameters);
4516
 
4517
+ default:
4518
+ throw new \InvalidArgumentException("Invalid scheme: '{$this->parameters->scheme}'.");
 
4519
  }
4520
+ }
4521
 
4522
+ /**
4523
+ * Creates a connected stream socket resource.
4524
+ *
4525
+ * @param ParametersInterface $parameters Connection parameters.
4526
+ * @param string $address Address for stream_socket_client().
4527
+ * @param int $flags Flags for stream_socket_client().
4528
+ *
4529
+ * @return resource
4530
+ */
4531
+ protected function createStreamSocket(ParametersInterface $parameters, $address, $flags)
4532
+ {
4533
+ $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0);
4534
 
4535
+ if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags)) {
4536
  $this->onConnectionError(trim($errstr), $errno);
4537
  }
4538
 
4552
  return $resource;
4553
  }
4554
 
4555
+ /**
4556
+ * Initializes a TCP stream resource.
4557
+ *
4558
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4559
+ *
4560
+ * @return resource
4561
+ */
4562
+ protected function tcpStreamInitializer(ParametersInterface $parameters)
4563
+ {
4564
+ if (!filter_var($parameters->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
4565
+ $address = "tcp://$parameters->host:$parameters->port";
4566
+ } else {
4567
+ $address = "tcp://[$parameters->host]:$parameters->port";
4568
+ }
4569
+
4570
+ $flags = STREAM_CLIENT_CONNECT;
4571
+
4572
+ if (isset($parameters->async_connect) && $parameters->async_connect) {
4573
+ $flags |= STREAM_CLIENT_ASYNC_CONNECT;
4574
+ }
4575
+
4576
+ if (isset($parameters->persistent)) {
4577
+ if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) {
4578
+ $flags |= STREAM_CLIENT_PERSISTENT;
4579
+
4580
+ if ($persistent === null) {
4581
+ $address = "{$address}/{$parameters->persistent}";
4582
+ }
4583
+ }
4584
+ }
4585
+
4586
+ $resource = $this->createStreamSocket($parameters, $address, $flags);
4587
+
4588
+ return $resource;
4589
+ }
4590
+
4591
  /**
4592
  * Initializes a UNIX stream resource.
4593
  *
4598
  protected function unixStreamInitializer(ParametersInterface $parameters)
4599
  {
4600
  if (!isset($parameters->path)) {
4601
+ throw new \InvalidArgumentException('Missing UNIX domain socket path.');
4602
  }
4603
 
 
4604
  $flags = STREAM_CLIENT_CONNECT;
4605
 
4606
+ if (isset($parameters->persistent)) {
4607
+ if (false !== $persistent = filter_var($parameters->persistent, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) {
4608
+ $flags |= STREAM_CLIENT_PERSISTENT;
4609
+
4610
+ if ($persistent === null) {
4611
+ throw new \InvalidArgumentException(
4612
+ 'Persistent connection IDs are not supported when using UNIX domain sockets.'
4613
+ );
4614
+ }
4615
+ }
4616
  }
4617
 
4618
+ $resource = $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags);
4619
 
4620
+ return $resource;
4621
+ }
4622
+
4623
+ /**
4624
+ * Initializes a SSL-encrypted TCP stream resource.
4625
+ *
4626
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4627
+ *
4628
+ * @return resource
4629
+ */
4630
+ protected function tlsStreamInitializer(ParametersInterface $parameters)
4631
+ {
4632
+ $resource = $this->tcpStreamInitializer($parameters);
4633
+ $metadata = stream_get_meta_data($resource);
4634
+
4635
+ // Detect if crypto mode is already enabled for this stream (PHP >= 7.0.0).
4636
+ if (isset($metadata['crypto'])) {
4637
+ return $resource;
4638
  }
4639
 
4640
+ if (is_array($parameters->ssl)) {
4641
+ $options = $parameters->ssl;
4642
+ } else {
4643
+ $options = array();
4644
+ }
4645
+
4646
+ if (!isset($options['crypto_type'])) {
4647
+ $options['crypto_type'] = STREAM_CRYPTO_METHOD_TLS_CLIENT;
4648
+ }
4649
+
4650
+ if (!stream_context_set_option($resource, array('ssl' => $options))) {
4651
+ $this->onConnectionError('Error while setting SSL context options');
4652
+ }
4653
+
4654
+ if (!stream_socket_enable_crypto($resource, true, $options['crypto_type'])) {
4655
+ $this->onConnectionError('Error while switching to encrypted communication');
4656
  }
4657
 
4658
  return $resource;
4665
  {
4666
  if (parent::connect() && $this->initCommands) {
4667
  foreach ($this->initCommands as $command) {
4668
+ $response = $this->executeCommand($command);
4669
+
4670
+ if ($response instanceof ErrorResponseInterface) {
4671
+ $this->onConnectionError("`{$command->getId()}` failed: $response", 0);
4672
+ }
4673
  }
4674
  }
4675
  }
4768
  return $multibulk;
4769
 
4770
  case ':':
4771
+ $integer = (int) $payload;
4772
+ return $integer == $payload ? $integer : $payload;
4773
 
4774
  case '-':
4775
  return new ErrorResponse($payload);
4794
 
4795
  $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
4796
 
4797
+ foreach ($arguments as $argument) {
 
4798
  $arglen = strlen($argument);
4799
  $buffer .= "\${$arglen}\r\n{$argument}\r\n";
4800
  }
4804
  }
4805
 
4806
  /**
4807
+ * Defines a connection to communicate with a single Redis server that leverages
4808
+ * an external protocol processor to handle pluggable protocol handlers.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4809
  *
4810
  * @author Daniele Alessandri <suppakilla@gmail.com>
4811
  */
4812
+ interface CompositeConnectionInterface extends NodeConnectionInterface
4813
  {
4814
  /**
4815
+ * Returns the protocol processor used by the connection.
4816
+ */
4817
+ public function getProtocol();
4818
+
4819
+ /**
4820
+ * Writes the buffer containing over the connection.
4821
  *
4822
+ * @param string $buffer String buffer to be sent over the connection.
4823
  */
4824
+ public function writeBuffer($buffer);
4825
 
4826
  /**
4827
+ * Reads the given number of bytes from the connection.
4828
  *
4829
+ * @param int $length Number of bytes to read from the connection.
4830
  *
4831
+ * @return string
4832
  */
4833
+ public function readBuffer($length);
4834
 
4835
  /**
4836
+ * Reads a line from the connection.
4837
  *
4838
+ * @param string
4839
  */
4840
+ public function readLine();
4841
  }
4842
 
4843
  /**
4881
  }
4882
 
4883
  /**
4884
+ * Interface defining a container for connection parameters.
4885
+ *
4886
+ * The actual list of connection parameters depends on the features supported by
4887
+ * each connection backend class (please refer to their specific documentation),
4888
+ * but the most common parameters used through the library are:
4889
+ *
4890
+ * @property-read string scheme Connection scheme, such as 'tcp' or 'unix'.
4891
+ * @property-read string host IP address or hostname of Redis.
4892
+ * @property-read int port TCP port on which Redis is listening to.
4893
+ * @property-read string path Path of a UNIX domain socket file.
4894
+ * @property-read string alias Alias for the connection.
4895
+ * @property-read float timeout Timeout for the connect() operation.
4896
+ * @property-read float read_write_timeout Timeout for read() and write() operations.
4897
+ * @property-read bool async_connect Performs the connect() operation asynchronously.
4898
+ * @property-read bool tcp_nodelay Toggles the Nagle's algorithm for coalescing.
4899
+ * @property-read bool persistent Leaves the connection open after a GC collection.
4900
+ * @property-read string password Password to access Redis (see the AUTH command).
4901
+ * @property-read string database Database index (see the SELECT command).
4902
  *
4903
  * @author Daniele Alessandri <suppakilla@gmail.com>
4904
  */
4905
+ interface ParametersInterface
4906
  {
4907
  /**
4908
+ * Checks if the specified parameters is set.
 
 
 
 
 
4909
  *
4910
+ * @param string $parameter Name of the parameter.
4911
+ *
4912
+ * @return bool
4913
  */
4914
+ public function __isset($parameter);
4915
 
4916
  /**
4917
+ * Returns the value of the specified parameter.
4918
  *
4919
+ * @param string $parameter Name of the parameter.
4920
  *
4921
+ * @return mixed|null
4922
  */
4923
+ public function __get($parameter);
4924
 
4925
  /**
4926
+ * Returns an array representation of the connection parameters.
4927
  *
4928
+ * @return array
4929
  */
4930
+ public function toArray();
4931
  }
4932
 
4933
  /**
4934
+ * Connection abstraction to Redis servers based on PHP's stream that uses an
4935
+ * external protocol processor defining the protocol used for the communication.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4936
  *
4937
  * @author Daniele Alessandri <suppakilla@gmail.com>
4938
  */
4939
+ class CompositeStreamConnection extends StreamConnection implements CompositeConnectionInterface
4940
  {
4941
+ protected $protocol;
4942
 
4943
  /**
4944
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4945
+ * @param ProtocolProcessorInterface $protocol Protocol processor.
4946
  */
4947
+ public function __construct(
4948
+ ParametersInterface $parameters,
4949
+ ProtocolProcessorInterface $protocol = null
4950
+ ) {
4951
+ $this->parameters = $this->assertParameters($parameters);
4952
+ $this->protocol = $protocol ?: new TextProtocolProcessor();
 
4953
  }
4954
 
4955
  /**
4956
  * {@inheritdoc}
4957
  */
4958
+ public function getProtocol()
4959
  {
4960
+ return $this->protocol;
 
 
4961
  }
4962
 
4963
  /**
4964
+ * {@inheritdoc}
4965
  */
4966
+ public function writeBuffer($buffer)
4967
  {
4968
+ $this->write($buffer);
 
 
 
 
4969
  }
4970
 
4971
  /**
4972
  * {@inheritdoc}
4973
  */
4974
+ public function readBuffer($length)
4975
  {
4976
+ if ($length <= 0) {
4977
+ throw new \InvalidArgumentException('Length parameter must be greater than 0.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4978
  }
4979
 
4980
+ $value = '';
4981
+ $socket = $this->getResource();
 
4982
 
4983
+ do {
4984
+ $chunk = fread($socket, $length);
 
 
4985
 
4986
+ if ($chunk === false || $chunk === '') {
4987
+ $this->onConnectionError('Error while reading bytes from the server.');
4988
+ }
 
4989
 
4990
+ $value .= $chunk;
4991
+ } while (($length -= strlen($chunk)) > 0);
 
 
4992
 
4993
+ return $value;
4994
  }
4995
 
4996
  /**
4997
+ * {@inheritdoc}
 
 
4998
  */
4999
+ public function readLine()
5000
  {
5001
+ $value = '';
5002
+ $socket = $this->getResource();
5003
 
5004
+ do {
5005
+ $chunk = fgets($socket);
5006
 
5007
+ if ($chunk === false || $chunk === '') {
5008
+ $this->onConnectionError('Error while reading line from the server.');
5009
+ }
5010
 
5011
+ $value .= $chunk;
5012
+ } while (substr($value, -2) !== "\r\n");
 
 
 
 
 
 
 
5013
 
5014
+ return substr($value, 0, -2);
 
 
 
 
 
 
 
 
 
5015
  }
5016
 
5017
  /**
5018
+ * {@inheritdoc}
 
 
5019
  */
5020
+ public function writeRequest(CommandInterface $command)
5021
  {
5022
+ $this->protocol->write($this, $command);
 
 
5023
  }
5024
 
5025
  /**
5027
  */
5028
  public function read()
5029
  {
5030
+ return $this->protocol->read($this);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5031
  }
5032
 
5033
  /**
5034
  * {@inheritdoc}
5035
  */
5036
+ public function __sleep()
5037
  {
5038
+ return array_merge(parent::__sleep(), array('protocol'));
 
 
 
5039
  }
5040
+ }
5041
 
5042
+ /**
5043
+ * Exception class that identifies connection-related errors.
5044
+ *
5045
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5046
+ */
5047
+ class ConnectionException extends CommunicationException
5048
+ {
 
5049
  }
5050
 
5051
  /**
5052
+ * Standard connection factory for creating connections to Redis nodes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5053
  *
5054
  * @author Daniele Alessandri <suppakilla@gmail.com>
5055
  */
5056
+ class Factory implements FactoryInterface
5057
  {
5058
+ private $defaults = array();
5059
+
5060
+ protected $schemes = array(
5061
+ 'tcp' => 'Predis\Connection\StreamConnection',
5062
+ 'unix' => 'Predis\Connection\StreamConnection',
5063
+ 'tls' => 'Predis\Connection\StreamConnection',
5064
+ 'redis' => 'Predis\Connection\StreamConnection',
5065
+ 'rediss' => 'Predis\Connection\StreamConnection',
5066
+ 'http' => 'Predis\Connection\WebdisConnection',
5067
+ );
5068
 
5069
  /**
5070
+ * Checks if the provided argument represents a valid connection class
5071
+ * implementing Predis\Connection\NodeConnectionInterface. Optionally,
5072
+ * callable objects are used for lazy initialization of connection objects.
5073
+ *
5074
+ * @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
5075
  *
5076
  * @throws \InvalidArgumentException
5077
+ *
5078
+ * @return mixed
5079
  */
5080
+ protected function checkInitializer($initializer)
5081
  {
5082
+ if (is_callable($initializer)) {
5083
+ return $initializer;
 
 
5084
  }
5085
 
5086
+ $class = new \ReflectionClass($initializer);
5087
 
5088
+ if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) {
5089
+ throw new \InvalidArgumentException(
5090
+ 'A connection initializer must be a valid connection class or a callable object.'
5091
+ );
5092
+ }
5093
+
5094
+ return $initializer;
5095
  }
5096
 
5097
  /**
5098
+ * {@inheritdoc}
 
5099
  */
5100
+ public function define($scheme, $initializer)
5101
  {
5102
+ $this->schemes[$scheme] = $this->checkInitializer($initializer);
 
5103
  }
5104
 
5105
  /**
5106
+ * {@inheritdoc}
 
 
 
 
5107
  */
5108
+ public function undefine($scheme)
5109
  {
5110
+ unset($this->schemes[$scheme]);
 
5111
  }
5112
 
5113
  /**
5114
+ * {@inheritdoc}
5115
  */
5116
+ public function create($parameters)
5117
  {
5118
+ if (!$parameters instanceof ParametersInterface) {
5119
+ $parameters = $this->createParameters($parameters);
 
 
 
 
 
 
 
 
5120
  }
 
5121
 
5122
+ $scheme = $parameters->scheme;
 
 
 
 
 
 
 
5123
 
5124
+ if (!isset($this->schemes[$scheme])) {
5125
+ throw new \InvalidArgumentException("Unknown connection scheme: '$scheme'.");
5126
  }
5127
 
5128
+ $initializer = $this->schemes[$scheme];
 
 
 
 
 
 
 
5129
 
5130
+ if (is_callable($initializer)) {
5131
+ $connection = call_user_func($initializer, $parameters, $this);
5132
+ } else {
5133
+ $connection = new $initializer($parameters);
5134
+ $this->prepareConnection($connection);
5135
  }
5136
 
5137
+ if (!$connection instanceof NodeConnectionInterface) {
5138
+ throw new \UnexpectedValueException(
5139
+ 'Objects returned by connection initializers must implement '.
5140
+ "'Predis\Connection\NodeConnectionInterface'."
5141
+ );
5142
+ }
5143
 
5144
+ return $connection;
5145
  }
5146
 
5147
  /**
5148
+ * {@inheritdoc}
 
 
5149
  */
5150
+ public function aggregate(AggregateConnectionInterface $connection, array $parameters)
5151
  {
5152
+ foreach ($parameters as $node) {
5153
+ $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node));
5154
+ }
 
 
 
5155
  }
5156
 
5157
  /**
5158
+ * Assigns a default set of parameters applied to new connections.
5159
  *
5160
+ * The set of parameters passed to create a new connection have precedence
5161
+ * over the default values set for the connection factory.
5162
+ *
5163
+ * @param array $parameters Set of connection parameters.
5164
  */
5165
+ public function setDefaultParameters(array $parameters)
5166
  {
5167
+ $this->defaults = $parameters;
 
 
5168
  }
5169
 
5170
  /**
5171
+ * Returns the default set of parameters applied to new connections.
5172
  *
5173
+ * @return array
5174
  */
5175
+ public function getDefaultParameters()
5176
  {
5177
+ return $this->defaults;
 
 
5178
  }
5179
 
5180
  /**
5181
+ * Creates a connection parameters instance from the supplied argument.
5182
  *
5183
+ * @param mixed $parameters Original connection parameters.
 
5184
  *
5185
+ * @return ParametersInterface
5186
  */
5187
+ protected function createParameters($parameters)
5188
  {
5189
+ if (is_string($parameters)) {
5190
+ $parameters = Parameters::parse($parameters);
5191
+ } else {
5192
+ $parameters = $parameters ?: array();
5193
+ }
5194
 
5195
+ if ($this->defaults) {
5196
+ $parameters += $this->defaults;
5197
+ }
5198
+
5199
+ return new Parameters($parameters);
5200
  }
5201
 
5202
  /**
5203
+ * Prepares a connection instance after its initialization.
5204
+ *
5205
+ * @param NodeConnectionInterface $connection Connection instance.
5206
  */
5207
+ protected function prepareConnection(NodeConnectionInterface $connection)
5208
  {
5209
+ $parameters = $connection->getParameters();
5210
+
5211
+ if (isset($parameters->password)) {
5212
+ $connection->addConnectCommand(
5213
+ new RawCommand(array('AUTH', $parameters->password))
5214
+ );
5215
+ }
5216
+
5217
+ if (isset($parameters->database)) {
5218
+ $connection->addConnectCommand(
5219
+ new RawCommand(array('SELECT', $parameters->database))
5220
+ );
5221
+ }
5222
  }
5223
+ }
5224
+
5225
+ /**
5226
+ * Container for connection parameters used to initialize connections to Redis.
5227
+ *
5228
+ * {@inheritdoc}
5229
+ *
5230
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5231
+ */
5232
+ class Parameters implements ParametersInterface
5233
+ {
5234
+ private $parameters;
5235
+
5236
+ private static $defaults = array(
5237
+ 'scheme' => 'tcp',
5238
+ 'host' => '127.0.0.1',
5239
+ 'port' => 6379,
5240
+ );
5241
 
5242
  /**
5243
+ * @param array $parameters Named array of connection parameters.
5244
  */
5245
+ public function __construct(array $parameters = array())
5246
  {
5247
+ $this->parameters = $this->filter($parameters) + $this->getDefaults();
5248
  }
5249
 
5250
  /**
5251
+ * Returns some default parameters with their values.
5252
+ *
5253
+ * @return array
5254
  */
5255
+ protected function getDefaults()
5256
  {
5257
+ return self::$defaults;
5258
  }
5259
 
5260
  /**
5261
+ * Creates a new instance by supplying the initial parameters either in the
5262
+ * form of an URI string or a named array.
 
5263
  *
5264
+ * @param array|string $parameters Set of connection parameters.
5265
  *
5266
+ * @return Parameters
5267
  */
5268
+ public static function create($parameters)
5269
  {
5270
+ if (is_string($parameters)) {
5271
+ $parameters = static::parse($parameters);
 
 
 
 
 
 
 
 
 
 
 
5272
  }
 
 
 
 
 
 
 
 
 
5273
 
5274
+ return new static($parameters ?: array());
 
 
 
 
 
5275
  }
5276
 
5277
  /**
5278
+ * Parses an URI string returning an array of connection parameters.
5279
+ *
5280
+ * When using the "redis" and "rediss" schemes the URI is parsed according
5281
+ * to the rules defined by the provisional registration documents approved
5282
+ * by IANA. If the URI has a password in its "user-information" part or a
5283
+ * database number in the "path" part these values override the values of
5284
+ * "password" and "database" if they are present in the "query" part.
5285
+ *
5286
+ * @link http://www.iana.org/assignments/uri-schemes/prov/redis
5287
+ * @link http://www.iana.org/assignments/uri-schemes/prov/rediss
5288
+ *
5289
+ * @param string $uri URI string.
5290
+ *
5291
+ * @throws \InvalidArgumentException
5292
+ *
5293
+ * @return array
5294
  */
5295
+ public static function parse($uri)
5296
  {
5297
+ if (stripos($uri, 'unix://') === 0) {
5298
+ // parse_url() can parse unix:/path/to/sock so we do not need the
5299
+ // unix:///path/to/sock hack, we will support it anyway until 2.0.
5300
+ $uri = str_ireplace('unix://', 'unix:', $uri);
5301
+ }
5302
 
5303
+ if (!$parsed = parse_url($uri)) {
5304
+ throw new \InvalidArgumentException("Invalid parameters URI: $uri");
 
 
 
5305
  }
5306
 
5307
+ if (
5308
+ isset($parsed['host'])
5309
+ && false !== strpos($parsed['host'], '[')
5310
+ && false !== strpos($parsed['host'], ']')
5311
+ ) {
5312
+ $parsed['host'] = substr($parsed['host'], 1, -1);
5313
+ }
5314
 
5315
+ if (isset($parsed['query'])) {
5316
+ parse_str($parsed['query'], $queryarray);
5317
+ unset($parsed['query']);
5318
 
5319
+ $parsed = array_merge($parsed, $queryarray);
5320
  }
5321
 
5322
+ if (stripos($uri, 'redis') === 0) {
5323
+ if (isset($parsed['pass'])) {
5324
+ $parsed['password'] = $parsed['pass'];
5325
+ unset($parsed['pass']);
5326
+ }
5327
 
5328
+ if (isset($parsed['path']) && preg_match('/^\/(\d+)(\/.*)?/', $parsed['path'], $path)) {
5329
+ $parsed['database'] = $path[1];
5330
 
5331
+ if (isset($path[2])) {
5332
+ $parsed['path'] = $path[2];
5333
+ } else {
5334
+ unset($parsed['path']);
5335
+ }
5336
+ }
5337
+ }
5338
+
5339
+ return $parsed;
5340
  }
5341
 
5342
  /**
5343
+ * Validates and converts each value of the connection parameters array.
5344
+ *
5345
+ * @param array $parameters Connection parameters.
5346
+ *
5347
+ * @return array
5348
  */
5349
+ protected function filter(array $parameters)
5350
  {
5351
+ return $parameters ?: array();
5352
  }
5353
 
5354
  /**
5355
  * {@inheritdoc}
5356
  */
5357
+ public function __get($parameter)
5358
  {
5359
+ if (isset($this->parameters[$parameter])) {
5360
+ return $this->parameters[$parameter];
5361
+ }
5362
  }
5363
 
5364
  /**
5365
  * {@inheritdoc}
5366
  */
5367
+ public function __isset($parameter)
5368
  {
5369
+ return isset($this->parameters[$parameter]);
5370
  }
5371
 
5372
  /**
5373
  * {@inheritdoc}
5374
  */
5375
+ public function toArray()
5376
  {
5377
+ return $this->parameters;
5378
  }
5379
 
5380
  /**
5384
  {
5385
  return array('parameters');
5386
  }
 
 
 
 
 
 
 
 
 
 
 
5387
  }
5388
 
5389
  /**
5406
  * - host: hostname or IP address of the server.
5407
  * - port: TCP port of the server.
5408
  * - path: path of a UNIX domain socket when scheme is 'unix'.
5409
+ * - timeout: timeout to perform the connection (default is 5 seconds).
5410
  * - read_write_timeout: timeout of read / write operations.
5411
  *
5412
  * @link http://github.com/nrk/phpiredis
5463
  */
5464
  protected function assertParameters(ParametersInterface $parameters)
5465
  {
5466
+ switch ($parameters->scheme) {
5467
+ case 'tcp':
5468
+ case 'redis':
5469
+ case 'unix':
5470
+ break;
5471
+
5472
+ default:
5473
+ throw new \InvalidArgumentException("Invalid scheme: '$parameters->scheme'.");
5474
+ }
5475
 
5476
  if (isset($parameters->persistent)) {
5477
  throw new NotSupportedException(
5512
  *
5513
  * @return \Closure
5514
  */
5515
+ protected function getStatusHandler()
5516
  {
5517
+ static $statusHandler;
5518
+
5519
+ if (!$statusHandler) {
5520
+ $statusHandler = function ($payload) {
5521
+ return StatusResponse::get($payload);
5522
+ };
5523
+ }
5524
+
5525
+ return $statusHandler;
5526
  }
5527
 
5528
  /**
5532
  */
5533
  protected function getErrorHandler()
5534
  {
5535
+ static $errorHandler;
5536
+
5537
+ if (!$errorHandler) {
5538
+ $errorHandler = function ($errorMessage) {
5539
+ return new ErrorResponse($errorMessage);
5540
+ };
5541
+ }
5542
+
5543
+ return $errorHandler;
5544
  }
5545
 
5546
  /**
5672
  $null = null;
5673
  $selectable = array($socket);
5674
 
5675
+ $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0);
5676
  $timeoutSecs = floor($timeout);
5677
  $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000;
5678
 
5698
  {
5699
  if (parent::connect() && $this->initCommands) {
5700
  foreach ($this->initCommands as $command) {
5701
+ $response = $this->executeCommand($command);
5702
+
5703
+ if ($response instanceof ErrorResponseInterface) {
5704
+ $this->onConnectionError("`{$command->getId()}` failed: $response", 0);
5705
+ }
5706
  }
5707
  }
5708
  }
5787
  }
5788
 
5789
  /**
5790
+ * This class provides the implementation of a Predis connection that uses PHP's
5791
+ * streams for network communication and wraps the phpiredis C extension (PHP
5792
+ * bindings for hiredis) to parse and serialize the Redis protocol.
5793
+ *
5794
+ * This class is intended to provide an optional low-overhead alternative for
5795
+ * processing responses from Redis compared to the standard pure-PHP classes.
5796
+ * Differences in speed when dealing with short inline responses are practically
5797
+ * nonexistent, the actual speed boost is for big multibulk responses when this
5798
+ * protocol processor can parse and return responses very fast.
5799
+ *
5800
+ * For instructions on how to build and install the phpiredis extension, please
5801
+ * consult the repository of the project.
5802
+ *
5803
+ * The connection parameters supported by this class are:
5804
+ *
5805
+ * - scheme: it can be either 'redis', 'tcp' or 'unix'.
5806
+ * - host: hostname or IP address of the server.
5807
+ * - port: TCP port of the server.
5808
+ * - path: path of a UNIX domain socket when scheme is 'unix'.
5809
+ * - timeout: timeout to perform the connection.
5810
+ * - read_write_timeout: timeout of read / write operations.
5811
+ * - async_connect: performs the connection asynchronously.
5812
+ * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
5813
+ * - persistent: the connection is left intact after a GC collection.
5814
+ *
5815
+ * @link https://github.com/nrk/phpiredis
5816
  *
5817
  * @author Daniele Alessandri <suppakilla@gmail.com>
5818
  */
5819
+ class PhpiredisStreamConnection extends StreamConnection
5820
  {
5821
+ private $reader;
5822
 
5823
  /**
5824
+ * {@inheritdoc}
 
5825
  */
5826
+ public function __construct(ParametersInterface $parameters)
5827
+ {
5828
+ $this->assertExtensions();
5829
+
5830
+ parent::__construct($parameters);
5831
+
5832
+ $this->reader = $this->createReader();
5833
  }
5834
 
5835
  /**
5836
  * {@inheritdoc}
5837
  */
5838
+ public function __destruct()
5839
  {
5840
+ phpiredis_reader_destroy($this->reader);
5841
+
5842
+ parent::__destruct();
5843
  }
5844
 
5845
  /**
5846
+ * Checks if the phpiredis extension is loaded in PHP.
5847
  */
5848
+ private function assertExtensions()
5849
  {
5850
+ if (!extension_loaded('phpiredis')) {
5851
+ throw new NotSupportedException(
5852
+ 'The "phpiredis" extension is required by this connection backend.'
5853
+ );
5854
+ }
5855
  }
5856
 
5857
  /**
5858
  * {@inheritdoc}
5859
  */
5860
+ protected function assertSslSupport(ParametersInterface $parameters)
5861
  {
5862
+ throw new \InvalidArgumentException('SSL encryption is not supported by this connection backend.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5863
  }
5864
 
5865
  /**
5866
  * {@inheritdoc}
5867
  */
5868
+ protected function createStreamSocket(ParametersInterface $parameters, $address, $flags, $context = null)
5869
  {
5870
+ $socket = null;
5871
+ $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0);
5872
 
5873
+ $resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags);
 
5874
 
5875
+ if (!$resource) {
5876
+ $this->onConnectionError(trim($errstr), $errno);
5877
+ }
5878
 
5879
+ if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
5880
+ $rwtimeout = (float) $parameters->read_write_timeout;
5881
+ $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
5882
 
5883
+ $timeout = array(
5884
+ 'sec' => $timeoutSeconds = floor($rwtimeout),
5885
+ 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
5886
+ );
5887
 
5888
+ $socket = $socket ?: socket_import_stream($resource);
5889
+ @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
5890
+ @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
5891
+ }
 
 
 
5892
 
5893
+ if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
5894
+ $socket = $socket ?: socket_import_stream($resource);
5895
+ socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
5896
+ }
5897
+
5898
+ return $resource;
5899
  }
5900
 
5901
  /**
5902
+ * Creates a new instance of the protocol reader resource.
5903
+ *
5904
+ * @return resource
5905
  */
5906
+ private function createReader()
5907
  {
5908
+ $reader = phpiredis_reader_create();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5909
 
5910
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
5911
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
 
 
 
 
5912
 
5913
+ return $reader;
 
 
 
 
 
5914
  }
5915
 
5916
  /**
5917
+ * Returns the underlying protocol reader resource.
5918
  *
5919
+ * @return resource
5920
  */
5921
+ protected function getReader()
5922
  {
5923
+ return $this->reader;
5924
  }
5925
 
5926
  /**
5927
+ * Returns the handler used by the protocol reader for inline responses.
 
 
 
5928
  *
5929
+ * @return \Closure
5930
  */
5931
+ protected function getStatusHandler()
5932
  {
5933
+ static $statusHandler;
5934
+
5935
+ if (!$statusHandler) {
5936
+ $statusHandler = function ($payload) {
5937
+ return StatusResponse::get($payload);
5938
+ };
5939
  }
5940
 
5941
+ return $statusHandler;
5942
  }
5943
 
5944
  /**
5945
+ * Returns the handler used by the protocol reader for error responses.
 
 
 
 
 
 
 
 
 
 
 
 
 
5946
  *
5947
+ * @return \Closure
5948
  */
5949
+ protected function getErrorHandler()
5950
  {
5951
+ static $errorHandler;
 
 
 
5952
 
5953
+ if (!$errorHandler) {
5954
+ $errorHandler = function ($errorMessage) {
5955
+ return new ErrorResponse($errorMessage);
5956
+ };
5957
  }
5958
 
5959
+ return $errorHandler;
5960
+ }
 
 
 
 
 
5961
 
5962
+ /**
5963
+ * {@inheritdoc}
5964
+ */
5965
+ public function read()
5966
+ {
5967
+ $socket = $this->getResource();
5968
+ $reader = $this->reader;
5969
 
5970
+ while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
5971
+ $buffer = stream_socket_recvfrom($socket, 4096);
5972
 
5973
+ if ($buffer === false || $buffer === '') {
5974
+ $this->onConnectionError('Error while reading bytes from the server.');
 
 
5975
  }
5976
 
5977
+ phpiredis_reader_feed($reader, $buffer);
 
 
 
 
 
 
 
 
5978
  }
5979
 
5980
+ if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
5981
+ return phpiredis_reader_get_reply($reader);
5982
+ } else {
5983
+ $this->onProtocolError(phpiredis_reader_get_error($reader));
 
 
 
 
 
 
 
 
 
 
5984
 
5985
+ return;
 
 
 
 
 
 
5986
  }
5987
  }
5988
 
5989
  /**
5990
  * {@inheritdoc}
5991
  */
5992
+ public function writeRequest(CommandInterface $command)
5993
  {
5994
+ $arguments = $command->getArguments();
5995
+ array_unshift($arguments, $command->getId());
5996
 
5997
+ $this->write(phpiredis_format_command($arguments));
 
 
 
 
 
5998
  }
5999
 
6000
  /**
6001
  * {@inheritdoc}
6002
  */
6003
+ public function __wakeup()
6004
  {
6005
+ $this->assertExtensions();
6006
+ $this->reader = $this->createReader();
6007
  }
6008
  }
6009
 
6010
  /**
6011
+ * This class implements a Predis connection that actually talks with Webdis
6012
+ * instead of connecting directly to Redis. It relies on the cURL extension to
6013
+ * communicate with the web server and the phpiredis extension to parse the
6014
+ * protocol for responses returned in the http response bodies.
6015
+ *
6016
+ * Some features are not yet available or they simply cannot be implemented:
6017
+ * - Pipelining commands.
6018
+ * - Publish / Subscribe.
6019
+ * - MULTI / EXEC transactions (not yet supported by Webdis).
6020
+ *
6021
+ * The connection parameters supported by this class are:
6022
+ *
6023
+ * - scheme: must be 'http'.
6024
+ * - host: hostname or IP address of the server.
6025
+ * - port: TCP port of the server.
6026
+ * - timeout: timeout to perform the connection (default is 5 seconds).
6027
+ * - user: username for authentication.
6028
+ * - pass: password for authentication.
6029
+ *
6030
+ * @link http://webd.is
6031
+ * @link http://github.com/nicolasff/webdis
6032
+ * @link http://github.com/seppo0010/phpiredis
6033
  *
6034
  * @author Daniele Alessandri <suppakilla@gmail.com>
6035
  */
6036
+ class WebdisConnection implements NodeConnectionInterface
6037
  {
6038
+ private $parameters;
6039
+ private $resource;
6040
+ private $reader;
 
 
 
6041
 
6042
  /**
6043
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
 
 
 
 
6044
  *
6045
  * @throws \InvalidArgumentException
 
 
6046
  */
6047
+ public function __construct(ParametersInterface $parameters)
6048
  {
6049
+ $this->assertExtensions();
 
 
 
 
6050
 
6051
+ if ($parameters->scheme !== 'http') {
6052
+ throw new \InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'.");
 
 
6053
  }
6054
 
6055
+ $this->parameters = $parameters;
6056
+
6057
+ $this->resource = $this->createCurl();
6058
+ $this->reader = $this->createReader();
6059
+ }
6060
 
6061
  /**
6062
+ * Frees the underlying cURL and protocol reader resources when the garbage
6063
+ * collector kicks in.
6064
  */
6065
+ public function __destruct()
6066
  {
6067
+ curl_close($this->resource);
6068
+ phpiredis_reader_destroy($this->reader);
6069
  }
6070
 
6071
  /**
6072
+ * Helper method used to throw on unsupported methods.
6073
+ *
6074
+ * @param string $method Name of the unsupported method.
6075
+ *
6076
+ * @throws NotSupportedException
6077
  */
6078
+ private function throwNotSupportedException($method)
6079
  {
6080
+ $class = __CLASS__;
6081
+ throw new NotSupportedException("The method $class::$method() is not supported.");
6082
  }
6083
 
6084
  /**
6085
+ * Checks if the cURL and phpiredis extensions are loaded in PHP.
6086
  */
6087
+ private function assertExtensions()
6088
  {
6089
+ if (!extension_loaded('curl')) {
6090
+ throw new NotSupportedException(
6091
+ 'The "curl" extension is required by this connection backend.'
6092
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
6093
  }
6094
 
6095
+ if (!extension_loaded('phpiredis')) {
6096
+ throw new NotSupportedException(
6097
+ 'The "phpiredis" extension is required by this connection backend.'
 
6098
  );
6099
  }
 
 
6100
  }
6101
 
6102
  /**
6103
+ * Initializes cURL.
6104
+ *
6105
+ * @return resource
6106
  */
6107
+ private function createCurl()
6108
  {
6109
+ $parameters = $this->getParameters();
6110
+ $timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0) * 1000;
6111
+
6112
+ if (filter_var($host = $parameters->host, FILTER_VALIDATE_IP)) {
6113
+ $host = "[$host]";
6114
+ }
6115
+
6116
+ $options = array(
6117
+ CURLOPT_FAILONERROR => true,
6118
+ CURLOPT_CONNECTTIMEOUT_MS => $timeout,
6119
+ CURLOPT_URL => "$parameters->scheme://$host:$parameters->port",
6120
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
6121
+ CURLOPT_POST => true,
6122
+ CURLOPT_WRITEFUNCTION => array($this, 'feedReader'),
6123
+ );
6124
+
6125
+ if (isset($parameters->user, $parameters->pass)) {
6126
+ $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}";
6127
  }
6128
+
6129
+ curl_setopt_array($resource = curl_init(), $options);
6130
+
6131
+ return $resource;
6132
  }
6133
 
6134
  /**
6135
+ * Initializes the phpiredis protocol reader.
 
 
6136
  *
6137
+ * @return resource
6138
  */
6139
+ private function createReader()
6140
  {
6141
+ $reader = phpiredis_reader_create();
6142
+
6143
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
6144
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
6145
+
6146
+ return $reader;
6147
  }
6148
 
6149
  /**
6150
+ * Returns the handler used by the protocol reader for inline responses.
6151
  *
6152
+ * @return \Closure
6153
  */
6154
+ protected function getStatusHandler()
6155
  {
6156
+ static $statusHandler;
6157
 
6158
+ if (!$statusHandler) {
6159
+ $statusHandler = function ($payload) {
6160
+ return StatusResponse::get($payload);
6161
+ };
6162
  }
6163
 
6164
+ return $statusHandler;
 
 
 
 
6165
  }
 
 
 
 
 
 
 
 
 
6166
 
 
 
 
 
 
 
 
 
 
6167
  /**
6168
+ * Returns the handler used by the protocol reader for error responses.
6169
  *
6170
+ * @return \Closure
6171
  */
6172
+ protected function getErrorHandler()
6173
+ {
6174
+ static $errorHandler;
6175
 
6176
+ if (!$errorHandler) {
6177
+ $errorHandler = function ($errorMessage) {
6178
+ return new ErrorResponse($errorMessage);
6179
+ };
6180
+ }
 
 
 
6181
 
6182
+ return $errorHandler;
6183
+ }
 
 
 
 
 
 
6184
 
6185
  /**
6186
+ * Feeds the phpredis reader resource with the data read from the network.
6187
  *
6188
+ * @param resource $resource Reader resource.
6189
+ * @param string $buffer Buffer of data read from a connection.
6190
  *
6191
+ * @return int
6192
  */
6193
+ protected function feedReader($resource, $buffer)
6194
+ {
6195
+ phpiredis_reader_feed($this->reader, $buffer);
6196
 
6197
+ return strlen($buffer);
6198
+ }
 
 
 
 
 
 
 
6199
 
6200
  /**
6201
+ * {@inheritdoc}
6202
  */
6203
+ public function connect()
6204
  {
6205
+ // NOOP
6206
  }
6207
 
 
 
 
 
 
 
 
 
6208
  /**
6209
  * {@inheritdoc}
6210
  */
6211
+ public function disconnect()
6212
  {
6213
+ // NOOP
6214
  }
6215
 
6216
  /**
6217
  * {@inheritdoc}
6218
  */
6219
+ public function isConnected()
6220
  {
 
 
 
 
 
 
6221
  return true;
6222
  }
6223
 
6224
  /**
6225
+ * Checks if the specified command is supported by this connection class.
 
6226
  *
6227
+ * @param CommandInterface $command Command instance.
6228
  *
6229
+ * @throws NotSupportedException
6230
+ *
6231
+ * @return string
6232
  */
6233
+ protected function getCommandId(CommandInterface $command)
6234
  {
6235
+ switch ($commandID = $command->getId()) {
6236
+ case 'AUTH':
6237
+ case 'SELECT':
6238
+ case 'MULTI':
6239
+ case 'EXEC':
6240
+ case 'WATCH':
6241
+ case 'UNWATCH':
6242
+ case 'DISCARD':
6243
+ case 'MONITOR':
6244
+ throw new NotSupportedException("Command '$commandID' is not allowed by Webdis.");
6245
+
6246
+ default:
6247
+ return $commandID;
6248
  }
6249
  }
6250
 
6251
  /**
6252
  * {@inheritdoc}
6253
  */
6254
+ public function writeRequest(CommandInterface $command)
6255
  {
6256
+ $this->throwNotSupportedException(__FUNCTION__);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6257
  }
6258
 
6259
  /**
6260
+ * {@inheritdoc}
6261
+ */
6262
+ public function readResponse(CommandInterface $command)
6263
+ {
6264
+ $this->throwNotSupportedException(__FUNCTION__);
6265
+ }
6266
+
6267
+ /**
6268
+ * {@inheritdoc}
6269
+ */
6270
+ public function executeCommand(CommandInterface $command)
6271
+ {
6272
+ $resource = $this->resource;
6273
+ $commandId = $this->getCommandId($command);
6274
+
6275
+ if ($arguments = $command->getArguments()) {
6276
+ $arguments = implode('/', array_map('urlencode', $arguments));
6277
+ $serializedCommand = "$commandId/$arguments.raw";
6278
+ } else {
6279
+ $serializedCommand = "$commandId.raw";
6280
+ }
6281
+
6282
+ curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand);
6283
+
6284
+ if (curl_exec($resource) === false) {
6285
+ $error = curl_error($resource);
6286
+ $errno = curl_errno($resource);
6287
+
6288
+ throw new ConnectionException($this, trim($error), $errno);
6289
+ }
6290
+
6291
+ if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) {
6292
+ throw new ProtocolException($this, phpiredis_reader_get_error($this->reader));
6293
+ }
6294
+
6295
+ return phpiredis_reader_get_reply($this->reader);
6296
+ }
6297
+
6298
+ /**
6299
+ * {@inheritdoc}
6300
+ */
6301
+ public function getResource()
6302
+ {
6303
+ return $this->resource;
6304
+ }
6305
+
6306
+ /**
6307
+ * {@inheritdoc}
6308
+ */
6309
+ public function getParameters()
6310
+ {
6311
+ return $this->parameters;
6312
+ }
6313
+
6314
+ /**
6315
+ * {@inheritdoc}
6316
+ */
6317
+ public function addConnectCommand(CommandInterface $command)
6318
+ {
6319
+ $this->throwNotSupportedException(__FUNCTION__);
6320
+ }
6321
+
6322
+ /**
6323
+ * {@inheritdoc}
6324
+ */
6325
+ public function read()
6326
+ {
6327
+ $this->throwNotSupportedException(__FUNCTION__);
6328
+ }
6329
+
6330
+ /**
6331
+ * {@inheritdoc}
6332
+ */
6333
+ public function __toString()
6334
+ {
6335
+ return "{$this->parameters->host}:{$this->parameters->port}";
6336
+ }
6337
+
6338
+ /**
6339
+ * {@inheritdoc}
6340
+ */
6341
+ public function __sleep()
6342
+ {
6343
+ return array('parameters');
6344
+ }
6345
+
6346
+ /**
6347
+ * {@inheritdoc}
6348
+ */
6349
+ public function __wakeup()
6350
+ {
6351
+ $this->assertExtensions();
6352
+
6353
+ $this->resource = $this->createCurl();
6354
+ $this->reader = $this->createReader();
6355
+ }
6356
+ }
6357
+
6358
+ /* --------------------------------------------------------------------------- */
6359
+
6360
+ namespace Predis\Profile;
6361
+
6362
+ use Predis\ClientException;
6363
+ use Predis\Command\CommandInterface;
6364
+ use Predis\Command\Processor\ProcessorInterface;
6365
+
6366
+ /**
6367
+ * A profile defines all the features and commands supported by certain versions
6368
+ * of Redis. Instances of Predis\Client should use a server profile matching the
6369
+ * version of Redis being used.
6370
+ *
6371
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6372
+ */
6373
+ interface ProfileInterface
6374
+ {
6375
+ /**
6376
+ * Returns the profile version corresponding to the Redis version.
6377
+ *
6378
+ * @return string
6379
+ */
6380
+ public function getVersion();
6381
+
6382
+ /**
6383
+ * Checks if the profile supports the specified command.
6384
+ *
6385
+ * @param string $commandID Command ID.
6386
+ *
6387
+ * @return bool
6388
+ */
6389
+ public function supportsCommand($commandID);
6390
+
6391
+ /**
6392
+ * Checks if the profile supports the specified list of commands.
6393
+ *
6394
+ * @param array $commandIDs List of command IDs.
6395
+ *
6396
+ * @return string
6397
+ */
6398
+ public function supportsCommands(array $commandIDs);
6399
+
6400
+ /**
6401
+ * Creates a new command instance.
6402
+ *
6403
+ * @param string $commandID Command ID.
6404
+ * @param array $arguments Arguments for the command.
6405
+ *
6406
+ * @return CommandInterface
6407
+ */
6408
+ public function createCommand($commandID, array $arguments = array());
6409
+ }
6410
+
6411
+ /**
6412
+ * Base class implementing common functionalities for Redis server profiles.
6413
+ *
6414
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6415
+ */
6416
+ abstract class RedisProfile implements ProfileInterface
6417
+ {
6418
+ private $commands;
6419
+ private $processor;
6420
+
6421
+ /**
6422
+ *
6423
+ */
6424
+ public function __construct()
6425
+ {
6426
+ $this->commands = $this->getSupportedCommands();
6427
+ }
6428
+
6429
+ /**
6430
+ * Returns a map of all the commands supported by the profile and their
6431
+ * actual PHP classes.
6432
+ *
6433
+ * @return array
6434
+ */
6435
+ abstract protected function getSupportedCommands();
6436
+
6437
+ /**
6438
+ * {@inheritdoc}
6439
+ */
6440
+ public function supportsCommand($commandID)
6441
+ {
6442
+ return isset($this->commands[strtoupper($commandID)]);
6443
+ }
6444
+
6445
+ /**
6446
+ * {@inheritdoc}
6447
+ */
6448
+ public function supportsCommands(array $commandIDs)
6449
+ {
6450
+ foreach ($commandIDs as $commandID) {
6451
+ if (!$this->supportsCommand($commandID)) {
6452
+ return false;
6453
+ }
6454
+ }
6455
+
6456
+ return true;
6457
+ }
6458
+
6459
+ /**
6460
+ * Returns the fully-qualified name of a class representing the specified
6461
+ * command ID registered in the current server profile.
6462
+ *
6463
+ * @param string $commandID Command ID.
6464
+ *
6465
+ * @return string|null
6466
+ */
6467
+ public function getCommandClass($commandID)
6468
+ {
6469
+ if (isset($this->commands[$commandID = strtoupper($commandID)])) {
6470
+ return $this->commands[$commandID];
6471
+ }
6472
+ }
6473
+
6474
+ /**
6475
+ * {@inheritdoc}
6476
+ */
6477
+ public function createCommand($commandID, array $arguments = array())
6478
+ {
6479
+ $commandID = strtoupper($commandID);
6480
+
6481
+ if (!isset($this->commands[$commandID])) {
6482
+ throw new ClientException("Command '$commandID' is not a registered Redis command.");
6483
+ }
6484
+
6485
+ $commandClass = $this->commands[$commandID];
6486
+ $command = new $commandClass();
6487
+ $command->setArguments($arguments);
6488
+
6489
+ if (isset($this->processor)) {
6490
+ $this->processor->process($command);
6491
+ }
6492
+
6493
+ return $command;
6494
+ }
6495
+
6496
+ /**
6497
+ * Defines a new command in the server profile.
6498
+ *
6499
+ * @param string $commandID Command ID.
6500
  * @param string $class Fully-qualified name of a Predis\Command\CommandInterface.
6501
  *
6502
  * @throws \InvalidArgumentException
6544
  *
6545
  * @author Daniele Alessandri <suppakilla@gmail.com>
6546
  */
6547
+ class RedisVersion320 extends RedisProfile
6548
  {
6549
  /**
6550
  * {@inheritdoc}
6551
  */
6552
  public function getVersion()
6553
  {
6554
+ return '3.2';
6555
  }
6556
 
6557
  /**
6791
  /* remote server control commands */
6792
  'COMMAND' => 'Predis\Command\ServerCommand',
6793
 
6794
+ /* ---------------- Redis 3.2 ---------------- */
6795
 
6796
+ /* commands operating on hashes */
6797
+ 'HSTRLEN' => 'Predis\Command\HashStringLength',
6798
+ 'BITFIELD' => 'Predis\Command\StringBitField',
6799
+
6800
+ /* commands performing geospatial operations */
6801
+ 'GEOADD' => 'Predis\Command\GeospatialGeoAdd',
6802
+ 'GEOHASH' => 'Predis\Command\GeospatialGeoHash',
6803
+ 'GEOPOS' => 'Predis\Command\GeospatialGeoPos',
6804
+ 'GEODIST' => 'Predis\Command\GeospatialGeoDist',
6805
+ 'GEORADIUS' => 'Predis\Command\GeospatialGeoRadius',
6806
+ 'GEORADIUSBYMEMBER' => 'Predis\Command\GeospatialGeoRadiusByMember',
6807
  );
6808
  }
6809
  }
6810
 
6811
  /**
6812
+ * Factory class for creating profile instances from strings.
6813
  *
6814
  * @author Daniele Alessandri <suppakilla@gmail.com>
6815
  */
6816
+ final class Factory
6817
  {
6818
+ private static $profiles = array(
6819
+ '2.0' => 'Predis\Profile\RedisVersion200',
6820
+ '2.2' => 'Predis\Profile\RedisVersion220',
6821
+ '2.4' => 'Predis\Profile\RedisVersion240',
6822
+ '2.6' => 'Predis\Profile\RedisVersion260',
6823
+ '2.8' => 'Predis\Profile\RedisVersion280',
6824
+ '3.0' => 'Predis\Profile\RedisVersion300',
6825
+ '3.2' => 'Predis\Profile\RedisVersion320',
6826
+ 'dev' => 'Predis\Profile\RedisUnstable',
6827
+ 'default' => 'Predis\Profile\RedisVersion320',
6828
+ );
6829
+
6830
+ /**
6831
+ *
6832
+ */
6833
+ private function __construct()
6834
+ {
6835
+ // NOOP
6836
+ }
6837
+
6838
+ /**
6839
+ * Returns the default server profile.
6840
+ *
6841
+ * @return ProfileInterface
6842
+ */
6843
+ public static function getDefault()
6844
+ {
6845
+ return self::get('default');
6846
+ }
6847
+
6848
+ /**
6849
+ * Returns the development server profile.
6850
+ *
6851
+ * @return ProfileInterface
6852
+ */
6853
+ public static function getDevelopment()
6854
+ {
6855
+ return self::get('dev');
6856
+ }
6857
+
6858
+ /**
6859
+ * Registers a new server profile.
6860
+ *
6861
+ * @param string $alias Profile version or alias.
6862
+ * @param string $class FQN of a class implementing Predis\Profile\ProfileInterface.
6863
+ *
6864
+ * @throws \InvalidArgumentException
6865
+ */
6866
+ public static function define($alias, $class)
6867
+ {
6868
+ $reflection = new \ReflectionClass($class);
6869
+
6870
+ if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) {
6871
+ throw new \InvalidArgumentException("The class '$class' is not a valid profile class.");
6872
+ }
6873
+
6874
+ self::$profiles[$alias] = $class;
6875
+ }
6876
+
6877
+ /**
6878
+ * Returns the specified server profile.
6879
+ *
6880
+ * @param string $version Profile version or alias.
6881
+ *
6882
+ * @throws ClientException
6883
+ *
6884
+ * @return ProfileInterface
6885
+ */
6886
+ public static function get($version)
6887
+ {
6888
+ if (!isset(self::$profiles[$version])) {
6889
+ throw new ClientException("Unknown server profile: '$version'.");
6890
+ }
6891
+
6892
+ $profile = self::$profiles[$version];
6893
+
6894
+ return new $profile();
6895
+ }
6896
+ }
6897
+
6898
+ /**
6899
+ * Server profile for the current unstable version of Redis.
6900
+ *
6901
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6902
+ */
6903
+ class RedisUnstable extends RedisVersion320
6904
+ {
6905
+ /**
6906
+ * {@inheritdoc}
6907
+ */
6908
+ public function getVersion()
6909
+ {
6910
+ return '3.2';
6911
+ }
6912
+
6913
+ /**
6914
+ * {@inheritdoc}
6915
+ */
6916
+ public function getSupportedCommands()
6917
+ {
6918
+ return array_merge(parent::getSupportedCommands(), array(
6919
+ // EMPTY
6920
+ ));
6921
+ }
6922
+ }
6923
+
6924
+ /**
6925
+ * Server profile for Redis 2.0.
6926
+ *
6927
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6928
+ */
6929
+ class RedisVersion200 extends RedisProfile
6930
+ {
6931
+ /**
6932
+ * {@inheritdoc}
6933
+ */
6934
+ public function getVersion()
6935
+ {
6936
+ return '2.0';
6937
  }
6938
 
6939
  /**
6957
  'TTL' => 'Predis\Command\KeyTimeToLive',
6958
  'MOVE' => 'Predis\Command\KeyMove',
6959
  'SORT' => 'Predis\Command\KeySort',
 
 
6960
 
6961
  /* commands operating on string values */
6962
  'SET' => 'Predis\Command\StringSet',
7019
  'QUIT' => 'Predis\Command\ConnectionQuit',
7020
 
7021
  /* remote server control commands */
7022
+ 'INFO' => 'Predis\Command\ServerInfo',
7023
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7024
  'MONITOR' => 'Predis\Command\ServerMonitor',
7025
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
7078
 
7079
  /* remote server control commands */
7080
  'CONFIG' => 'Predis\Command\ServerConfig',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7081
  );
7082
  }
7083
  }
7084
 
7085
  /**
7086
+ * Server profile for Redis 2.2.
7087
  *
7088
  * @author Daniele Alessandri <suppakilla@gmail.com>
7089
  */
7090
+ class RedisVersion220 extends RedisProfile
7091
  {
7092
  /**
7093
  * {@inheritdoc}
7094
  */
7095
  public function getVersion()
7096
  {
7097
+ return '2.2';
7098
  }
7099
 
7100
  /**
7118
  'TTL' => 'Predis\Command\KeyTimeToLive',
7119
  'MOVE' => 'Predis\Command\KeyMove',
7120
  'SORT' => 'Predis\Command\KeySort',
 
 
7121
 
7122
  /* commands operating on string values */
7123
  'SET' => 'Predis\Command\StringSet',
7180
  'QUIT' => 'Predis\Command\ConnectionQuit',
7181
 
7182
  /* remote server control commands */
7183
+ 'INFO' => 'Predis\Command\ServerInfo',
7184
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7185
  'MONITOR' => 'Predis\Command\ServerMonitor',
7186
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
7268
  /* remote server control commands */
7269
  'OBJECT' => 'Predis\Command\ServerObject',
7270
  'SLOWLOG' => 'Predis\Command\ServerSlowlog',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7271
  );
7272
  }
7273
  }
7468
  }
7469
 
7470
  /**
7471
+ * Server profile for Redis 2.6.
7472
  *
7473
  * @author Daniele Alessandri <suppakilla@gmail.com>
7474
  */
7475
+ class RedisVersion260 extends RedisProfile
7476
  {
7477
  /**
7478
  * {@inheritdoc}
7479
  */
7480
  public function getVersion()
7481
  {
7482
+ return '2.6';
7483
  }
7484
 
7485
  /**
7503
  'TTL' => 'Predis\Command\KeyTimeToLive',
7504
  'MOVE' => 'Predis\Command\KeyMove',
7505
  'SORT' => 'Predis\Command\KeySort',
7506
+ 'DUMP' => 'Predis\Command\KeyDump',
7507
+ 'RESTORE' => 'Predis\Command\KeyRestore',
7508
 
7509
  /* commands operating on string values */
7510
  'SET' => 'Predis\Command\StringSet',
7567
  'QUIT' => 'Predis\Command\ConnectionQuit',
7568
 
7569
  /* remote server control commands */
7570
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
7571
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7572
  'MONITOR' => 'Predis\Command\ServerMonitor',
7573
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
7626
 
7627
  /* remote server control commands */
7628
  'CONFIG' => 'Predis\Command\ServerConfig',
 
 
 
7629
 
7630
+ /* ---------------- Redis 2.2 ---------------- */
 
 
 
 
 
 
 
 
 
 
 
 
 
7631
 
7632
+ /* commands operating on the key space */
7633
+ 'PERSIST' => 'Predis\Command\KeyPersist',
 
 
 
 
 
7634
 
7635
+ /* commands operating on string values */
7636
+ 'STRLEN' => 'Predis\Command\StringStrlen',
7637
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
7638
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
7639
+ 'SETBIT' => 'Predis\Command\StringSetBit',
7640
+ 'GETBIT' => 'Predis\Command\StringGetBit',
7641
 
7642
+ /* commands operating on lists */
7643
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
7644
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
7645
+ 'LINSERT' => 'Predis\Command\ListInsert',
7646
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
 
 
 
 
 
 
 
 
 
 
 
 
7647
 
7648
+ /* commands operating on sorted sets */
7649
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
 
 
 
 
 
7650
 
7651
+ /* transactions */
7652
+ 'WATCH' => 'Predis\Command\TransactionWatch',
7653
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
 
 
 
 
 
 
7654
 
7655
+ /* remote server control commands */
7656
+ 'OBJECT' => 'Predis\Command\ServerObject',
7657
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
 
 
 
 
 
 
7658
 
7659
+ /* ---------------- Redis 2.4 ---------------- */
 
 
 
 
 
 
 
 
 
 
7660
 
7661
+ /* remote server control commands */
7662
+ 'CLIENT' => 'Predis\Command\ServerClient',
 
7663
 
7664
+ /* ---------------- Redis 2.6 ---------------- */
 
7665
 
7666
+ /* commands operating on the key space */
7667
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
7668
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
7669
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
7670
+ 'MIGRATE' => 'Predis\Command\KeyMigrate',
 
 
 
 
 
 
 
 
 
7671
 
7672
+ /* commands operating on string values */
7673
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
7674
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
7675
+ 'BITOP' => 'Predis\Command\StringBitOp',
7676
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
7677
 
7678
+ /* commands operating on hashes */
7679
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
7680
+
7681
+ /* scripting */
7682
+ 'EVAL' => 'Predis\Command\ServerEval',
7683
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
7684
+ 'SCRIPT' => 'Predis\Command\ServerScript',
7685
+
7686
+ /* remote server control commands */
7687
+ 'TIME' => 'Predis\Command\ServerTime',
7688
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
7689
+ );
7690
  }
7691
  }
7692
 
7693
  /**
7694
+ * Server profile for Redis 2.8.
7695
  *
7696
  * @author Daniele Alessandri <suppakilla@gmail.com>
7697
  */
7698
+ class RedisVersion280 extends RedisProfile
7699
  {
7700
  /**
7701
  * {@inheritdoc}
7702
  */
7703
  public function getVersion()
7704
  {
7705
+ return '2.8';
7706
  }
7707
 
7708
  /**
7726
  'TTL' => 'Predis\Command\KeyTimeToLive',
7727
  'MOVE' => 'Predis\Command\KeyMove',
7728
  'SORT' => 'Predis\Command\KeySort',
7729
+ 'DUMP' => 'Predis\Command\KeyDump',
7730
+ 'RESTORE' => 'Predis\Command\KeyRestore',
7731
 
7732
  /* commands operating on string values */
7733
  'SET' => 'Predis\Command\StringSet',
7790
  'QUIT' => 'Predis\Command\ConnectionQuit',
7791
 
7792
  /* remote server control commands */
7793
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
7794
  'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7795
  'MONITOR' => 'Predis\Command\ServerMonitor',
7796
  'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
7878
  /* remote server control commands */
7879
  'OBJECT' => 'Predis\Command\ServerObject',
7880
  'SLOWLOG' => 'Predis\Command\ServerSlowlog',
 
 
 
7881
 
7882
+ /* ---------------- Redis 2.4 ---------------- */
7883
 
7884
+ /* remote server control commands */
7885
+ 'CLIENT' => 'Predis\Command\ServerClient',
7886
 
7887
+ /* ---------------- Redis 2.6 ---------------- */
7888
+
7889
+ /* commands operating on the key space */
7890
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
7891
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
7892
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
7893
+ 'MIGRATE' => 'Predis\Command\KeyMigrate',
7894
+
7895
+ /* commands operating on string values */
7896
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
7897
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
7898
+ 'BITOP' => 'Predis\Command\StringBitOp',
7899
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
7900
+
7901
+ /* commands operating on hashes */
7902
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
7903
+
7904
+ /* scripting */
7905
+ 'EVAL' => 'Predis\Command\ServerEval',
7906
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
7907
+ 'SCRIPT' => 'Predis\Command\ServerScript',
7908
+
7909
+ /* remote server control commands */
7910
+ 'TIME' => 'Predis\Command\ServerTime',
7911
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
7912
+
7913
+ /* ---------------- Redis 2.8 ---------------- */
7914
+
7915
+ /* commands operating on the key space */
7916
+ 'SCAN' => 'Predis\Command\KeyScan',
7917
+
7918
+ /* commands operating on string values */
7919
+ 'BITPOS' => 'Predis\Command\StringBitPos',
7920
+
7921
+ /* commands operating on sets */
7922
+ 'SSCAN' => 'Predis\Command\SetScan',
7923
+
7924
+ /* commands operating on sorted sets */
7925
+ 'ZSCAN' => 'Predis\Command\ZSetScan',
7926
+ 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
7927
+ 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
7928
+ 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
7929
+ 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex',
7930
+
7931
+ /* commands operating on hashes */
7932
+ 'HSCAN' => 'Predis\Command\HashScan',
7933
+
7934
+ /* publish - subscribe */
7935
+ 'PUBSUB' => 'Predis\Command\PubSubPubsub',
7936
+
7937
+ /* commands operating on HyperLogLog */
7938
+ 'PFADD' => 'Predis\Command\HyperLogLogAdd',
7939
+ 'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
7940
+ 'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
7941
+
7942
+ /* remote server control commands */
7943
+ 'COMMAND' => 'Predis\Command\ServerCommand',
7944
+ );
7945
+ }
7946
+ }
7947
 
7948
  /**
7949
+ * Server profile for Redis 3.0.
7950
  *
7951
  * @author Daniele Alessandri <suppakilla@gmail.com>
7952
  */
7953
+ class RedisVersion300 extends RedisProfile
7954
  {
7955
+ /**
7956
+ * {@inheritdoc}
7957
+ */
7958
+ public function getVersion()
7959
+ {
7960
+ return '3.0';
7961
+ }
7962
+
7963
+ /**
7964
+ * {@inheritdoc}
7965
+ */
7966
+ public function getSupportedCommands()
7967
+ {
7968
+ return array(
7969
+ /* ---------------- Redis 1.2 ---------------- */
7970
+
7971
+ /* commands operating on the key space */
7972
+ 'EXISTS' => 'Predis\Command\KeyExists',
7973
+ 'DEL' => 'Predis\Command\KeyDelete',
7974
+ 'TYPE' => 'Predis\Command\KeyType',
7975
+ 'KEYS' => 'Predis\Command\KeyKeys',
7976
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
7977
+ 'RENAME' => 'Predis\Command\KeyRename',
7978
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
7979
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
7980
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
7981
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
7982
+ 'MOVE' => 'Predis\Command\KeyMove',
7983
+ 'SORT' => 'Predis\Command\KeySort',
7984
+ 'DUMP' => 'Predis\Command\KeyDump',
7985
+ 'RESTORE' => 'Predis\Command\KeyRestore',
7986
+
7987
+ /* commands operating on string values */
7988
+ 'SET' => 'Predis\Command\StringSet',
7989
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
7990
+ 'MSET' => 'Predis\Command\StringSetMultiple',
7991
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
7992
+ 'GET' => 'Predis\Command\StringGet',
7993
+ 'MGET' => 'Predis\Command\StringGetMultiple',
7994
+ 'GETSET' => 'Predis\Command\StringGetSet',
7995
+ 'INCR' => 'Predis\Command\StringIncrement',
7996
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
7997
+ 'DECR' => 'Predis\Command\StringDecrement',
7998
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
7999
+
8000
+ /* commands operating on lists */
8001
+ 'RPUSH' => 'Predis\Command\ListPushTail',
8002
+ 'LPUSH' => 'Predis\Command\ListPushHead',
8003
+ 'LLEN' => 'Predis\Command\ListLength',
8004
+ 'LRANGE' => 'Predis\Command\ListRange',
8005
+ 'LTRIM' => 'Predis\Command\ListTrim',
8006
+ 'LINDEX' => 'Predis\Command\ListIndex',
8007
+ 'LSET' => 'Predis\Command\ListSet',
8008
+ 'LREM' => 'Predis\Command\ListRemove',
8009
+ 'LPOP' => 'Predis\Command\ListPopFirst',
8010
+ 'RPOP' => 'Predis\Command\ListPopLast',
8011
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
8012
+
8013
+ /* commands operating on sets */
8014
+ 'SADD' => 'Predis\Command\SetAdd',
8015
+ 'SREM' => 'Predis\Command\SetRemove',
8016
+ 'SPOP' => 'Predis\Command\SetPop',
8017
+ 'SMOVE' => 'Predis\Command\SetMove',
8018
+ 'SCARD' => 'Predis\Command\SetCardinality',
8019
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
8020
+ 'SINTER' => 'Predis\Command\SetIntersection',
8021
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
8022
+ 'SUNION' => 'Predis\Command\SetUnion',
8023
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
8024
+ 'SDIFF' => 'Predis\Command\SetDifference',
8025
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
8026
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
8027
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
8028
+
8029
+ /* commands operating on sorted sets */
8030
+ 'ZADD' => 'Predis\Command\ZSetAdd',
8031
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
8032
+ 'ZREM' => 'Predis\Command\ZSetRemove',
8033
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
8034
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
8035
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
8036
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
8037
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
8038
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
8039
+
8040
+ /* connection related commands */
8041
+ 'PING' => 'Predis\Command\ConnectionPing',
8042
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
8043
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
8044
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
8045
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
8046
+
8047
+ /* remote server control commands */
8048
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
8049
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
8050
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
8051
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
8052
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
8053
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
8054
+ 'SAVE' => 'Predis\Command\ServerSave',
8055
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
8056
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
8057
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
8058
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
8059
+
8060
+ /* ---------------- Redis 2.0 ---------------- */
8061
+
8062
+ /* commands operating on string values */
8063
+ 'SETEX' => 'Predis\Command\StringSetExpire',
8064
+ 'APPEND' => 'Predis\Command\StringAppend',
8065
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
8066
+
8067
+ /* commands operating on lists */
8068
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
8069
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
8070
+
8071
+ /* commands operating on sorted sets */
8072
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
8073
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
8074
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
8075
+ 'ZRANK' => 'Predis\Command\ZSetRank',
8076
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
8077
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
8078
+
8079
+ /* commands operating on hashes */
8080
+ 'HSET' => 'Predis\Command\HashSet',
8081
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
8082
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
8083
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
8084
+ 'HGET' => 'Predis\Command\HashGet',
8085
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
8086
+ 'HDEL' => 'Predis\Command\HashDelete',
8087
+ 'HEXISTS' => 'Predis\Command\HashExists',
8088
+ 'HLEN' => 'Predis\Command\HashLength',
8089
+ 'HKEYS' => 'Predis\Command\HashKeys',
8090
+ 'HVALS' => 'Predis\Command\HashValues',
8091
+ 'HGETALL' => 'Predis\Command\HashGetAll',
8092
+
8093
+ /* transactions */
8094
+ 'MULTI' => 'Predis\Command\TransactionMulti',
8095
+ 'EXEC' => 'Predis\Command\TransactionExec',
8096
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
8097
+
8098
+ /* publish - subscribe */
8099
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
8100
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
8101
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
8102
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
8103
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
8104
+
8105
+ /* remote server control commands */
8106
+ 'CONFIG' => 'Predis\Command\ServerConfig',
8107
+
8108
+ /* ---------------- Redis 2.2 ---------------- */
8109
+
8110
+ /* commands operating on the key space */
8111
+ 'PERSIST' => 'Predis\Command\KeyPersist',
8112
+
8113
+ /* commands operating on string values */
8114
+ 'STRLEN' => 'Predis\Command\StringStrlen',
8115
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
8116
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
8117
+ 'SETBIT' => 'Predis\Command\StringSetBit',
8118
+ 'GETBIT' => 'Predis\Command\StringGetBit',
8119
+
8120
+ /* commands operating on lists */
8121
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
8122
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
8123
+ 'LINSERT' => 'Predis\Command\ListInsert',
8124
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
8125
+
8126
+ /* commands operating on sorted sets */
8127
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
8128
+
8129
+ /* transactions */
8130
+ 'WATCH' => 'Predis\Command\TransactionWatch',
8131
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
8132
+
8133
+ /* remote server control commands */
8134
+ 'OBJECT' => 'Predis\Command\ServerObject',
8135
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
8136
+
8137
+ /* ---------------- Redis 2.4 ---------------- */
8138
+
8139
+ /* remote server control commands */
8140
+ 'CLIENT' => 'Predis\Command\ServerClient',
8141
+
8142
+ /* ---------------- Redis 2.6 ---------------- */
8143
+
8144
+ /* commands operating on the key space */
8145
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
8146
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
8147
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
8148
+ 'MIGRATE' => 'Predis\Command\KeyMigrate',
8149
+
8150
+ /* commands operating on string values */
8151
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
8152
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
8153
+ 'BITOP' => 'Predis\Command\StringBitOp',
8154
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
8155
+
8156
+ /* commands operating on hashes */
8157
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
8158
+
8159
+ /* scripting */
8160
+ 'EVAL' => 'Predis\Command\ServerEval',
8161
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
8162
+ 'SCRIPT' => 'Predis\Command\ServerScript',
8163
+
8164
+ /* remote server control commands */
8165
+ 'TIME' => 'Predis\Command\ServerTime',
8166
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
8167
+
8168
+ /* ---------------- Redis 2.8 ---------------- */
8169
+
8170
+ /* commands operating on the key space */
8171
+ 'SCAN' => 'Predis\Command\KeyScan',
8172
+
8173
+ /* commands operating on string values */
8174
+ 'BITPOS' => 'Predis\Command\StringBitPos',
8175
+
8176
+ /* commands operating on sets */
8177
+ 'SSCAN' => 'Predis\Command\SetScan',
8178
+
8179
+ /* commands operating on sorted sets */
8180
+ 'ZSCAN' => 'Predis\Command\ZSetScan',
8181
+ 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
8182
+ 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
8183
+ 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
8184
+ 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex',
8185
+
8186
+ /* commands operating on hashes */
8187
+ 'HSCAN' => 'Predis\Command\HashScan',
8188
+
8189
+ /* publish - subscribe */
8190
+ 'PUBSUB' => 'Predis\Command\PubSubPubsub',
8191
+
8192
+ /* commands operating on HyperLogLog */
8193
+ 'PFADD' => 'Predis\Command\HyperLogLogAdd',
8194
+ 'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
8195
+ 'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
8196
+
8197
+ /* remote server control commands */
8198
+ 'COMMAND' => 'Predis\Command\ServerCommand',
8199
+
8200
+ /* ---------------- Redis 3.0 ---------------- */
8201
+
8202
+ );
8203
+ }
8204
+ }
8205
+
8206
+ /* --------------------------------------------------------------------------- */
8207
+
8208
+ namespace Predis;
8209
+
8210
+ use Predis\Command\CommandInterface;
8211
+ use Predis\Command\RawCommand;
8212
+ use Predis\Command\ScriptCommand;
8213
+ use Predis\Configuration\Options;
8214
+ use Predis\Configuration\OptionsInterface;
8215
+ use Predis\Connection\AggregateConnectionInterface;
8216
+ use Predis\Connection\ConnectionInterface;
8217
+ use Predis\Connection\ParametersInterface;
8218
+ use Predis\Monitor\Consumer as MonitorConsumer;
8219
+ use Predis\Pipeline\Pipeline;
8220
+ use Predis\PubSub\Consumer as PubSubConsumer;
8221
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
8222
+ use Predis\Response\ResponseInterface;
8223
+ use Predis\Response\ServerException;
8224
+ use Predis\Transaction\MultiExec as MultiExecTransaction;
8225
+ use Predis\Profile\ProfileInterface;
8226
+ use Predis\Connection\NodeConnectionInterface;
8227
+
8228
+ /**
8229
+ * Base exception class for Predis-related errors.
8230
+ *
8231
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8232
+ */
8233
+ abstract class PredisException extends \Exception
8234
+ {
8235
+ }
8236
+
8237
+ /**
8238
+ * Interface defining a client-side context such as a pipeline or transaction.
8239
+ *
8240
+ * @method $this del(array $keys)
8241
+ * @method $this dump($key)
8242
+ * @method $this exists($key)
8243
+ * @method $this expire($key, $seconds)
8244
+ * @method $this expireat($key, $timestamp)
8245
+ * @method $this keys($pattern)
8246
+ * @method $this move($key, $db)
8247
+ * @method $this object($subcommand, $key)
8248
+ * @method $this persist($key)
8249
+ * @method $this pexpire($key, $milliseconds)
8250
+ * @method $this pexpireat($key, $timestamp)
8251
+ * @method $this pttl($key)
8252
+ * @method $this randomkey()
8253
+ * @method $this rename($key, $target)
8254
+ * @method $this renamenx($key, $target)
8255
+ * @method $this scan($cursor, array $options = null)
8256
+ * @method $this sort($key, array $options = null)
8257
+ * @method $this ttl($key)
8258
+ * @method $this type($key)
8259
+ * @method $this append($key, $value)
8260
+ * @method $this bitcount($key, $start = null, $end = null)
8261
+ * @method $this bitop($operation, $destkey, $key)
8262
+ * @method $this bitfield($key, $subcommand, ...$subcommandArg)
8263
+ * @method $this decr($key)
8264
+ * @method $this decrby($key, $decrement)
8265
+ * @method $this get($key)
8266
+ * @method $this getbit($key, $offset)
8267
+ * @method $this getrange($key, $start, $end)
8268
+ * @method $this getset($key, $value)
8269
+ * @method $this incr($key)
8270
+ * @method $this incrby($key, $increment)
8271
+ * @method $this incrbyfloat($key, $increment)
8272
+ * @method $this mget(array $keys)
8273
+ * @method $this mset(array $dictionary)
8274
+ * @method $this msetnx(array $dictionary)
8275
+ * @method $this psetex($key, $milliseconds, $value)
8276
+ * @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
8277
+ * @method $this setbit($key, $offset, $value)
8278
+ * @method $this setex($key, $seconds, $value)
8279
+ * @method $this setnx($key, $value)
8280
+ * @method $this setrange($key, $offset, $value)
8281
+ * @method $this strlen($key)
8282
+ * @method $this hdel($key, array $fields)
8283
+ * @method $this hexists($key, $field)
8284
+ * @method $this hget($key, $field)
8285
+ * @method $this hgetall($key)
8286
+ * @method $this hincrby($key, $field, $increment)
8287
+ * @method $this hincrbyfloat($key, $field, $increment)
8288
+ * @method $this hkeys($key)
8289
+ * @method $this hlen($key)
8290
+ * @method $this hmget($key, array $fields)
8291
+ * @method $this hmset($key, array $dictionary)
8292
+ * @method $this hscan($key, $cursor, array $options = null)
8293
+ * @method $this hset($key, $field, $value)
8294
+ * @method $this hsetnx($key, $field, $value)
8295
+ * @method $this hvals($key)
8296
+ * @method $this hstrlen($key, $field)
8297
+ * @method $this blpop(array $keys, $timeout)
8298
+ * @method $this brpop(array $keys, $timeout)
8299
+ * @method $this brpoplpush($source, $destination, $timeout)
8300
+ * @method $this lindex($key, $index)
8301
+ * @method $this linsert($key, $whence, $pivot, $value)
8302
+ * @method $this llen($key)
8303
+ * @method $this lpop($key)
8304
+ * @method $this lpush($key, array $values)
8305
+ * @method $this lpushx($key, $value)
8306
+ * @method $this lrange($key, $start, $stop)
8307
+ * @method $this lrem($key, $count, $value)
8308
+ * @method $this lset($key, $index, $value)
8309
+ * @method $this ltrim($key, $start, $stop)
8310
+ * @method $this rpop($key)
8311
+ * @method $this rpoplpush($source, $destination)
8312
+ * @method $this rpush($key, array $values)
8313
+ * @method $this rpushx($key, $value)
8314
+ * @method $this sadd($key, array $members)
8315
+ * @method $this scard($key)
8316
+ * @method $this sdiff(array $keys)
8317
+ * @method $this sdiffstore($destination, array $keys)
8318
+ * @method $this sinter(array $keys)
8319
+ * @method $this sinterstore($destination, array $keys)
8320
+ * @method $this sismember($key, $member)
8321
+ * @method $this smembers($key)
8322
+ * @method $this smove($source, $destination, $member)
8323
+ * @method $this spop($key, $count = null)
8324
+ * @method $this srandmember($key, $count = null)
8325
+ * @method $this srem($key, $member)
8326
+ * @method $this sscan($key, $cursor, array $options = null)
8327
+ * @method $this sunion(array $keys)
8328
+ * @method $this sunionstore($destination, array $keys)
8329
+ * @method $this zadd($key, array $membersAndScoresDictionary)
8330
+ * @method $this zcard($key)
8331
+ * @method $this zcount($key, $min, $max)
8332
+ * @method $this zincrby($key, $increment, $member)
8333
+ * @method $this zinterstore($destination, array $keys, array $options = null)
8334
+ * @method $this zrange($key, $start, $stop, array $options = null)
8335
+ * @method $this zrangebyscore($key, $min, $max, array $options = null)
8336
+ * @method $this zrank($key, $member)
8337
+ * @method $this zrem($key, $member)
8338
+ * @method $this zremrangebyrank($key, $start, $stop)
8339
+ * @method $this zremrangebyscore($key, $min, $max)
8340
+ * @method $this zrevrange($key, $start, $stop, array $options = null)
8341
+ * @method $this zrevrangebyscore($key, $min, $max, array $options = null)
8342
+ * @method $this zrevrank($key, $member)
8343
+ * @method $this zunionstore($destination, array $keys, array $options = null)
8344
+ * @method $this zscore($key, $member)
8345
+ * @method $this zscan($key, $cursor, array $options = null)
8346
+ * @method $this zrangebylex($key, $start, $stop, array $options = null)
8347
+ * @method $this zrevrangebylex($key, $start, $stop, array $options = null)
8348
+ * @method $this zremrangebylex($key, $min, $max)
8349
+ * @method $this zlexcount($key, $min, $max)
8350
+ * @method $this pfadd($key, array $elements)
8351
+ * @method $this pfmerge($destinationKey, array $sourceKeys)
8352
+ * @method $this pfcount(array $keys)
8353
+ * @method $this pubsub($subcommand, $argument)
8354
+ * @method $this publish($channel, $message)
8355
+ * @method $this discard()
8356
+ * @method $this exec()
8357
+ * @method $this multi()
8358
+ * @method $this unwatch()
8359
+ * @method $this watch($key)
8360
+ * @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
8361
+ * @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
8362
+ * @method $this script($subcommand, $argument = null)
8363
+ * @method $this auth($password)
8364
+ * @method $this echo($message)
8365
+ * @method $this ping($message = null)
8366
+ * @method $this select($database)
8367
+ * @method $this bgrewriteaof()
8368
+ * @method $this bgsave()
8369
+ * @method $this client($subcommand, $argument = null)
8370
+ * @method $this config($subcommand, $argument = null)
8371
+ * @method $this dbsize()
8372
+ * @method $this flushall()
8373
+ * @method $this flushdb()
8374
+ * @method $this info($section = null)
8375
+ * @method $this lastsave()
8376
+ * @method $this save()
8377
+ * @method $this slaveof($host, $port)
8378
+ * @method $this slowlog($subcommand, $argument = null)
8379
+ * @method $this time()
8380
+ * @method $this command()
8381
+ * @method $this geoadd($key, $longitude, $latitude, $member)
8382
+ * @method $this geohash($key, array $members)
8383
+ * @method $this geopos($key, array $members)
8384
+ * @method $this geodist($key, $member1, $member2, $unit = null)
8385
+ * @method $this georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
8386
+ * @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null)
8387
+ *
8388
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8389
+ */
8390
+ interface ClientContextInterface
8391
+ {
8392
+ /**
8393
+ * Sends the specified command instance to Redis.
8394
+ *
8395
+ * @param CommandInterface $command Command instance.
8396
+ *
8397
+ * @return mixed
8398
+ */
8399
+ public function executeCommand(CommandInterface $command);
8400
+
8401
+ /**
8402
+ * Sends the specified command with its arguments to Redis.
8403
+ *
8404
+ * @param string $method Command ID.
8405
+ * @param array $arguments Arguments for the command.
8406
+ *
8407
+ * @return mixed
8408
+ */
8409
+ public function __call($method, $arguments);
8410
+
8411
+ /**
8412
+ * Starts the execution of the context.
8413
+ *
8414
+ * @param mixed $callable Optional callback for execution.
8415
+ *
8416
+ * @return array
8417
+ */
8418
+ public function execute($callable = null);
8419
+ }
8420
+
8421
+ /**
8422
+ * Base exception class for network-related errors.
8423
+ *
8424
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8425
+ */
8426
+ abstract class CommunicationException extends PredisException
8427
+ {
8428
+ private $connection;
8429
+
8430
+ /**
8431
+ * @param NodeConnectionInterface $connection Connection that generated the exception.
8432
+ * @param string $message Error message.
8433
+ * @param int $code Error code.
8434
+ * @param \Exception $innerException Inner exception for wrapping the original error.
8435
+ */
8436
+ public function __construct(
8437
+ NodeConnectionInterface $connection,
8438
+ $message = null,
8439
+ $code = null,
8440
+ \Exception $innerException = null
8441
+ ) {
8442
+ parent::__construct($message, $code, $innerException);
8443
+ $this->connection = $connection;
8444
+ }
8445
+
8446
+ /**
8447
+ * Gets the connection that generated the exception.
8448
+ *
8449
+ * @return NodeConnectionInterface
8450
+ */
8451
+ public function getConnection()
8452
+ {
8453
+ return $this->connection;
8454
+ }
8455
+
8456
+ /**
8457
+ * Indicates if the receiver should reset the underlying connection.
8458
+ *
8459
+ * @return bool
8460
+ */
8461
+ public function shouldResetConnection()
8462
+ {
8463
+ return true;
8464
+ }
8465
+
8466
+ /**
8467
+ * Helper method to handle exceptions generated by a connection object.
8468
+ *
8469
+ * @param CommunicationException $exception Exception.
8470
+ *
8471
+ * @throws CommunicationException
8472
+ */
8473
+ public static function handle(CommunicationException $exception)
8474
+ {
8475
+ if ($exception->shouldResetConnection()) {
8476
+ $connection = $exception->getConnection();
8477
+
8478
+ if ($connection->isConnected()) {
8479
+ $connection->disconnect();
8480
+ }
8481
+ }
8482
+
8483
+ throw $exception;
8484
+ }
8485
+ }
8486
+
8487
+ /**
8488
+ * Exception class that identifies client-side errors.
8489
+ *
8490
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8491
+ */
8492
+ class ClientException extends PredisException
8493
+ {
8494
+ }
8495
+
8496
+ /**
8497
+ * Interface defining a client able to execute commands against Redis.
8498
+ *
8499
+ * All the commands exposed by the client generally have the same signature as
8500
+ * described by the Redis documentation, but some of them offer an additional
8501
+ * and more friendly interface to ease programming which is described in the
8502
+ * following list of methods:
8503
+ *
8504
+ * @method int del(array $keys)
8505
+ * @method string dump($key)
8506
+ * @method int exists($key)
8507
+ * @method int expire($key, $seconds)
8508
+ * @method int expireat($key, $timestamp)
8509
+ * @method array keys($pattern)
8510
+ * @method int move($key, $db)
8511
+ * @method mixed object($subcommand, $key)
8512
+ * @method int persist($key)
8513
+ * @method int pexpire($key, $milliseconds)
8514
+ * @method int pexpireat($key, $timestamp)
8515
+ * @method int pttl($key)
8516
+ * @method string randomkey()
8517
+ * @method mixed rename($key, $target)
8518
+ * @method int renamenx($key, $target)
8519
+ * @method array scan($cursor, array $options = null)
8520
+ * @method array sort($key, array $options = null)
8521
+ * @method int ttl($key)
8522
+ * @method mixed type($key)
8523
+ * @method int append($key, $value)
8524
+ * @method int bitcount($key, $start = null, $end = null)
8525
+ * @method int bitop($operation, $destkey, $key)
8526
+ * @method array bitfield($key, $subcommand, ...$subcommandArg)
8527
+ * @method int decr($key)
8528
+ * @method int decrby($key, $decrement)
8529
+ * @method string get($key)
8530
+ * @method int getbit($key, $offset)
8531
+ * @method string getrange($key, $start, $end)
8532
+ * @method string getset($key, $value)
8533
+ * @method int incr($key)
8534
+ * @method int incrby($key, $increment)
8535
+ * @method string incrbyfloat($key, $increment)
8536
+ * @method array mget(array $keys)
8537
+ * @method mixed mset(array $dictionary)
8538
+ * @method int msetnx(array $dictionary)
8539
+ * @method mixed psetex($key, $milliseconds, $value)
8540
+ * @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
8541
+ * @method int setbit($key, $offset, $value)
8542
+ * @method int setex($key, $seconds, $value)
8543
+ * @method int setnx($key, $value)
8544
+ * @method int setrange($key, $offset, $value)
8545
+ * @method int strlen($key)
8546
+ * @method int hdel($key, array $fields)
8547
+ * @method int hexists($key, $field)
8548
+ * @method string hget($key, $field)
8549
+ * @method array hgetall($key)
8550
+ * @method int hincrby($key, $field, $increment)
8551
+ * @method string hincrbyfloat($key, $field, $increment)
8552
+ * @method array hkeys($key)
8553
+ * @method int hlen($key)
8554
+ * @method array hmget($key, array $fields)
8555
+ * @method mixed hmset($key, array $dictionary)
8556
+ * @method array hscan($key, $cursor, array $options = null)
8557
+ * @method int hset($key, $field, $value)
8558
+ * @method int hsetnx($key, $field, $value)
8559
+ * @method array hvals($key)
8560
+ * @method int hstrlen($key, $field)
8561
+ * @method array blpop(array $keys, $timeout)
8562
+ * @method array brpop(array $keys, $timeout)
8563
+ * @method array brpoplpush($source, $destination, $timeout)
8564
+ * @method string lindex($key, $index)
8565
+ * @method int linsert($key, $whence, $pivot, $value)
8566
+ * @method int llen($key)
8567
+ * @method string lpop($key)
8568
+ * @method int lpush($key, array $values)
8569
+ * @method int lpushx($key, $value)
8570
+ * @method array lrange($key, $start, $stop)
8571
+ * @method int lrem($key, $count, $value)
8572
+ * @method mixed lset($key, $index, $value)
8573
+ * @method mixed ltrim($key, $start, $stop)
8574
+ * @method string rpop($key)
8575
+ * @method string rpoplpush($source, $destination)
8576
+ * @method int rpush($key, array $values)
8577
+ * @method int rpushx($key, $value)
8578
+ * @method int sadd($key, array $members)
8579
+ * @method int scard($key)
8580
+ * @method array sdiff(array $keys)
8581
+ * @method int sdiffstore($destination, array $keys)
8582
+ * @method array sinter(array $keys)
8583
+ * @method int sinterstore($destination, array $keys)
8584
+ * @method int sismember($key, $member)
8585
+ * @method array smembers($key)
8586
+ * @method int smove($source, $destination, $member)
8587
+ * @method string spop($key, $count = null)
8588
+ * @method string srandmember($key, $count = null)
8589
+ * @method int srem($key, $member)
8590
+ * @method array sscan($key, $cursor, array $options = null)
8591
+ * @method array sunion(array $keys)
8592
+ * @method int sunionstore($destination, array $keys)
8593
+ * @method int zadd($key, array $membersAndScoresDictionary)
8594
+ * @method int zcard($key)
8595
+ * @method string zcount($key, $min, $max)
8596
+ * @method string zincrby($key, $increment, $member)
8597
+ * @method int zinterstore($destination, array $keys, array $options = null)
8598
+ * @method array zrange($key, $start, $stop, array $options = null)
8599
+ * @method array zrangebyscore($key, $min, $max, array $options = null)
8600
+ * @method int zrank($key, $member)
8601
+ * @method int zrem($key, $member)
8602
+ * @method int zremrangebyrank($key, $start, $stop)
8603
+ * @method int zremrangebyscore($key, $min, $max)
8604
+ * @method array zrevrange($key, $start, $stop, array $options = null)
8605
+ * @method array zrevrangebyscore($key, $max, $min, array $options = null)
8606
+ * @method int zrevrank($key, $member)
8607
+ * @method int zunionstore($destination, array $keys, array $options = null)
8608
+ * @method string zscore($key, $member)
8609
+ * @method array zscan($key, $cursor, array $options = null)
8610
+ * @method array zrangebylex($key, $start, $stop, array $options = null)
8611
+ * @method array zrevrangebylex($key, $start, $stop, array $options = null)
8612
+ * @method int zremrangebylex($key, $min, $max)
8613
+ * @method int zlexcount($key, $min, $max)
8614
+ * @method int pfadd($key, array $elements)
8615
+ * @method mixed pfmerge($destinationKey, array $sourceKeys)
8616
+ * @method int pfcount(array $keys)
8617
+ * @method mixed pubsub($subcommand, $argument)
8618
+ * @method int publish($channel, $message)
8619
+ * @method mixed discard()
8620
+ * @method array exec()
8621
+ * @method mixed multi()
8622
+ * @method mixed unwatch()
8623
+ * @method mixed watch($key)
8624
+ * @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
8625
+ * @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
8626
+ * @method mixed script($subcommand, $argument = null)
8627
+ * @method mixed auth($password)
8628
+ * @method string echo($message)
8629
+ * @method mixed ping($message = null)
8630
+ * @method mixed select($database)
8631
+ * @method mixed bgrewriteaof()
8632
+ * @method mixed bgsave()
8633
+ * @method mixed client($subcommand, $argument = null)
8634
+ * @method mixed config($subcommand, $argument = null)
8635
+ * @method int dbsize()
8636
+ * @method mixed flushall()
8637
+ * @method mixed flushdb()
8638
+ * @method array info($section = null)
8639
+ * @method int lastsave()
8640
+ * @method mixed save()
8641
+ * @method mixed slaveof($host, $port)
8642
+ * @method mixed slowlog($subcommand, $argument = null)
8643
+ * @method array time()
8644
+ * @method array command()
8645
+ * @method int geoadd($key, $longitude, $latitude, $member)
8646
+ * @method array geohash($key, array $members)
8647
+ * @method array geopos($key, array $members)
8648
+ * @method string geodist($key, $member1, $member2, $unit = null)
8649
+ * @method array georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
8650
+ * @method array georadiusbymember($key, $member, $radius, $unit, array $options = null)
8651
+ *
8652
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8653
+ */
8654
+ interface ClientInterface
8655
+ {
8656
+ /**
8657
+ * Returns the server profile used by the client.
8658
+ *
8659
+ * @return ProfileInterface
8660
+ */
8661
+ public function getProfile();
8662
+
8663
+ /**
8664
+ * Returns the client options specified upon initialization.
8665
+ *
8666
+ * @return OptionsInterface
8667
+ */
8668
+ public function getOptions();
8669
+
8670
+ /**
8671
+ * Opens the underlying connection to the server.
8672
+ */
8673
+ public function connect();
8674
+
8675
+ /**
8676
+ * Closes the underlying connection from the server.
8677
+ */
8678
+ public function disconnect();
8679
+
8680
+ /**
8681
+ * Returns the underlying connection instance.
8682
+ *
8683
+ * @return ConnectionInterface
8684
+ */
8685
+ public function getConnection();
8686
+
8687
+ /**
8688
+ * Creates a new instance of the specified Redis command.
8689
+ *
8690
+ * @param string $method Command ID.
8691
+ * @param array $arguments Arguments for the command.
8692
+ *
8693
+ * @return CommandInterface
8694
+ */
8695
+ public function createCommand($method, $arguments = array());
8696
+
8697
+ /**
8698
+ * Executes the specified Redis command.
8699
+ *
8700
+ * @param CommandInterface $command Command instance.
8701
+ *
8702
+ * @return mixed
8703
+ */
8704
+ public function executeCommand(CommandInterface $command);
8705
+
8706
+ /**
8707
+ * Creates a Redis command with the specified arguments and sends a request
8708
+ * to the server.
8709
+ *
8710
+ * @param string $method Command ID.
8711
+ * @param array $arguments Arguments for the command.
8712
+ *
8713
+ * @return mixed
8714
+ */
8715
+ public function __call($method, $arguments);
8716
+ }
8717
+
8718
+ /**
8719
+ * Implements a lightweight PSR-0 compliant autoloader for Predis.
8720
+ *
8721
+ * @author Eric Naeseth <eric@thumbtack.com>
8722
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8723
+ */
8724
+ class Autoloader
8725
+ {
8726
+ private $directory;
8727
+ private $prefix;
8728
+ private $prefixLength;
8729
+
8730
+ /**
8731
+ * @param string $baseDirectory Base directory where the source files are located.
8732
+ */
8733
+ public function __construct($baseDirectory = __DIR__)
8734
+ {
8735
+ $this->directory = $baseDirectory;
8736
+ $this->prefix = __NAMESPACE__.'\\';
8737
+ $this->prefixLength = strlen($this->prefix);
8738
+ }
8739
+
8740
+ /**
8741
+ * Registers the autoloader class with the PHP SPL autoloader.
8742
+ *
8743
+ * @param bool $prepend Prepend the autoloader on the stack instead of appending it.
8744
+ */
8745
+ public static function register($prepend = false)
8746
+ {
8747
+ spl_autoload_register(array(new self(), 'autoload'), true, $prepend);
8748
+ }
8749
+
8750
+ /**
8751
+ * Loads a class from a file using its fully qualified name.
8752
+ *
8753
+ * @param string $className Fully qualified name of a class.
8754
+ */
8755
+ public function autoload($className)
8756
+ {
8757
+ if (0 === strpos($className, $this->prefix)) {
8758
+ $parts = explode('\\', substr($className, $this->prefixLength));
8759
+ $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
8760
+
8761
+ if (is_file($filepath)) {
8762
+ require $filepath;
8763
+ }
8764
+ }
8765
+ }
8766
+ }
8767
+
8768
+ /**
8769
+ * Client class used for connecting and executing commands on Redis.
8770
+ *
8771
+ * This is the main high-level abstraction of Predis upon which various other
8772
+ * abstractions are built. Internally it aggregates various other classes each
8773
+ * one with its own responsibility and scope.
8774
+ *
8775
+ * {@inheritdoc}
8776
+ *
8777
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8778
+ */
8779
+ class Client implements ClientInterface, \IteratorAggregate
8780
+ {
8781
+ const VERSION = '1.1.1';
8782
+
8783
+ protected $connection;
8784
+ protected $options;
8785
+ private $profile;
8786
+
8787
+ /**
8788
+ * @param mixed $parameters Connection parameters for one or more servers.
8789
+ * @param mixed $options Options to configure some behaviours of the client.
8790
+ */
8791
+ public function __construct($parameters = null, $options = null)
8792
+ {
8793
+ $this->options = $this->createOptions($options ?: array());
8794
+ $this->connection = $this->createConnection($parameters ?: array());
8795
+ $this->profile = $this->options->profile;
8796
+ }
8797
+
8798
+ /**
8799
+ * Creates a new instance of Predis\Configuration\Options from different
8800
+ * types of arguments or simply returns the passed argument if it is an
8801
+ * instance of Predis\Configuration\OptionsInterface.
8802
+ *
8803
+ * @param mixed $options Client options.
8804
+ *
8805
+ * @throws \InvalidArgumentException
8806
+ *
8807
+ * @return OptionsInterface
8808
+ */
8809
+ protected function createOptions($options)
8810
+ {
8811
+ if (is_array($options)) {
8812
+ return new Options($options);
8813
+ }
8814
+
8815
+ if ($options instanceof OptionsInterface) {
8816
+ return $options;
8817
+ }
8818
+
8819
+ throw new \InvalidArgumentException('Invalid type for client options.');
8820
+ }
8821
+
8822
+ /**
8823
+ * Creates single or aggregate connections from different types of arguments
8824
+ * (string, array) or returns the passed argument if it is an instance of a
8825
+ * class implementing Predis\Connection\ConnectionInterface.
8826
+ *
8827
+ * Accepted types for connection parameters are:
8828
+ *
8829
+ * - Instance of Predis\Connection\ConnectionInterface.
8830
+ * - Instance of Predis\Connection\ParametersInterface.
8831
+ * - Array
8832
+ * - String
8833
+ * - Callable
8834
+ *
8835
+ * @param mixed $parameters Connection parameters or connection instance.
8836
+ *
8837
+ * @throws \InvalidArgumentException
8838
+ *
8839
+ * @return ConnectionInterface
8840
+ */
8841
+ protected function createConnection($parameters)
8842
+ {
8843
+ if ($parameters instanceof ConnectionInterface) {
8844
+ return $parameters;
8845
+ }
8846
+
8847
+ if ($parameters instanceof ParametersInterface || is_string($parameters)) {
8848
+ return $this->options->connections->create($parameters);
8849
+ }
8850
+
8851
+ if (is_array($parameters)) {
8852
+ if (!isset($parameters[0])) {
8853
+ return $this->options->connections->create($parameters);
8854
+ }
8855
+
8856
+ $options = $this->options;
8857
+
8858
+ if ($options->defined('aggregate')) {
8859
+ $initializer = $this->getConnectionInitializerWrapper($options->aggregate);
8860
+ $connection = $initializer($parameters, $options);
8861
+ } elseif ($options->defined('replication')) {
8862
+ $replication = $options->replication;
8863
+
8864
+ if ($replication instanceof AggregateConnectionInterface) {
8865
+ $connection = $replication;
8866
+ $options->connections->aggregate($connection, $parameters);
8867
+ } else {
8868
+ $initializer = $this->getConnectionInitializerWrapper($replication);
8869
+ $connection = $initializer($parameters, $options);
8870
+ }
8871
+ } else {
8872
+ $connection = $options->cluster;
8873
+ $options->connections->aggregate($connection, $parameters);
8874
+ }
8875
+
8876
+ return $connection;
8877
+ }
8878
+
8879
+ if (is_callable($parameters)) {
8880
+ $initializer = $this->getConnectionInitializerWrapper($parameters);
8881
+ $connection = $initializer($this->options);
8882
+
8883
+ return $connection;
8884
+ }
8885
+
8886
+ throw new \InvalidArgumentException('Invalid type for connection parameters.');
8887
+ }
8888
+
8889
+ /**
8890
+ * Wraps a callable to make sure that its returned value represents a valid
8891
+ * connection type.
8892
+ *
8893
+ * @param mixed $callable
8894
+ *
8895
+ * @return \Closure
8896
+ */
8897
+ protected function getConnectionInitializerWrapper($callable)
8898
+ {
8899
+ return function () use ($callable) {
8900
+ $connection = call_user_func_array($callable, func_get_args());
8901
+
8902
+ if (!$connection instanceof ConnectionInterface) {
8903
+ throw new \UnexpectedValueException(
8904
+ 'The callable connection initializer returned an invalid type.'
8905
+ );
8906
+ }
8907
+
8908
+ return $connection;
8909
+ };
8910
+ }
8911
+
8912
+ /**
8913
+ * {@inheritdoc}
8914
+ */
8915
+ public function getProfile()
8916
+ {
8917
+ return $this->profile;
8918
+ }
8919
+
8920
+ /**
8921
+ * {@inheritdoc}
8922
+ */
8923
+ public function getOptions()
8924
+ {
8925
+ return $this->options;
8926
+ }
8927
+
8928
+ /**
8929
+ * Creates a new client instance for the specified connection ID or alias,
8930
+ * only when working with an aggregate connection (cluster, replication).
8931
+ * The new client instances uses the same options of the original one.
8932
+ *
8933
+ * @param string $connectionID Identifier of a connection.
8934
+ *
8935
+ * @throws \InvalidArgumentException
8936
+ *
8937
+ * @return Client
8938
+ */
8939
+ public function getClientFor($connectionID)
8940
+ {
8941
+ if (!$connection = $this->getConnectionById($connectionID)) {
8942
+ throw new \InvalidArgumentException("Invalid connection ID: $connectionID.");
8943
+ }
8944
+
8945
+ return new static($connection, $this->options);
8946
+ }
8947
+
8948
+ /**
8949
+ * Opens the underlying connection and connects to the server.
8950
+ */
8951
+ public function connect()
8952
+ {
8953
+ $this->connection->connect();
8954
+ }
8955
+
8956
+ /**
8957
+ * Closes the underlying connection and disconnects from the server.
8958
+ */
8959
+ public function disconnect()
8960
+ {
8961
+ $this->connection->disconnect();
8962
+ }
8963
+
8964
+ /**
8965
+ * Closes the underlying connection and disconnects from the server.
8966
+ *
8967
+ * This is the same as `Client::disconnect()` as it does not actually send
8968
+ * the `QUIT` command to Redis, but simply closes the connection.
8969
+ */
8970
+ public function quit()
8971
+ {
8972
+ $this->disconnect();
8973
+ }
8974
+
8975
+ /**
8976
+ * Returns the current state of the underlying connection.
8977
+ *
8978
+ * @return bool
8979
+ */
8980
+ public function isConnected()
8981
+ {
8982
+ return $this->connection->isConnected();
8983
+ }
8984
+
8985
+ /**
8986
+ * {@inheritdoc}
8987
+ */
8988
+ public function getConnection()
8989
+ {
8990
+ return $this->connection;
8991
+ }
8992
+
8993
+ /**
8994
+ * Retrieves the specified connection from the aggregate connection when the
8995
+ * client is in cluster or replication mode.
8996
+ *
8997
+ * @param string $connectionID Index or alias of the single connection.
8998
+ *
8999
+ * @throws NotSupportedException
9000
+ *
9001
+ * @return Connection\NodeConnectionInterface
9002
+ */
9003
+ public function getConnectionById($connectionID)
9004
+ {
9005
+ if (!$this->connection instanceof AggregateConnectionInterface) {
9006
+ throw new NotSupportedException(
9007
+ 'Retrieving connections by ID is supported only by aggregate connections.'
9008
+ );
9009
+ }
9010
+
9011
+ return $this->connection->getConnectionById($connectionID);
9012
+ }
9013
+
9014
+ /**
9015
+ * Executes a command without filtering its arguments, parsing the response,
9016
+ * applying any prefix to keys or throwing exceptions on Redis errors even
9017
+ * regardless of client options.
9018
+ *
9019
+ * It is possible to identify Redis error responses from normal responses
9020
+ * using the second optional argument which is populated by reference.
9021
+ *
9022
+ * @param array $arguments Command arguments as defined by the command signature.
9023
+ * @param bool $error Set to TRUE when Redis returned an error response.
9024
+ *
9025
+ * @return mixed
9026
+ */
9027
+ public function executeRaw(array $arguments, &$error = null)
9028
+ {
9029
+ $error = false;
9030
+
9031
+ $response = $this->connection->executeCommand(
9032
+ new RawCommand($arguments)
9033
+ );
9034
+
9035
+ if ($response instanceof ResponseInterface) {
9036
+ if ($response instanceof ErrorResponseInterface) {
9037
+ $error = true;
9038
+ }
9039
+
9040
+ return (string) $response;
9041
+ }
9042
+
9043
+ return $response;
9044
+ }
9045
+
9046
+ /**
9047
+ * {@inheritdoc}
9048
+ */
9049
+ public function __call($commandID, $arguments)
9050
+ {
9051
+ return $this->executeCommand(
9052
+ $this->createCommand($commandID, $arguments)
9053
+ );
9054
+ }
9055
+
9056
+ /**
9057
+ * {@inheritdoc}
9058
+ */
9059
+ public function createCommand($commandID, $arguments = array())
9060
+ {
9061
+ return $this->profile->createCommand($commandID, $arguments);
9062
+ }
9063
+
9064
+ /**
9065
+ * {@inheritdoc}
9066
+ */
9067
+ public function executeCommand(CommandInterface $command)
9068
+ {
9069
+ $response = $this->connection->executeCommand($command);
9070
+
9071
+ if ($response instanceof ResponseInterface) {
9072
+ if ($response instanceof ErrorResponseInterface) {
9073
+ $response = $this->onErrorResponse($command, $response);
9074
+ }
9075
+
9076
+ return $response;
9077
+ }
9078
+
9079
+ return $command->parseResponse($response);
9080
+ }
9081
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9082
  /**
9083
+ * Handles -ERR responses returned by Redis.
9084
  *
9085
+ * @param CommandInterface $command Redis command that generated the error.
9086
+ * @param ErrorResponseInterface $response Instance of the error response.
9087
+ *
9088
+ * @throws ServerException
9089
  *
9090
  * @return mixed
9091
  */
9092
+ protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
9093
+ {
9094
+ if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
9095
+ $eval = $this->createCommand('EVAL');
9096
+ $eval->setRawArguments($command->getEvalArguments());
9097
+
9098
+ $response = $this->executeCommand($eval);
9099
+
9100
+ if (!$response instanceof ResponseInterface) {
9101
+ $response = $command->parseResponse($response);
9102
+ }
9103
+
9104
+ return $response;
9105
+ }
9106
+
9107
+ if ($this->options->exceptions) {
9108
+ throw new ServerException($response->getMessage());
9109
+ }
9110
+
9111
+ return $response;
9112
+ }
9113
 
9114
  /**
9115
+ * Executes the specified initializer method on `$this` by adjusting the
9116
+ * actual invokation depending on the arity (0, 1 or 2 arguments). This is
9117
+ * simply an utility method to create Redis contexts instances since they
9118
+ * follow a common initialization path.
9119
  *
9120
+ * @param string $initializer Method name.
9121
+ * @param array $argv Arguments for the method.
9122
  *
9123
  * @return mixed
9124
  */
9125
+ private function sharedContextFactory($initializer, $argv = null)
9126
+ {
9127
+ switch (count($argv)) {
9128
+ case 0:
9129
+ return $this->$initializer();
9130
+
9131
+ case 1:
9132
+ return is_array($argv[0])
9133
+ ? $this->$initializer($argv[0])
9134
+ : $this->$initializer(null, $argv[0]);
9135
+
9136
+ case 2:
9137
+ list($arg0, $arg1) = $argv;
9138
+
9139
+ return $this->$initializer($arg0, $arg1);
9140
+
9141
+ default:
9142
+ return $this->$initializer($this, $argv);
9143
+ }
9144
+ }
9145
 
9146
  /**
9147
+ * Creates a new pipeline context and returns it, or returns the results of
9148
+ * a pipeline executed inside the optionally provided callable object.
9149
  *
9150
+ * @param mixed ... Array of options, a callable for execution, or both.
9151
  *
9152
+ * @return Pipeline|array
9153
  */
9154
+ public function pipeline(/* arguments */)
9155
+ {
9156
+ return $this->sharedContextFactory('createPipeline', func_get_args());
9157
+ }
9158
 
9159
+ /**
9160
+ * Actual pipeline context initializer method.
9161
+ *
9162
+ * @param array $options Options for the context.
9163
+ * @param mixed $callable Optional callable used to execute the context.
9164
+ *
9165
+ * @return Pipeline|array
9166
+ */
9167
+ protected function createPipeline(array $options = null, $callable = null)
9168
+ {
9169
+ if (isset($options['atomic']) && $options['atomic']) {
9170
+ $class = 'Predis\Pipeline\Atomic';
9171
+ } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
9172
+ $class = 'Predis\Pipeline\FireAndForget';
9173
+ } else {
9174
+ $class = 'Predis\Pipeline\Pipeline';
9175
+ }
9176
+
9177
+ /*
9178
+ * @var ClientContextInterface
9179
+ */
9180
+ $pipeline = new $class($this);
9181
+
9182
+ if (isset($callable)) {
9183
+ return $pipeline->execute($callable);
9184
+ }
9185
+
9186
+ return $pipeline;
9187
+ }
9188
 
9189
  /**
9190
+ * Creates a new transaction context and returns it, or returns the results
9191
+ * of a transaction executed inside the optionally provided callable object.
9192
+ *
9193
+ * @param mixed ... Array of options, a callable for execution, or both.
9194
+ *
9195
+ * @return MultiExecTransaction|array
9196
  */
9197
+ public function transaction(/* arguments */)
9198
+ {
9199
+ return $this->sharedContextFactory('createTransaction', func_get_args());
 
 
 
 
 
9200
  }
9201
 
9202
  /**
9203
+ * Actual transaction context initializer method.
9204
  *
9205
+ * @param array $options Options for the context.
9206
+ * @param mixed $callable Optional callable used to execute the context.
9207
+ *
9208
+ * @return MultiExecTransaction|array
9209
  */
9210
+ protected function createTransaction(array $options = null, $callable = null)
9211
  {
9212
+ $transaction = new MultiExecTransaction($this, $options);
9213
+
9214
+ if (isset($callable)) {
9215
+ return $transaction->execute($callable);
9216
+ }
9217
+
9218
+ return $transaction;
9219
  }
9220
 
9221
  /**
9222
+ * Creates a new publish/subscribe context and returns it, or starts its loop
9223
+ * inside the optionally provided callable object.
9224
  *
9225
+ * @param mixed ... Array of options, a callable for execution, or both.
9226
+ *
9227
+ * @return PubSubConsumer|null
9228
  */
9229
+ public function pubSubLoop(/* arguments */)
9230
  {
9231
+ return $this->sharedContextFactory('createPubSub', func_get_args());
9232
+ }
9233
+
9234
+ /**
9235
+ * Actual publish/subscribe context initializer method.
9236
+ *
9237
+ * @param array $options Options for the context.
9238
+ * @param mixed $callable Optional callable used to execute the context.
9239
+ *
9240
+ * @return PubSubConsumer|null
9241
+ */
9242
+ protected function createPubSub(array $options = null, $callable = null)
9243
+ {
9244
+ $pubsub = new PubSubConsumer($this, $options);
9245
+
9246
+ if (!isset($callable)) {
9247
+ return $pubsub;
9248
+ }
9249
+
9250
+ foreach ($pubsub as $message) {
9251
+ if (call_user_func($callable, $pubsub, $message) === false) {
9252
+ $pubsub->stop();
9253
+ }
9254
+ }
9255
+ }
9256
+
9257
+ /**
9258
+ * Creates a new monitor consumer and returns it.
9259
+ *
9260
+ * @return MonitorConsumer
9261
+ */
9262
+ public function monitor()
9263
+ {
9264
+ return new MonitorConsumer($this);
9265
+ }
9266
+
9267
+ /**
9268
+ * {@inheritdoc}
9269
+ */
9270
+ public function getIterator()
9271
+ {
9272
+ $clients = array();
9273
+ $connection = $this->getConnection();
9274
+
9275
+ if (!$connection instanceof \Traversable) {
9276
+ throw new ClientException('The underlying connection is not traversable');
9277
+ }
9278
+
9279
+ foreach ($connection as $node) {
9280
+ $clients[(string) $node] = new static($node, $this->getOptions());
9281
+ }
9282
+
9283
+ return new \ArrayIterator($clients);
9284
  }
9285
+ }
9286
+
9287
+ /**
9288
+ * Exception class thrown when trying to use features not supported by certain
9289
+ * classes or abstractions of Predis.
9290
+ *
9291
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9292
+ */
9293
+ class NotSupportedException extends PredisException
9294
+ {
9295
+ }
9296
+
9297
+ /* --------------------------------------------------------------------------- */
9298
+
9299
+ namespace Predis\Configuration;
9300
+
9301
+ use Predis\Connection\Aggregate\ClusterInterface;
9302
+ use Predis\Connection\Aggregate\PredisCluster;
9303
+ use Predis\Connection\Aggregate\RedisCluster;
9304
+ use Predis\Connection\Factory;
9305
+ use Predis\Connection\FactoryInterface;
9306
+ use Predis\Command\Processor\KeyPrefixProcessor;
9307
+ use Predis\Command\Processor\ProcessorInterface;
9308
+ use Predis\Profile\Factory as Predis_Factory;
9309
+ use Predis\Profile\ProfileInterface;
9310
+ use Predis\Profile\RedisProfile;
9311
+ use Predis\Connection\Aggregate\MasterSlaveReplication;
9312
+ use Predis\Connection\Aggregate\ReplicationInterface;
9313
+ use Predis\Connection\Aggregate\SentinelReplication;
9314
+
9315
+ /**
9316
+ * Defines an handler used by Predis\Configuration\Options to filter, validate
9317
+ * or return default values for a given option.
9318
+ *
9319
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9320
+ */
9321
+ interface OptionInterface
9322
+ {
9323
+ /**
9324
+ * Filters and validates the passed value.
9325
+ *
9326
+ * @param OptionsInterface $options Options container.
9327
+ * @param mixed $value Input value.
9328
+ *
9329
+ * @return mixed
9330
+ */
9331
+ public function filter(OptionsInterface $options, $value);
9332
 
9333
  /**
9334
+ * Returns the default value for the option.
9335
  *
9336
+ * @param OptionsInterface $options Options container.
9337
  *
9338
+ * @return mixed
9339
  */
9340
+ public function getDefault(OptionsInterface $options);
 
 
 
 
 
 
 
 
 
 
 
9341
  }
9342
 
9343
  /**
9344
+ * Interface defining a container for client options.
 
 
 
 
 
9345
  *
9346
+ * @property-read mixed aggregate Custom connection aggregator.
9347
+ * @property-read mixed cluster Aggregate connection for clustering.
9348
+ * @property-read mixed connections Connection factory.
9349
+ * @property-read mixed exceptions Toggles exceptions in client for -ERR responses.
9350
+ * @property-read mixed prefix Key prefixing strategy using the given prefix.
9351
+ * @property-read mixed profile Server profile.
9352
+ * @property-read mixed replication Aggregate connection for replication.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9353
  *
9354
  * @author Daniele Alessandri <suppakilla@gmail.com>
9355
  */
9356
+ interface OptionsInterface
9357
  {
9358
  /**
9359
+ * Returns the default value for the given option.
9360
  *
9361
+ * @param string $option Name of the option.
9362
+ *
9363
+ * @return mixed|null
9364
  */
9365
+ public function getDefault($option);
9366
 
9367
  /**
9368
+ * Checks if the given option has been set by the user upon initialization.
9369
  *
9370
+ * @param string $option Name of the option.
9371
+ *
9372
+ * @return bool
9373
  */
9374
+ public function defined($option);
9375
 
9376
  /**
9377
+ * Checks if the given option has been set and does not evaluate to NULL.
9378
+ *
9379
+ * @param string $option Name of the option.
9380
+ *
9381
+ * @return bool
9382
  */
9383
+ public function __isset($option);
9384
 
9385
  /**
9386
+ * Returns the value of the given option.
9387
+ *
9388
+ * @param string $option Name of the option.
9389
+ *
9390
+ * @return mixed|null
9391
  */
9392
+ public function __get($option);
9393
+ }
9394
 
9395
+ /**
9396
+ * Configures an aggregate connection used for clustering
9397
+ * multiple Redis nodes using various implementations with
9398
+ * different algorithms or strategies.
9399
+ *
9400
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9401
+ */
9402
+ class ClusterOption implements OptionInterface
9403
+ {
9404
  /**
9405
+ * Creates a new cluster connection from on a known descriptive name.
9406
  *
9407
+ * @param OptionsInterface $options Instance of the client options.
9408
+ * @param string $id Descriptive identifier of the cluster type (`predis`, `redis-cluster`)
9409
+ *
9410
+ * @return ClusterInterface|null
9411
  */
9412
+ protected function createByDescription(OptionsInterface $options, $id)
9413
+ {
9414
+ switch ($id) {
9415
+ case 'predis':
9416
+ case 'predis-cluster':
9417
+ return new PredisCluster();
9418
+
9419
+ case 'redis':
9420
+ case 'redis-cluster':
9421
+ return new RedisCluster($options->connections);
9422
+
9423
+ default:
9424
+ return;
9425
+ }
9426
+ }
9427
 
9428
  /**
9429
+ * {@inheritdoc}
 
 
 
 
 
9430
  */
9431
+ public function filter(OptionsInterface $options, $value)
9432
+ {
9433
+ if (is_string($value)) {
9434
+ $value = $this->createByDescription($options, $value);
9435
+ }
9436
+
9437
+ if (!$value instanceof ClusterInterface) {
9438
+ throw new \InvalidArgumentException(
9439
+ "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected."
9440
+ );
9441
+ }
9442
 
9443
+ return $value;
9444
+ }
 
 
 
 
 
 
9445
 
9446
  /**
9447
+ * {@inheritdoc}
 
 
 
 
 
 
9448
  */
9449
+ public function getDefault(OptionsInterface $options)
9450
+ {
9451
+ return new PredisCluster();
9452
+ }
9453
  }
9454
 
9455
  /**
9456
+ * Configures a connection factory used by the client to create new connection
9457
+ * instances for single Redis nodes.
9458
  *
9459
  * @author Daniele Alessandri <suppakilla@gmail.com>
9460
  */
9461
+ class ConnectionFactoryOption implements OptionInterface
9462
  {
9463
+ /**
9464
+ * {@inheritdoc}
9465
+ */
9466
+ public function filter(OptionsInterface $options, $value)
9467
+ {
9468
+ if ($value instanceof FactoryInterface) {
9469
+ return $value;
9470
+ } elseif (is_array($value)) {
9471
+ $factory = $this->getDefault($options);
9472
+
9473
+ foreach ($value as $scheme => $initializer) {
9474
+ $factory->define($scheme, $initializer);
9475
+ }
9476
+
9477
+ return $factory;
9478
+ } else {
9479
+ throw new \InvalidArgumentException(
9480
+ 'Invalid value provided for the connections option.'
9481
+ );
9482
+ }
9483
+ }
9484
+
9485
+ /**
9486
+ * {@inheritdoc}
9487
+ */
9488
+ public function getDefault(OptionsInterface $options)
9489
+ {
9490
+ $factory = new Factory();
9491
+
9492
+ if ($options->defined('parameters')) {
9493
+ $factory->setDefaultParameters($options->parameters);
9494
+ }
9495
+
9496
+ return $factory;
9497
+ }
9498
  }
9499
 
9500
  /**
9501
+ * Configures whether consumers (such as the client) should throw exceptions on
9502
+ * Redis errors (-ERR responses) or just return instances of error responses.
9503
  *
9504
  * @author Daniele Alessandri <suppakilla@gmail.com>
9505
  */
9506
+ class ExceptionsOption implements OptionInterface
9507
  {
9508
+ /**
9509
+ * {@inheritdoc}
9510
+ */
9511
+ public function filter(OptionsInterface $options, $value)
9512
+ {
9513
+ return filter_var($value, FILTER_VALIDATE_BOOLEAN);
9514
+ }
9515
+
9516
+ /**
9517
+ * {@inheritdoc}
9518
+ */
9519
+ public function getDefault(OptionsInterface $options)
9520
+ {
9521
+ return true;
9522
+ }
9523
  }
9524
 
9525
  /**
9526
+ * Manages Predis options with filtering, conversion and lazy initialization of
9527
+ * values using a mini-DI container approach.
 
 
 
9528
  *
9529
  * {@inheritdoc}
9530
  *
9531
  * @author Daniele Alessandri <suppakilla@gmail.com>
9532
  */
9533
+ class Options implements OptionsInterface
9534
  {
9535
+ protected $input;
 
 
9536
  protected $options;
9537
+ protected $handlers;
9538
 
9539
  /**
9540
+ * @param array $options Array of options with their values
 
9541
  */
9542
+ public function __construct(array $options = array())
9543
  {
9544
+ $this->input = $options;
9545
+ $this->options = array();
9546
+ $this->handlers = $this->getHandlers();
9547
  }
9548
 
9549
  /**
9550
+ * Ensures that the default options are initialized.
 
 
 
 
 
 
9551
  *
9552
+ * @return array
9553
  */
9554
+ protected function getHandlers()
9555
  {
9556
+ return array(
9557
+ 'cluster' => 'Predis\Configuration\ClusterOption',
9558
+ 'connections' => 'Predis\Configuration\ConnectionFactoryOption',
9559
+ 'exceptions' => 'Predis\Configuration\ExceptionsOption',
9560
+ 'prefix' => 'Predis\Configuration\PrefixOption',
9561
+ 'profile' => 'Predis\Configuration\ProfileOption',
9562
+ 'replication' => 'Predis\Configuration\ReplicationOption',
9563
+ );
9564
+ }
9565
 
9566
+ /**
9567
+ * {@inheritdoc}
9568
+ */
9569
+ public function getDefault($option)
9570
+ {
9571
+ if (isset($this->handlers[$option])) {
9572
+ $handler = $this->handlers[$option];
9573
+ $handler = new $handler();
9574
+
9575
+ return $handler->getDefault($this);
9576
  }
9577
+ }
9578
 
9579
+ /**
9580
+ * {@inheritdoc}
9581
+ */
9582
+ public function defined($option)
9583
+ {
9584
+ return
9585
+ array_key_exists($option, $this->options) ||
9586
+ array_key_exists($option, $this->input)
9587
+ ;
9588
  }
9589
 
9590
  /**
9591
+ * {@inheritdoc}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9592
  */
9593
+ public function __isset($option)
9594
  {
9595
+ return (
9596
+ array_key_exists($option, $this->options) ||
9597
+ array_key_exists($option, $this->input)
9598
+ ) && $this->__get($option) !== null;
9599
+ }
9600
 
9601
+ /**
9602
+ * {@inheritdoc}
9603
+ */
9604
+ public function __get($option)
9605
+ {
9606
+ if (isset($this->options[$option]) || array_key_exists($option, $this->options)) {
9607
+ return $this->options[$option];
9608
  }
9609
 
9610
+ if (isset($this->input[$option]) || array_key_exists($option, $this->input)) {
9611
+ $value = $this->input[$option];
9612
+ unset($this->input[$option]);
 
 
 
9613
 
9614
+ if (is_object($value) && method_exists($value, '__invoke')) {
9615
+ $value = $value($this, $option);
9616
+ }
 
 
 
 
 
 
9617
 
9618
+ if (isset($this->handlers[$option])) {
9619
+ $handler = $this->handlers[$option];
9620
+ $handler = new $handler();
9621
+ $value = $handler->filter($this, $value);
9622
  }
9623
 
9624
+ return $this->options[$option] = $value;
9625
  }
9626
 
9627
+ if (isset($this->handlers[$option])) {
9628
+ return $this->options[$option] = $this->getDefault($option);
9629
+ }
9630
 
9631
+ return;
9632
+ }
9633
+ }
9634
+
9635
+ /**
9636
+ * Configures a command processor that apply the specified prefix string to a
9637
+ * series of Redis commands considered prefixable.
9638
+ *
9639
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9640
+ */
9641
+ class PrefixOption implements OptionInterface
9642
+ {
9643
+ /**
9644
+ * {@inheritdoc}
9645
+ */
9646
+ public function filter(OptionsInterface $options, $value)
9647
+ {
9648
+ if ($value instanceof ProcessorInterface) {
9649
+ return $value;
9650
  }
9651
 
9652
+ return new KeyPrefixProcessor($value);
9653
  }
9654
 
9655
  /**
9656
+ * {@inheritdoc}
9657
+ */
9658
+ public function getDefault(OptionsInterface $options)
9659
+ {
9660
+ // NOOP
9661
+ }
9662
+ }
9663
+
9664
+ /**
9665
+ * Configures the server profile to be used by the client to create command
9666
+ * instances depending on the specified version of the Redis server.
9667
+ *
9668
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9669
+ */
9670
+ class ProfileOption implements OptionInterface
9671
+ {
9672
+ /**
9673
+ * Sets the commands processors that need to be applied to the profile.
9674
  *
9675
+ * @param OptionsInterface $options Client options.
9676
+ * @param ProfileInterface $profile Server profile.
9677
  */
9678
+ protected function setProcessors(OptionsInterface $options, ProfileInterface $profile)
9679
  {
9680
+ if (isset($options->prefix) && $profile instanceof RedisProfile) {
9681
+ // NOTE: directly using __get('prefix') is actually a workaround for
9682
+ // HHVM 2.3.0. It's correct and respects the options interface, it's
9683
+ // just ugly. We will remove this hack when HHVM will fix re-entrant
9684
+ // calls to __get() once and for all.
 
 
 
9685
 
9686
+ $profile->setProcessor($options->__get('prefix'));
9687
+ }
9688
  }
9689
 
9690
  /**
9691
  * {@inheritdoc}
9692
  */
9693
+ public function filter(OptionsInterface $options, $value)
9694
  {
9695
+ if (is_string($value)) {
9696
+ $value = Predis_Factory::get($value);
9697
+ $this->setProcessors($options, $value);
9698
+ } elseif (!$value instanceof ProfileInterface) {
9699
+ throw new \InvalidArgumentException('Invalid value for the profile option.');
9700
+ }
9701
+
9702
+ return $value;
9703
  }
9704
 
9705
  /**
9706
  * {@inheritdoc}
9707
  */
9708
+ public function getDefault(OptionsInterface $options)
9709
  {
9710
+ $profile = Predis_Factory::getDefault();
9711
+ $this->setProcessors($options, $profile);
9712
+
9713
+ return $profile;
9714
  }
9715
+ }
9716
 
9717
+ /**
9718
+ * Configures an aggregate connection used for master/slave replication among
9719
+ * multiple Redis nodes.
9720
+ *
9721
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9722
+ */
9723
+ class ReplicationOption implements OptionInterface
9724
+ {
9725
  /**
9726
+ * {@inheritdoc}
 
 
 
 
 
 
9727
  *
9728
+ * @todo There's more code than needed due to a bug in filter_var() as
9729
+ * discussed here https://bugs.php.net/bug.php?id=49510 and different
9730
+ * behaviours when encountering NULL values on PHP 5.3.
9731
  */
9732
+ public function filter(OptionsInterface $options, $value)
9733
  {
9734
+ if ($value instanceof ReplicationInterface) {
9735
+ return $value;
9736
  }
9737
 
9738
+ if (is_bool($value) || $value === null) {
9739
+ return $value ? $this->getDefault($options) : null;
9740
+ }
9741
+
9742
+ if ($value === 'sentinel') {
9743
+ return function ($sentinels, $options) {
9744
+ return new SentinelReplication($options->service, $sentinels, $options->connections);
9745
+ };
9746
+ }
9747
+
9748
+ if (
9749
+ !is_object($value) &&
9750
+ null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)
9751
+ ) {
9752
+ return $asbool ? $this->getDefault($options) : null;
9753
+ }
9754
+
9755
+ throw new \InvalidArgumentException(
9756
+ "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected."
9757
+ );
9758
  }
9759
 
9760
  /**
9761
+ * {@inheritdoc}
9762
  */
9763
+ public function getDefault(OptionsInterface $options)
9764
  {
9765
+ $replication = new MasterSlaveReplication();
9766
+
9767
+ if ($options->autodiscovery) {
9768
+ $replication->setConnectionFactory($options->connections);
9769
+ $replication->setAutoDiscovery(true);
9770
+ }
9771
+
9772
+ return $replication;
9773
  }
9774
+ }
9775
+
9776
+ /* --------------------------------------------------------------------------- */
9777
+
9778
+ namespace Predis\Response;
9779
+
9780
+ use Predis\PredisException;
9781
+
9782
+ /**
9783
+ * Represents a complex response object from Redis.
9784
+ *
9785
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9786
+ */
9787
+ interface ResponseInterface
9788
+ {
9789
+ }
9790
 
9791
+ /**
9792
+ * Represents an error returned by Redis (responses identified by "-" in the
9793
+ * Redis protocol) during the execution of an operation on the server.
9794
+ *
9795
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9796
+ */
9797
+ interface ErrorInterface extends ResponseInterface
9798
+ {
9799
  /**
9800
+ * Returns the error message.
9801
+ *
9802
+ * @return string
9803
  */
9804
+ public function getMessage();
 
 
 
9805
 
9806
  /**
9807
+ * Returns the error type (e.g. ERR, ASK, MOVED).
9808
  *
9809
+ * @return string
 
9810
  */
9811
+ public function getErrorType();
9812
+ }
9813
+
9814
+ /**
9815
+ * Represents an error returned by Redis (-ERR responses) during the execution
9816
+ * of a command on the server.
9817
+ *
9818
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9819
+ */
9820
+ class Error implements ErrorInterface
9821
+ {
9822
+ private $message;
9823
+
9824
+ /**
9825
+ * @param string $message Error message returned by Redis
9826
+ */
9827
+ public function __construct($message)
9828
  {
9829
+ $this->message = $message;
9830
  }
9831
 
9832
  /**
9833
+ * {@inheritdoc}
 
 
9834
  */
9835
+ public function getMessage()
9836
  {
9837
+ return $this->message;
9838
  }
9839
 
9840
  /**
9841
  * {@inheritdoc}
9842
  */
9843
+ public function getErrorType()
9844
  {
9845
+ list($errorType) = explode(' ', $this->getMessage(), 2);
9846
+
9847
+ return $errorType;
9848
  }
9849
 
9850
  /**
9851
+ * Converts the object to its string representation.
 
 
 
 
 
9852
  *
9853
+ * @return string
9854
  */
9855
+ public function __toString()
9856
  {
9857
+ return $this->getMessage();
 
 
 
 
 
 
9858
  }
9859
+ }
9860
 
9861
+ /**
9862
+ * Exception class that identifies server-side Redis errors.
9863
+ *
9864
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9865
+ */
9866
+ class ServerException extends PredisException implements ErrorInterface
9867
+ {
9868
  /**
9869
+ * Gets the type of the error returned by Redis.
 
 
 
 
 
 
 
 
9870
  *
9871
+ * @return string
9872
  */
9873
+ public function getErrorType()
9874
  {
9875
+ list($errorType) = explode(' ', $this->getMessage(), 2);
9876
 
9877
+ return $errorType;
9878
+ }
 
9879
 
9880
+ /**
9881
+ * Converts the exception to an instance of Predis\Response\Error.
9882
+ *
9883
+ * @return Error
9884
+ */
9885
+ public function toErrorResponse()
9886
+ {
9887
+ return new Error($this->getMessage());
9888
+ }
9889
+ }
9890
 
9891
+ /**
9892
+ * Represents a status response returned by Redis.
9893
+ *
9894
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9895
+ */
9896
+ class Status implements ResponseInterface
9897
+ {
9898
+ private static $OK;
9899
+ private static $QUEUED;
9900
 
9901
+ private $payload;
 
9902
 
9903
  /**
9904
+ * @param string $payload Payload of the status response as returned by Redis.
9905
  */
9906
+ public function __construct($payload)
9907
  {
9908
+ $this->payload = $payload;
 
 
9909
  }
9910
 
9911
  /**
9912
+ * Converts the response object to its string representation.
9913
+ *
9914
+ * @return string
9915
  */
9916
+ public function __toString()
9917
  {
9918
+ return $this->payload;
9919
  }
9920
 
9921
  /**
9922
+ * Returns the payload of status response.
9923
+ *
9924
+ * @return string
9925
  */
9926
+ public function getPayload()
9927
  {
9928
+ return $this->payload;
 
 
 
 
 
 
 
 
 
 
9929
  }
9930
 
9931
  /**
9932
+ * Returns an instance of a status response object.
9933
  *
9934
+ * Common status responses such as OK or QUEUED are cached in order to lower
9935
+ * the global memory usage especially when using pipelines.
9936
  *
9937
+ * @param string $payload Status response payload.
9938
  *
9939
+ * @return string
9940
  */
9941
+ public static function get($payload)
9942
  {
9943
+ switch ($payload) {
9944
+ case 'OK':
9945
+ case 'QUEUED':
9946
+ if (isset(self::$$payload)) {
9947
+ return self::$$payload;
9948
+ }
9949
 
9950
+ return self::$$payload = new self($payload);
 
 
9951
 
9952
+ default:
9953
+ return new self($payload);
9954
  }
9955
+ }
9956
+ }
9957
 
9958
+ /* --------------------------------------------------------------------------- */
 
 
9959
 
9960
+ namespace Predis\Protocol\Text\Handler;
9961
+
9962
+ use Predis\CommunicationException;
9963
+ use Predis\Connection\CompositeConnectionInterface;
9964
+ use Predis\Protocol\ProtocolException;
9965
+ use Predis\Response\Error;
9966
+ use Predis\Response\Status;
9967
+ use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
9968
 
9969
+ /**
9970
+ * Defines a pluggable handler used to parse a particular type of response.
9971
+ *
9972
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9973
+ */
9974
+ interface ResponseHandlerInterface
9975
+ {
9976
  /**
9977
+ * Deserializes a response returned by Redis and reads more data from the
9978
+ * connection if needed.
 
 
9979
  *
9980
+ * @param CompositeConnectionInterface $connection Redis connection.
9981
+ * @param string $payload String payload.
9982
  *
9983
  * @return mixed
9984
  */
9985
+ public function handle(CompositeConnectionInterface $connection, $payload);
9986
+ }
 
 
 
9987
 
9988
+ /**
9989
+ * Handler for the bulk response type in the standard Redis wire protocol.
9990
+ * It translates the payload to a string or a NULL.
9991
+ *
9992
+ * @link http://redis.io/topics/protocol
9993
+ *
9994
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9995
+ */
9996
+ class BulkResponse implements ResponseHandlerInterface
9997
+ {
9998
+ /**
9999
+ * {@inheritdoc}
10000
+ */
10001
+ public function handle(CompositeConnectionInterface $connection, $payload)
10002
+ {
10003
+ $length = (int) $payload;
10004
 
10005
+ if ("$length" !== $payload) {
10006
+ CommunicationException::handle(new ProtocolException(
10007
+ $connection, "Cannot parse '$payload' as a valid length for a bulk response."
10008
+ ));
10009
+ }
10010
 
10011
+ if ($length >= 0) {
10012
+ return substr($connection->readBuffer($length + 2), 0, -2);
10013
+ }
10014
 
10015
+ if ($length == -1) {
10016
+ return;
10017
  }
10018
+
10019
+ CommunicationException::handle(new ProtocolException(
10020
+ $connection, "Value '$payload' is not a valid length for a bulk response."
10021
+ ));
10022
+
10023
+ return;
10024
  }
10025
+ }
10026
 
10027
+ /**
10028
+ * Handler for the error response type in the standard Redis wire protocol.
10029
+ * It translates the payload to a complex response object for Predis.
10030
+ *
10031
+ * @link http://redis.io/topics/protocol
10032
+ *
10033
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10034
+ */
10035
+ class ErrorResponse implements ResponseHandlerInterface
10036
+ {
10037
  /**
10038
+ * {@inheritdoc}
 
 
 
 
 
10039
  */
10040
+ public function handle(CompositeConnectionInterface $connection, $payload)
10041
  {
10042
+ return new Error($payload);
10043
  }
10044
+ }
10045
 
10046
+ /**
10047
+ * Handler for the integer response type in the standard Redis wire protocol.
10048
+ * It translates the payload an integer or NULL.
10049
+ *
10050
+ * @link http://redis.io/topics/protocol
10051
+ *
10052
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10053
+ */
10054
+ class IntegerResponse implements ResponseHandlerInterface
10055
+ {
10056
  /**
10057
+ * {@inheritdoc}
 
 
 
 
 
10058
  */
10059
+ public function handle(CompositeConnectionInterface $connection, $payload)
10060
  {
10061
+ if (is_numeric($payload)) {
10062
+ $integer = (int) $payload;
10063
+ return $integer == $payload ? $integer : $payload;
 
 
 
10064
  }
10065
 
10066
+ if ($payload !== 'nil') {
10067
+ CommunicationException::handle(new ProtocolException(
10068
+ $connection, "Cannot parse '$payload' as a valid numeric response."
10069
+ ));
 
 
 
10070
  }
10071
 
10072
+ return;
10073
  }
10074
+ }
10075
 
10076
+ /**
10077
+ * Handler for the multibulk response type in the standard Redis wire protocol.
10078
+ * It returns multibulk responses as PHP arrays.
10079
+ *
10080
+ * @link http://redis.io/topics/protocol
10081
+ *
10082
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10083
+ */
10084
+ class MultiBulkResponse implements ResponseHandlerInterface
10085
+ {
10086
  /**
10087
+ * {@inheritdoc}
 
 
 
 
 
10088
  */
10089
+ public function handle(CompositeConnectionInterface $connection, $payload)
10090
  {
10091
+ $length = (int) $payload;
 
10092
 
10093
+ if ("$length" !== $payload) {
10094
+ CommunicationException::handle(new ProtocolException(
10095
+ $connection, "Cannot parse '$payload' as a valid length of a multi-bulk response."
10096
+ ));
10097
+ }
10098
+
10099
+ if ($length === -1) {
10100
+ return;
10101
+ }
10102
+
10103
+ $list = array();
10104
+
10105
+ if ($length > 0) {
10106
+ $handlersCache = array();
10107
+ $reader = $connection->getProtocol()->getResponseReader();
10108
+
10109
+ for ($i = 0; $i < $length; ++$i) {
10110
+ $header = $connection->readLine();
10111
+ $prefix = $header[0];
10112
+
10113
+ if (isset($handlersCache[$prefix])) {
10114
+ $handler = $handlersCache[$prefix];
10115
+ } else {
10116
+ $handler = $reader->getHandler($prefix);
10117
+ $handlersCache[$prefix] = $handler;
10118
+ }
10119
 
10120
+ $list[$i] = $handler->handle($connection, substr($header, 1));
10121
+ }
10122
  }
10123
 
10124
+ return $list;
10125
  }
10126
+ }
10127
 
10128
+ /**
10129
+ * Handler for the status response type in the standard Redis wire protocol. It
10130
+ * translates certain classes of status response to PHP objects or just returns
10131
+ * the payload as a string.
10132
+ *
10133
+ * @link http://redis.io/topics/protocol
10134
+ *
10135
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10136
+ */
10137
+ class StatusResponse implements ResponseHandlerInterface
10138
+ {
10139
  /**
10140
+ * {@inheritdoc}
 
 
 
 
 
10141
  */
10142
+ public function handle(CompositeConnectionInterface $connection, $payload)
10143
  {
10144
+ return Status::get($payload);
10145
  }
10146
+ }
10147
 
10148
+ /**
10149
+ * Handler for the multibulk response type in the standard Redis wire protocol.
10150
+ * It returns multibulk responses as iterators that can stream bulk elements.
10151
+ *
10152
+ * Streamable multibulk responses are not globally supported by the abstractions
10153
+ * built-in into Predis, such as transactions or pipelines. Use them with care!
10154
+ *
10155
+ * @link http://redis.io/topics/protocol
10156
+ *
10157
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10158
+ */
10159
+ class StreamableMultiBulkResponse implements ResponseHandlerInterface
10160
+ {
10161
  /**
10162
+ * {@inheritdoc}
 
 
 
 
 
10163
  */
10164
+ public function handle(CompositeConnectionInterface $connection, $payload)
10165
  {
10166
+ $length = (int) $payload;
 
 
 
 
10167
 
10168
+ if ("$length" != $payload) {
10169
+ CommunicationException::handle(new ProtocolException(
10170
+ $connection, "Cannot parse '$payload' as a valid length for a multi-bulk response."
10171
+ ));
10172
  }
 
10173
 
10174
+ return new MultiBulkIterator($connection, $length);
 
 
 
 
 
 
 
10175
  }
10176
  }
10177
 
10178
+ /* --------------------------------------------------------------------------- */
10179
+
10180
+ namespace Predis\Collection\Iterator;
10181
+
10182
+ use Predis\ClientInterface;
10183
+ use Predis\NotSupportedException;
10184
+
10185
  /**
10186
+ * Provides the base implementation for a fully-rewindable PHP iterator that can
10187
+ * incrementally iterate over cursor-based collections stored on Redis using the
10188
+ * commands in the `SCAN` family.
10189
+ *
10190
+ * Given their incremental nature with multiple fetches, these kind of iterators
10191
+ * offer limited guarantees about the returned elements because the collection
10192
+ * can change several times during the iteration process.
10193
+ *
10194
+ * @see http://redis.io/commands/scan
10195
  *
 
10196
  * @author Daniele Alessandri <suppakilla@gmail.com>
10197
  */
10198
+ abstract class CursorBasedIterator implements \Iterator
10199
  {
10200
+ protected $client;
10201
+ protected $match;
10202
+ protected $count;
10203
+
10204
+ protected $valid;
10205
+ protected $fetchmore;
10206
+ protected $elements;
10207
+ protected $cursor;
10208
+ protected $position;
10209
+ protected $current;
10210
 
10211
  /**
10212
+ * @param ClientInterface $client Client connected to Redis.
10213
+ * @param string $match Pattern to match during the server-side iteration.
10214
+ * @param int $count Hint used by Redis to compute the number of results per iteration.
10215
  */
10216
+ public function __construct(ClientInterface $client, $match = null, $count = null)
10217
  {
10218
+ $this->client = $client;
10219
+ $this->match = $match;
10220
+ $this->count = $count;
10221
+
10222
+ $this->reset();
10223
  }
10224
 
10225
  /**
10226
+ * Ensures that the client supports the specified Redis command required to
10227
+ * fetch elements from the server to perform the iteration.
10228
  *
10229
+ * @param ClientInterface $client Client connected to Redis.
10230
+ * @param string $commandID Command ID.
10231
+ *
10232
+ * @throws NotSupportedException
10233
  */
10234
+ protected function requiredCommand(ClientInterface $client, $commandID)
10235
  {
10236
+ if (!$client->getProfile()->supportsCommand($commandID)) {
10237
+ throw new NotSupportedException("The current profile does not support '$commandID'.");
10238
+ }
10239
  }
10240
 
10241
  /**
10242
+ * Resets the inner state of the iterator.
10243
+ */
10244
+ protected function reset()
10245
+ {
10246
+ $this->valid = true;
10247
+ $this->fetchmore = true;
10248
+ $this->elements = array();
10249
+ $this->cursor = 0;
10250
+ $this->position = -1;
10251
+ $this->current = null;
10252
+ }
10253
+
10254
+ /**
10255
+ * Returns an array of options for the `SCAN` command.
10256
  *
10257
+ * @return array
10258
  */
10259
+ protected function getScanOptions()
10260
  {
10261
+ $options = array();
 
 
10262
 
10263
+ if (strlen($this->match) > 0) {
10264
+ $options['MATCH'] = $this->match;
 
10265
  }
 
 
 
 
10266
 
10267
+ if ($this->count > 0) {
10268
+ $options['COUNT'] = $this->count;
10269
+ }
10270
 
10271
+ return $options;
10272
+ }
 
 
 
 
 
 
 
 
 
 
10273
 
 
 
 
 
 
 
 
 
10274
  /**
10275
+ * Fetches a new set of elements from the remote collection, effectively
10276
+ * advancing the iteration process.
 
 
10277
  *
10278
+ * @return array
10279
  */
10280
+ abstract protected function executeCommand();
10281
 
10282
  /**
10283
+ * Populates the local buffer of elements fetched from the server during
10284
+ * the iteration.
 
 
 
10285
  */
10286
+ protected function fetch()
10287
+ {
10288
+ list($cursor, $elements) = $this->executeCommand();
10289
+
10290
+ if (!$cursor) {
10291
+ $this->fetchmore = false;
10292
+ }
10293
+
10294
+ $this->cursor = $cursor;
10295
+ $this->elements = $elements;
10296
+ }
10297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10298
  /**
10299
+ * Extracts next values for key() and current().
 
 
 
 
10300
  */
10301
+ protected function extractNext()
10302
+ {
10303
+ ++$this->position;
10304
+ $this->current = array_shift($this->elements);
10305
+ }
10306
 
10307
  /**
10308
+ * {@inheritdoc}
 
 
 
 
10309
  */
10310
+ public function rewind()
10311
+ {
10312
+ $this->reset();
10313
+ $this->next();
10314
+ }
10315
 
10316
  /**
10317
+ * {@inheritdoc}
 
 
 
 
10318
  */
10319
+ public function current()
10320
+ {
10321
+ return $this->current;
10322
+ }
10323
 
10324
  /**
10325
+ * {@inheritdoc}
 
 
 
 
10326
  */
10327
+ public function key()
10328
+ {
10329
+ return $this->position;
10330
+ }
10331
 
 
 
 
 
 
 
 
 
10332
  /**
10333
  * {@inheritdoc}
10334
  */
10335
+ public function next()
10336
  {
10337
+ tryFetch: {
10338
+ if (!$this->elements && $this->fetchmore) {
10339
+ $this->fetch();
10340
+ }
10341
 
10342
+ if ($this->elements) {
10343
+ $this->extractNext();
10344
+ } elseif ($this->cursor) {
10345
+ goto tryFetch;
10346
+ } else {
10347
+ $this->valid = false;
10348
+ }
10349
+ }
10350
  }
10351
 
10352
  /**
10353
  * {@inheritdoc}
10354
  */
10355
+ public function valid()
10356
  {
10357
+ return $this->valid;
10358
  }
10359
  }
10360
 
10361
  /**
10362
+ * Abstracts the iteration of fields and values of an hash by leveraging the
10363
+ * HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
10364
  *
10365
  * @author Daniele Alessandri <suppakilla@gmail.com>
10366
+ *
10367
+ * @link http://redis.io/commands/scan
10368
  */
10369
+ class HashKey extends CursorBasedIterator
10370
  {
10371
+ protected $key;
10372
+
10373
  /**
10374
+ * {@inheritdoc}
 
 
 
10375
  */
10376
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
10377
  {
10378
+ $this->requiredCommand($client, 'HSCAN');
 
 
 
 
10379
 
10380
+ parent::__construct($client, $match, $count);
10381
+
10382
+ $this->key = $key;
10383
  }
10384
 
10385
  /**
10386
  * {@inheritdoc}
10387
  */
10388
+ protected function executeCommand()
10389
  {
10390
+ return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
 
 
 
 
 
 
 
10391
  }
10392
 
10393
  /**
10394
  * {@inheritdoc}
10395
  */
10396
+ protected function extractNext()
10397
  {
10398
+ if ($kv = each($this->elements)) {
10399
+ $this->position = $kv[0];
10400
+ $this->current = $kv[1];
10401
 
10402
+ unset($this->elements[$this->position]);
10403
+ }
10404
  }
10405
  }
10406
 
10407
  /**
10408
+ * Abstracts the iteration of the keyspace on a Redis instance by leveraging the
10409
+ * SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
10410
  *
10411
  * @author Daniele Alessandri <suppakilla@gmail.com>
10412
+ *
10413
+ * @link http://redis.io/commands/scan
10414
  */
10415
+ class Keyspace extends CursorBasedIterator
10416
  {
10417
  /**
10418
  * {@inheritdoc}
 
 
 
 
10419
  */
10420
+ public function __construct(ClientInterface $client, $match = null, $count = null)
10421
  {
10422
+ $this->requiredCommand($client, 'SCAN');
 
 
 
 
 
 
 
 
 
 
 
 
 
10423
 
10424
+ parent::__construct($client, $match, $count);
 
 
10425
  }
10426
 
10427
  /**
10428
  * {@inheritdoc}
10429
  */
10430
+ protected function executeCommand()
10431
  {
10432
+ return $this->client->scan($this->cursor, $this->getScanOptions());
10433
  }
10434
  }
10435
 
10436
  /**
10437
+ * Abstracts the iteration of items stored in a list by leveraging the LRANGE
10438
+ * command wrapped in a fully-rewindable PHP iterator.
10439
  *
10440
+ * This iterator tries to emulate the behaviour of cursor-based iterators based
10441
+ * on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
10442
+ * to its incremental nature with multiple fetches it can only offer limited
10443
+ * guarantees on the returned elements because the collection can change several
10444
+ * times (trimmed, deleted, overwritten) during the iteration process.
10445
  *
10446
  * @author Daniele Alessandri <suppakilla@gmail.com>
10447
+ *
10448
+ * @link http://redis.io/commands/lrange
10449
  */
10450
+ class ListKey implements \Iterator
10451
  {
10452
+ protected $client;
10453
+ protected $count;
10454
+ protected $key;
10455
+
10456
+ protected $valid;
10457
+ protected $fetchmore;
10458
+ protected $elements;
10459
+ protected $position;
10460
+ protected $current;
10461
 
10462
  /**
10463
+ * @param ClientInterface $client Client connected to Redis.
10464
+ * @param string $key Redis list key.
10465
+ * @param int $count Number of items retrieved on each fetch operation.
10466
+ *
10467
+ * @throws \InvalidArgumentException
10468
  */
10469
+ public function __construct(ClientInterface $client, $key, $count = 10)
10470
  {
10471
+ $this->requiredCommand($client, 'LRANGE');
10472
+
10473
+ if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
10474
+ throw new \InvalidArgumentException('The $count argument must be a positive integer.');
10475
+ }
10476
+
10477
+ $this->client = $client;
10478
+ $this->key = $key;
10479
+ $this->count = $count;
10480
+
10481
+ $this->reset();
10482
  }
10483
 
10484
  /**
10485
+ * Ensures that the client instance supports the specified Redis command
10486
+ * required to fetch elements from the server to perform the iteration.
10487
+ *
10488
+ * @param ClientInterface $client Client connected to Redis.
10489
+ * @param string $commandID Command ID.
10490
+ *
10491
+ * @throws NotSupportedException
10492
+ */
10493
+ protected function requiredCommand(ClientInterface $client, $commandID)
10494
+ {
10495
+ if (!$client->getProfile()->supportsCommand($commandID)) {
10496
+ throw new NotSupportedException("The current profile does not support '$commandID'.");
10497
+ }
10498
+ }
10499
+
10500
+ /**
10501
+ * Resets the inner state of the iterator.
10502
+ */
10503
+ protected function reset()
10504
+ {
10505
+ $this->valid = true;
10506
+ $this->fetchmore = true;
10507
+ $this->elements = array();
10508
+ $this->position = -1;
10509
+ $this->current = null;
10510
+ }
10511
+
10512
+ /**
10513
+ * Fetches a new set of elements from the remote collection, effectively
10514
+ * advancing the iteration process.
10515
  *
10516
  * @return array
10517
  */
10518
+ protected function executeCommand()
10519
  {
10520
+ return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
 
 
 
 
 
 
 
10521
  }
10522
 
10523
  /**
10524
+ * Populates the local buffer of elements fetched from the server during the
10525
+ * iteration.
10526
  */
10527
+ protected function fetch()
10528
  {
10529
+ $elements = $this->executeCommand();
 
 
10530
 
10531
+ if (count($elements) < $this->count) {
10532
+ $this->fetchmore = false;
10533
  }
10534
+
10535
+ $this->elements = $elements;
10536
+ }
10537
+
10538
+ /**
10539
+ * Extracts next values for key() and current().
10540
+ */
10541
+ protected function extractNext()
10542
+ {
10543
+ ++$this->position;
10544
+ $this->current = array_shift($this->elements);
10545
  }
10546
 
10547
  /**
10548
  * {@inheritdoc}
10549
  */
10550
+ public function rewind()
10551
  {
10552
+ $this->reset();
10553
+ $this->next();
 
 
10554
  }
10555
 
10556
  /**
10557
  * {@inheritdoc}
10558
  */
10559
+ public function current()
10560
  {
10561
+ return $this->current;
 
 
 
10562
  }
10563
 
10564
  /**
10565
  * {@inheritdoc}
10566
  */
10567
+ public function key()
10568
  {
10569
+ return $this->position;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10570
  }
 
10571
 
 
 
 
 
 
 
 
 
10572
  /**
10573
  * {@inheritdoc}
10574
  */
10575
+ public function next()
10576
  {
10577
+ if (!$this->elements && $this->fetchmore) {
10578
+ $this->fetch();
10579
+ }
 
 
 
 
 
10580
 
10581
+ if ($this->elements) {
10582
+ $this->extractNext();
10583
  } else {
10584
+ $this->valid = false;
 
 
10585
  }
10586
  }
10587
 
10588
  /**
10589
  * {@inheritdoc}
10590
  */
10591
+ public function valid()
10592
  {
10593
+ return $this->valid;
10594
  }
10595
  }
10596
 
10597
  /**
10598
+ * Abstracts the iteration of members stored in a set by leveraging the SSCAN
10599
+ * command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
10600
  *
10601
  * @author Daniele Alessandri <suppakilla@gmail.com>
10602
+ *
10603
+ * @link http://redis.io/commands/scan
10604
  */
10605
+ class SetKey extends CursorBasedIterator
10606
  {
10607
+ protected $key;
10608
+
10609
  /**
10610
  * {@inheritdoc}
10611
  */
10612
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
10613
  {
10614
+ $this->requiredCommand($client, 'SSCAN');
10615
+
10616
+ parent::__construct($client, $match, $count);
10617
+
10618
+ $this->key = $key;
10619
  }
10620
 
10621
  /**
10622
  * {@inheritdoc}
10623
  */
10624
+ protected function executeCommand()
10625
  {
10626
+ return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
10627
  }
10628
  }
10629
 
10630
  /**
10631
+ * Abstracts the iteration of members stored in a sorted set by leveraging the
10632
+ * ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
 
10633
  *
10634
  * @author Daniele Alessandri <suppakilla@gmail.com>
10635
+ *
10636
+ * @link http://redis.io/commands/scan
10637
  */
10638
+ class SortedSetKey extends CursorBasedIterator
10639
  {
10640
+ protected $key;
10641
+
10642
  /**
10643
+ * {@inheritdoc}
 
 
 
 
 
10644
  */
10645
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
10646
  {
10647
+ $this->requiredCommand($client, 'ZSCAN');
 
 
 
10648
 
10649
+ parent::__construct($client, $match, $count);
 
 
10650
 
10651
+ $this->key = $key;
 
 
10652
  }
10653
 
10654
  /**
10655
  * {@inheritdoc}
10656
  */
10657
+ protected function executeCommand()
10658
  {
10659
+ return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
 
 
 
 
 
 
 
 
 
 
10660
  }
10661
 
10662
  /**
10663
  * {@inheritdoc}
10664
  */
10665
+ protected function extractNext()
10666
  {
10667
+ if ($kv = each($this->elements)) {
10668
+ $this->position = $kv[0];
10669
+ $this->current = $kv[1];
10670
+
10671
+ unset($this->elements[$this->position]);
10672
+ }
10673
  }
10674
  }
10675
 
10676
  /* --------------------------------------------------------------------------- */
10677
 
10678
+ namespace Predis\Connection\Aggregate;
 
 
10679
 
10680
+ use Predis\Connection\AggregateConnectionInterface;
10681
+ use Predis\ClientException;
10682
+ use Predis\Command\CommandInterface;
10683
+ use Predis\Command\RawCommand;
10684
+ use Predis\Connection\ConnectionException;
10685
+ use Predis\Connection\FactoryInterface;
10686
+ use Predis\Connection\NodeConnectionInterface;
10687
+ use Predis\Replication\MissingMasterException;
10688
+ use Predis\Replication\ReplicationStrategy;
10689
+ use Predis\Response\ErrorInterface as ResponseErrorInterface;
10690
+ use Predis\Cluster\PredisStrategy;
10691
+ use Predis\Cluster\StrategyInterface;
10692
+ use Predis\NotSupportedException;
10693
+ use Predis\Cluster\RedisStrategy as RedisClusterStrategy;
10694
+ use Predis\CommunicationException;
10695
+ use Predis\Connection\Parameters;
10696
+ use Predis\Replication\RoleException;
10697
+ use Predis\Response\ServerException;
10698
 
10699
  /**
10700
+ * Defines a cluster of Redis servers formed by aggregating multiple connection
10701
+ * instances to single Redis nodes.
10702
  *
10703
  * @author Daniele Alessandri <suppakilla@gmail.com>
10704
  */
10705
+ interface ClusterInterface extends AggregateConnectionInterface
10706
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
10707
  }
10708
 
10709
  /**
10710
+ * Defines a group of Redis nodes in a master / slave replication setup.
10711
  *
10712
  * @author Daniele Alessandri <suppakilla@gmail.com>
10713
  */
10714
+ interface ReplicationInterface extends AggregateConnectionInterface
10715
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
10716
  /**
10717
+ * Switches the internal connection instance in use.
10718
  *
10719
+ * @param string $connection Alias of a connection
10720
  */
10721
+ public function switchTo($connection);
 
 
 
10722
 
10723
  /**
10724
+ * Returns the connection instance currently in use by the aggregate
10725
+ * connection.
10726
  *
10727
+ * @return NodeConnectionInterface
10728
  */
10729
+ public function getCurrent();
 
 
 
10730
 
10731
  /**
10732
+ * Returns the connection instance for the master Redis node.
 
 
 
 
 
10733
  *
10734
+ * @return NodeConnectionInterface
10735
  */
10736
+ public function getMaster();
 
 
 
 
 
 
 
 
 
10737
 
10738
+ /**
10739
+ * Returns a list of connection instances to slave nodes.
10740
+ *
10741
+ * @return NodeConnectionInterface
10742
+ */
10743
+ public function getSlaves();
10744
  }
10745
 
10746
  /**
10747
+ * Aggregate connection handling replication of Redis nodes configured in a
10748
+ * single master / multiple slaves setup.
10749
  *
10750
  * @author Daniele Alessandri <suppakilla@gmail.com>
10751
  */
10752
+ class MasterSlaveReplication implements ReplicationInterface
10753
  {
10754
+ /**
10755
+ * @var ReplicationStrategy
10756
+ */
10757
+ protected $strategy;
10758
 
10759
  /**
10760
+ * @var NodeConnectionInterface
10761
  */
10762
+ protected $master;
 
 
 
10763
 
10764
  /**
10765
+ * @var NodeConnectionInterface[]
10766
  */
10767
+ protected $slaves = array();
 
 
 
10768
 
10769
  /**
10770
+ * @var NodeConnectionInterface
10771
  */
10772
+ protected $current;
 
 
10773
 
10774
+ /**
10775
+ * @var bool
10776
+ */
10777
+ protected $autoDiscovery = false;
10778
 
10779
  /**
10780
+ * @var FactoryInterface
 
 
10781
  */
10782
+ protected $connectionFactory;
 
 
 
 
10783
 
 
 
 
 
 
 
 
10784
  /**
10785
+ * {@inheritdoc}
 
 
10786
  */
10787
+ public function __construct(ReplicationStrategy $strategy = null)
10788
  {
10789
+ $this->strategy = $strategy ?: new ReplicationStrategy();
 
 
10790
  }
10791
 
10792
  /**
10793
+ * Configures the automatic discovery of the replication configuration on failure.
10794
  *
10795
+ * @param bool $value Enable or disable auto discovery.
10796
  */
10797
+ public function setAutoDiscovery($value)
10798
  {
10799
+ if (!$this->connectionFactory) {
10800
+ throw new ClientException('Automatic discovery requires a connection factory');
10801
+ }
 
 
 
 
10802
 
10803
+ $this->autoDiscovery = (bool) $value;
10804
+ }
 
 
 
 
10805
 
 
 
 
 
 
 
 
10806
  /**
10807
+ * Sets the connection factory used to create the connections by the auto
10808
+ * discovery procedure.
10809
  *
10810
+ * @param FactoryInterface $connectionFactory Connection factory instance.
 
 
 
10811
  */
10812
+ public function setConnectionFactory(FactoryInterface $connectionFactory)
10813
+ {
10814
+ $this->connectionFactory = $connectionFactory;
10815
+ }
10816
 
 
 
 
 
 
 
 
 
 
 
 
10817
  /**
10818
+ * Resets the connection state.
10819
  */
10820
+ protected function reset()
10821
  {
10822
+ $this->current = null;
10823
  }
 
10824
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10825
  /**
10826
  * {@inheritdoc}
10827
  */
10828
+ public function add(NodeConnectionInterface $connection)
10829
  {
10830
+ $alias = $connection->getParameters()->alias;
10831
 
10832
+ if ($alias === 'master') {
10833
+ $this->master = $connection;
10834
+ } else {
10835
+ $this->slaves[$alias ?: "slave-$connection"] = $connection;
10836
  }
10837
 
10838
+ $this->reset();
10839
  }
 
10840
 
 
 
 
 
 
 
 
 
 
 
10841
  /**
10842
  * {@inheritdoc}
10843
  */
10844
+ public function remove(NodeConnectionInterface $connection)
10845
  {
10846
+ if ($connection->getParameters()->alias === 'master') {
10847
+ $this->master = null;
10848
+ $this->reset();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10849
 
10850
+ return true;
10851
+ } else {
10852
+ if (($id = array_search($connection, $this->slaves, true)) !== false) {
10853
+ unset($this->slaves[$id]);
10854
+ $this->reset();
 
10855
 
10856
+ return true;
10857
  }
10858
  }
10859
 
10860
+ return false;
10861
  }
 
10862
 
 
 
 
 
 
 
 
 
 
 
10863
  /**
10864
  * {@inheritdoc}
10865
  */
10866
+ public function getConnection(CommandInterface $command)
10867
  {
10868
+ if (!$this->current) {
10869
+ if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) {
10870
+ $this->current = $slave;
10871
+ } else {
10872
+ $this->current = $this->getMasterOrDie();
10873
+ }
10874
+
10875
+ return $this->current;
10876
+ }
10877
+
10878
+ if ($this->current === $master = $this->getMasterOrDie()) {
10879
+ return $master;
10880
+ }
10881
+
10882
+ if (!$this->strategy->isReadOperation($command) || !$this->slaves) {
10883
+ $this->current = $master;
10884
+ }
10885
+
10886
+ return $this->current;
10887
  }
 
10888
 
 
 
 
 
 
 
 
 
 
 
10889
  /**
10890
  * {@inheritdoc}
10891
  */
10892
+ public function getConnectionById($connectionId)
10893
  {
10894
+ if ($connectionId === 'master') {
10895
+ return $this->master;
10896
  }
10897
 
10898
+ if (isset($this->slaves[$connectionId])) {
10899
+ return $this->slaves[$connectionId];
 
 
10900
  }
10901
 
10902
  return;
10903
+ }
10904
+
 
 
 
 
 
 
 
 
 
 
 
10905
  /**
10906
  * {@inheritdoc}
10907
  */
10908
+ public function switchTo($connection)
10909
  {
10910
+ if (!$connection instanceof NodeConnectionInterface) {
10911
+ $connection = $this->getConnectionById($connection);
 
 
 
 
10912
  }
10913
 
10914
+ if (!$connection) {
10915
+ throw new \InvalidArgumentException('Invalid connection or connection not found.');
10916
  }
10917
 
10918
+ if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
10919
+ throw new \InvalidArgumentException('Invalid connection or connection not found.');
10920
  }
10921
 
10922
+ $this->current = $connection;
 
 
 
 
10923
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10924
 
10925
  /**
10926
+ * Switches to the master server.
 
 
10927
  */
10928
+ public function switchToMaster()
10929
  {
10930
+ $this->switchTo('master');
10931
+ }
 
10932
 
10933
+ /**
10934
+ * Switches to a random slave server.
10935
+ */
10936
+ public function switchToSlave()
10937
+ {
10938
+ $connection = $this->pickSlave();
10939
+ $this->switchTo($connection);
10940
  }
10941
 
10942
  /**
10943
+ * {@inheritdoc}
 
 
 
 
 
 
10944
  */
10945
+ public function getCurrent()
10946
  {
10947
+ return $this->current;
 
 
10948
  }
10949
 
10950
  /**
10951
+ * {@inheritdoc}
10952
  */
10953
+ public function getMaster()
10954
  {
10955
+ return $this->master;
 
 
 
 
 
10956
  }
10957
 
10958
  /**
10959
+ * Returns the connection associated to the master server.
10960
  *
10961
+ * @return NodeConnectionInterface
10962
  */
10963
+ private function getMasterOrDie()
10964
  {
10965
+ if (!$connection = $this->getMaster()) {
10966
+ throw new MissingMasterException('No master server available for replication');
 
 
10967
  }
10968
 
10969
+ return $connection;
10970
+ }
 
10971
 
10972
+ /**
10973
+ * {@inheritdoc}
10974
+ */
10975
+ public function getSlaves()
10976
+ {
10977
+ return array_values($this->slaves);
10978
  }
10979
 
10980
  /**
10981
+ * Returns the underlying replication strategy.
 
10982
  *
10983
+ * @return ReplicationStrategy
10984
  */
10985
+ public function getReplicationStrategy()
10986
+ {
10987
+ return $this->strategy;
10988
+ }
10989
 
10990
  /**
10991
+ * Returns a random slave.
10992
+ *
10993
+ * @return NodeConnectionInterface
10994
  */
10995
+ protected function pickSlave()
10996
  {
10997
+ if ($this->slaves) {
10998
+ return $this->slaves[array_rand($this->slaves)];
 
 
10999
  }
 
 
 
11000
  }
11001
 
11002
  /**
11003
+ * {@inheritdoc}
11004
  */
11005
+ public function isConnected()
11006
  {
11007
+ return $this->current ? $this->current->isConnected() : false;
 
11008
  }
11009
 
11010
  /**
11011
  * {@inheritdoc}
11012
  */
11013
+ public function connect()
11014
  {
11015
+ if (!$this->current) {
11016
+ if (!$this->current = $this->pickSlave()) {
11017
+ if (!$this->current = $this->getMaster()) {
11018
+ throw new ClientException('No available connection for replication');
11019
+ }
11020
+ }
11021
+ }
11022
+
11023
+ $this->current->connect();
11024
  }
11025
 
11026
  /**
11027
  * {@inheritdoc}
11028
  */
11029
+ public function disconnect()
11030
  {
11031
+ if ($this->master) {
11032
+ $this->master->disconnect();
11033
+ }
11034
+
11035
+ foreach ($this->slaves as $connection) {
11036
+ $connection->disconnect();
11037
+ }
11038
  }
11039
 
11040
  /**
11041
+ * Handles response from INFO.
11042
+ *
11043
+ * @param string $response
11044
+ *
11045
+ * @return array
11046
  */
11047
+ private function handleInfoResponse($response)
11048
  {
11049
+ $info = array();
11050
+
11051
+ foreach (preg_split('/\r?\n/', $response) as $row) {
11052
+ if (strpos($row, ':') === false) {
11053
+ continue;
11054
+ }
11055
+
11056
+ list($k, $v) = explode(':', $row, 2);
11057
+ $info[$k] = $v;
11058
+ }
11059
+
11060
+ return $info;
11061
  }
11062
 
11063
  /**
11064
+ * Fetches the replication configuration from one of the servers.
11065
  */
11066
+ public function discover()
11067
  {
11068
+ if (!$this->connectionFactory) {
11069
+ throw new ClientException('Discovery requires a connection factory');
11070
+ }
 
11071
 
11072
+ RETRY_FETCH: {
11073
+ try {
11074
+ if ($connection = $this->getMaster()) {
11075
+ $this->discoverFromMaster($connection, $this->connectionFactory);
11076
+ } elseif ($connection = $this->pickSlave()) {
11077
+ $this->discoverFromSlave($connection, $this->connectionFactory);
11078
+ } else {
11079
+ throw new ClientException('No connection available for discovery');
11080
+ }
11081
+ } catch (ConnectionException $exception) {
11082
+ $this->remove($connection);
11083
+ goto RETRY_FETCH;
11084
  }
11085
  }
11086
  }
11087
 
11088
  /**
11089
+ * Discovers the replication configuration by contacting the master node.
11090
+ *
11091
+ * @param NodeConnectionInterface $connection Connection to the master node.
11092
+ * @param FactoryInterface $connectionFactory Connection factory instance.
11093
  */
11094
+ protected function discoverFromMaster(NodeConnectionInterface $connection, FactoryInterface $connectionFactory)
11095
  {
11096
+ $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION'));
11097
+ $replication = $this->handleInfoResponse($response);
11098
+
11099
+ if ($replication['role'] !== 'master') {
11100
+ throw new ClientException("Role mismatch (expected master, got slave) [$connection]");
11101
+ }
11102
+
11103
+ $this->slaves = array();
11104
+
11105
+ foreach ($replication as $k => $v) {
11106
+ $parameters = null;
11107
+
11108
+ if (strpos($k, 'slave') === 0 && preg_match('/ip=(?P<host>.*),port=(?P<port>\d+)/', $v, $parameters)) {
11109
+ $slaveConnection = $connectionFactory->create(array(
11110
+ 'host' => $parameters['host'],
11111
+ 'port' => $parameters['port'],
11112
+ ));
11113
+
11114
+ $this->add($slaveConnection);
11115
+ }
11116
+ }
11117
  }
 
11118
 
11119
+ /**
11120
+ * Discovers the replication configuration by contacting one of the slaves.
11121
+ *
11122
+ * @param NodeConnectionInterface $connection Connection to one of the slaves.
11123
+ * @param FactoryInterface $connectionFactory Connection factory instance.
11124
+ */
11125
+ protected function discoverFromSlave(NodeConnectionInterface $connection, FactoryInterface $connectionFactory)
11126
+ {
11127
+ $response = $connection->executeCommand(RawCommand::create('INFO', 'REPLICATION'));
11128
+ $replication = $this->handleInfoResponse($response);
11129
+
11130
+ if ($replication['role'] !== 'slave') {
11131
+ throw new ClientException("Role mismatch (expected slave, got master) [$connection]");
11132
+ }
11133
+
11134
+ $masterConnection = $connectionFactory->create(array(
11135
+ 'host' => $replication['master_host'],
11136
+ 'port' => $replication['master_port'],
11137
+ 'alias' => 'master',
11138
+ ));
11139
+
11140
+ $this->add($masterConnection);
11141
+
11142
+ $this->discoverFromMaster($masterConnection, $connectionFactory);
11143
+ }
11144
 
11145
  /**
11146
+ * Retries the execution of a command upon slave failure.
11147
+ *
11148
+ * @param CommandInterface $command Command instance.
11149
+ * @param string $method Actual method.
11150
+ *
11151
+ * @return mixed
11152
  */
11153
+ private function retryCommandOnFailure(CommandInterface $command, $method)
11154
  {
11155
+ RETRY_COMMAND: {
11156
+ try {
11157
+ $connection = $this->getConnection($command);
11158
+ $response = $connection->$method($command);
11159
 
11160
+ if ($response instanceof ResponseErrorInterface && $response->getErrorType() === 'LOADING') {
11161
+ throw new ConnectionException($connection, "Redis is loading the dataset in memory [$connection]");
11162
+ }
11163
+ } catch (ConnectionException $exception) {
11164
+ $connection = $exception->getConnection();
11165
+ $connection->disconnect();
11166
+
11167
+ if ($connection === $this->master && !$this->autoDiscovery) {
11168
+ // Throw immediately when master connection is failing, even
11169
+ // when the command represents a read-only operation, unless
11170
+ // automatic discovery has been enabled.
11171
+ throw $exception;
11172
+ } else {
11173
+ // Otherwise remove the failing slave and attempt to execute
11174
+ // the command again on one of the remaining slaves...
11175
+ $this->remove($connection);
11176
+ }
11177
+
11178
+ // ... that is, unless we have no more connections to use.
11179
+ if (!$this->slaves && !$this->master) {
11180
+ throw $exception;
11181
+ } elseif ($this->autoDiscovery) {
11182
+ $this->discover();
11183
+ }
11184
+
11185
+ goto RETRY_COMMAND;
11186
+ } catch (MissingMasterException $exception) {
11187
+ if ($this->autoDiscovery) {
11188
+ $this->discover();
11189
+ } else {
11190
+ throw $exception;
11191
+ }
11192
+
11193
+ goto RETRY_COMMAND;
11194
+ }
11195
+ }
11196
 
11197
+ return $response;
11198
  }
11199
 
11200
  /**
11201
  * {@inheritdoc}
11202
  */
11203
+ public function writeRequest(CommandInterface $command)
11204
  {
11205
+ $this->retryCommandOnFailure($command, __FUNCTION__);
11206
  }
11207
 
11208
  /**
11209
  * {@inheritdoc}
11210
  */
11211
+ public function readResponse(CommandInterface $command)
11212
  {
11213
+ return $this->retryCommandOnFailure($command, __FUNCTION__);
 
 
 
 
 
11214
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
11215
 
11216
  /**
11217
  * {@inheritdoc}
11218
  */
11219
+ public function executeCommand(CommandInterface $command)
11220
  {
11221
+ return $this->retryCommandOnFailure($command, __FUNCTION__);
 
 
 
 
11222
  }
11223
 
11224
  /**
11225
  * {@inheritdoc}
11226
  */
11227
+ public function __sleep()
11228
  {
11229
+ return array('master', 'slaves', 'strategy');
11230
  }
11231
  }
11232
 
11233
  /**
11234
+ * Abstraction for a cluster of aggregate connections to various Redis servers
11235
+ * implementing client-side sharding based on pluggable distribution strategies.
11236
  *
11237
  * @author Daniele Alessandri <suppakilla@gmail.com>
11238
  *
11239
+ * @todo Add the ability to remove connections from pool.
11240
  */
11241
+ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
11242
  {
11243
+ private $pool;
11244
+ private $strategy;
11245
+ private $distributor;
11246
+
11247
  /**
11248
+ * @param StrategyInterface $strategy Optional cluster strategy.
11249
  */
11250
+ public function __construct(StrategyInterface $strategy = null)
11251
  {
11252
+ $this->pool = array();
11253
+ $this->strategy = $strategy ?: new PredisStrategy();
11254
+ $this->distributor = $this->strategy->getDistributor();
11255
  }
11256
 
11257
  /**
11258
  * {@inheritdoc}
11259
  */
11260
+ public function isConnected()
11261
  {
11262
+ foreach ($this->pool as $connection) {
11263
+ if ($connection->isConnected()) {
11264
+ return true;
11265
+ }
11266
+ }
11267
 
11268
+ return false;
11269
+ }
 
 
 
 
 
 
 
 
 
11270
 
11271
  /**
11272
  * {@inheritdoc}
11273
  */
11274
+ public function connect()
11275
  {
11276
+ foreach ($this->pool as $connection) {
11277
+ $connection->connect();
11278
+ }
 
 
11279
  }
11280
 
11281
  /**
11282
  * {@inheritdoc}
11283
  */
11284
+ public function disconnect()
11285
  {
11286
+ foreach ($this->pool as $connection) {
11287
+ $connection->disconnect();
11288
+ }
11289
  }
11290
 
11291
  /**
11292
  * {@inheritdoc}
11293
  */
11294
+ public function add(NodeConnectionInterface $connection)
11295
  {
11296
+ $parameters = $connection->getParameters();
 
 
 
11297
 
11298
+ if (isset($parameters->alias)) {
11299
+ $this->pool[$parameters->alias] = $connection;
11300
+ } else {
11301
+ $this->pool[] = $connection;
11302
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11303
 
11304
+ $weight = isset($parameters->weight) ? $parameters->weight : null;
11305
+ $this->distributor->add($connection, $weight);
11306
+ }
 
 
11307
 
11308
  /**
11309
+ * {@inheritdoc}
 
 
 
 
11310
  */
11311
+ public function remove(NodeConnectionInterface $connection)
11312
  {
11313
+ if (($id = array_search($connection, $this->pool, true)) !== false) {
11314
+ unset($this->pool[$id]);
11315
+ $this->distributor->remove($connection);
11316
 
11317
+ return true;
 
11318
  }
11319
 
11320
+ return false;
 
 
 
 
11321
  }
11322
 
11323
  /**
11324
+ * Removes a connection instance using its alias or index.
 
11325
  *
11326
+ * @param string $connectionID Alias or index of a connection.
 
11327
  *
11328
+ * @return bool Returns true if the connection was in the pool.
11329
  */
11330
+ public function removeById($connectionID)
11331
  {
11332
+ if ($connection = $this->getConnectionById($connectionID)) {
11333
+ return $this->remove($connection);
11334
  }
11335
+
11336
+ return false;
11337
  }
11338
 
11339
  /**
11340
+ * {@inheritdoc}
11341
  */
11342
+ public function getConnection(CommandInterface $command)
11343
  {
11344
+ $slot = $this->strategy->getSlot($command);
11345
+
11346
+ if (!isset($slot)) {
11347
+ throw new NotSupportedException(
11348
+ "Cannot use '{$command->getId()}' over clusters of connections."
11349
+ );
11350
+ }
11351
+
11352
+ $node = $this->distributor->getBySlot($slot);
11353
+
11354
+ return $node;
11355
  }
11356
 
11357
  /**
11358
+ * {@inheritdoc}
 
 
 
11359
  */
11360
+ public function getConnectionById($connectionID)
11361
  {
11362
+ return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null;
11363
  }
11364
 
11365
  /**
11366
+ * Retrieves a connection instance from the cluster using a key.
11367
+ *
11368
+ * @param string $key Key string.
11369
+ *
11370
+ * @return NodeConnectionInterface
11371
  */
11372
+ public function getConnectionByKey($key)
11373
  {
11374
+ $hash = $this->strategy->getSlotByKey($key);
11375
+ $node = $this->distributor->getBySlot($hash);
 
 
 
11376
 
11377
+ return $node;
11378
  }
11379
 
11380
  /**
11381
+ * Returns the underlying command hash strategy used to hash commands by
11382
+ * using keys found in their arguments.
11383
+ *
11384
+ * @return StrategyInterface
11385
  */
11386
+ public function getClusterStrategy()
11387
  {
11388
+ return $this->strategy;
 
11389
  }
11390
 
11391
  /**
11392
  * {@inheritdoc}
11393
  */
11394
+ public function count()
11395
  {
11396
+ return count($this->pool);
 
11397
  }
11398
 
11399
  /**
11400
  * {@inheritdoc}
11401
  */
11402
+ public function getIterator()
11403
  {
11404
+ return new \ArrayIterator($this->pool);
11405
  }
11406
 
11407
  /**
11408
  * {@inheritdoc}
11409
  */
11410
+ public function writeRequest(CommandInterface $command)
11411
  {
11412
+ $this->getConnection($command)->writeRequest($command);
11413
  }
11414
 
11415
  /**
11416
  * {@inheritdoc}
11417
  */
11418
+ public function readResponse(CommandInterface $command)
11419
+ {
11420
+ return $this->getConnection($command)->readResponse($command);
 
 
 
 
 
 
 
 
11421
  }
11422
 
11423
  /**
11424
  * {@inheritdoc}
11425
  */
11426
+ public function executeCommand(CommandInterface $command)
11427
  {
11428
+ return $this->getConnection($command)->executeCommand($command);
11429
  }
 
11430
 
11431
+ /**
11432
+ * Executes the specified Redis command on all the nodes of a cluster.
11433
+ *
11434
+ * @param CommandInterface $command A Redis command.
11435
+ *
11436
+ * @return array
11437
+ */
11438
+ public function executeCommandOnNodes(CommandInterface $command)
11439
+ {
11440
+ $responses = array();
11441
 
11442
+ foreach ($this->pool as $connection) {
11443
+ $responses[] = $connection->executeCommand($command);
11444
+ }
11445
 
11446
+ return $responses;
11447
+ }
11448
+ }
 
 
 
 
11449
 
11450
  /**
11451
+ * Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
 
11452
  *
11453
+ * This connection backend offers smart support for redis-cluster by handling
11454
+ * automatic slots map (re)generation upon -MOVED or -ASK responses returned by
11455
+ * Redis when redirecting a client to a different node.
11456
+ *
11457
+ * The cluster can be pre-initialized using only a subset of the actual nodes in
11458
+ * the cluster, Predis will do the rest by adjusting the slots map and creating
11459
+ * the missing underlying connection instances on the fly.
11460
+ *
11461
+ * It is possible to pre-associate connections to a slots range with the "slots"
11462
+ * parameter in the form "$first-$last". This can greatly reduce runtime node
11463
+ * guessing and redirections.
11464
+ *
11465
+ * It is also possible to ask for the full and updated slots map directly to one
11466
+ * of the nodes and optionally enable such a behaviour upon -MOVED redirections.
11467
+ * Asking for the cluster configuration to Redis is actually done by issuing a
11468
+ * CLUSTER SLOTS command to a random node in the pool.
11469
  *
11470
  * @author Daniele Alessandri <suppakilla@gmail.com>
11471
  */
11472
+ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
11473
  {
11474
+ private $useClusterSlots = true;
11475
+ private $pool = array();
11476
+ private $slots = array();
11477
+ private $slotsMap;
11478
+ private $strategy;
11479
+ private $connections;
11480
+ private $retryLimit = 5;
11481
+
11482
  /**
11483
+ * @param FactoryInterface $connections Optional connection factory.
11484
+ * @param StrategyInterface $strategy Optional cluster strategy.
 
 
 
 
11485
  */
11486
+ public function __construct(
11487
+ FactoryInterface $connections,
11488
+ StrategyInterface $strategy = null
11489
+ ) {
11490
+ $this->connections = $connections;
11491
+ $this->strategy = $strategy ?: new RedisClusterStrategy();
11492
+ }
11493
 
11494
  /**
11495
+ * Sets the maximum number of retries for commands upon server failure.
 
11496
  *
11497
+ * -1 = unlimited retry attempts
11498
+ * 0 = no retry attempts (fails immediatly)
11499
+ * n = fail only after n retry attempts
11500
  *
11501
+ * @param int $retry Number of retry attempts.
11502
  */
11503
+ public function setRetryLimit($retry)
11504
+ {
11505
+ $this->retryLimit = (int) $retry;
11506
+ }
11507
 
11508
  /**
11509
+ * {@inheritdoc}
 
 
11510
  */
11511
+ public function isConnected()
11512
+ {
11513
+ foreach ($this->pool as $connection) {
11514
+ if ($connection->isConnected()) {
11515
+ return true;
11516
+ }
11517
+ }
11518
 
11519
+ return false;
11520
+ }
 
 
 
 
 
 
11521
 
11522
  /**
11523
+ * {@inheritdoc}
11524
  */
11525
+ public function connect()
11526
  {
11527
+ if ($connection = $this->getRandomConnection()) {
11528
+ $connection->connect();
11529
+ }
11530
  }
11531
 
11532
  /**
11533
+ * {@inheritdoc}
 
 
11534
  */
11535
+ public function disconnect()
11536
  {
11537
+ foreach ($this->pool as $connection) {
11538
+ $connection->disconnect();
11539
+ }
11540
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11541
 
11542
+ /**
11543
+ * {@inheritdoc}
11544
+ */
11545
+ public function add(NodeConnectionInterface $connection)
11546
+ {
11547
+ $this->pool[(string) $connection] = $connection;
11548
+ unset($this->slotsMap);
11549
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11550
 
11551
+ /**
11552
+ * {@inheritdoc}
11553
+ */
11554
+ public function remove(NodeConnectionInterface $connection)
11555
+ {
11556
+ if (false !== $id = array_search($connection, $this->pool, true)) {
11557
+ unset(
11558
+ $this->pool[$id],
11559
+ $this->slotsMap
11560
+ );
 
 
 
 
 
 
 
 
11561
 
11562
+ $this->slots = array_diff($this->slots, array($connection));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11563
 
11564
+ return true;
11565
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11566
 
11567
+ return false;
11568
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11569
 
11570
+ /**
11571
+ * Removes a connection instance by using its identifier.
11572
+ *
11573
+ * @param string $connectionID Connection identifier.
11574
+ *
11575
+ * @return bool True if the connection was in the pool.
11576
+ */
11577
+ public function removeById($connectionID)
11578
+ {
11579
+ if (isset($this->pool[$connectionID])) {
11580
+ unset(
11581
+ $this->pool[$connectionID],
11582
+ $this->slotsMap
11583
+ );
11584
 
11585
+ return true;
11586
+ }
11587
+
11588
+ return false;
11589
  }
11590
 
11591
  /**
11592
+ * Generates the current slots map by guessing the cluster configuration out
11593
+ * of the connection parameters of the connections in the pool.
11594
+ *
11595
+ * Generation is based on the same algorithm used by Redis to generate the
11596
+ * cluster, so it is most effective when all of the connections supplied on
11597
+ * initialization have the "slots" parameter properly set accordingly to the
11598
+ * current cluster configuration.
11599
  *
11600
  * @return array
11601
  */
11602
+ public function buildSlotsMap()
11603
  {
11604
+ $this->slotsMap = array();
11605
+
11606
+ foreach ($this->pool as $connectionID => $connection) {
11607
+ $parameters = $connection->getParameters();
11608
+
11609
+ if (!isset($parameters->slots)) {
11610
+ continue;
11611
+ }
11612
+
11613
+ foreach (explode(',', $parameters->slots) as $slotRange) {
11614
+ $slots = explode('-', $slotRange, 2);
11615
+
11616
+ if (!isset($slots[1])) {
11617
+ $slots[1] = $slots[0];
11618
+ }
11619
+
11620
+ $this->setSlots($slots[0], $slots[1], $connectionID);
11621
+ }
11622
+ }
11623
+
11624
+ return $this->slotsMap;
11625
  }
11626
 
11627
  /**
11628
+ * Queries the specified node of the cluster to fetch the updated slots map.
11629
  *
11630
+ * When the connection fails, this method tries to execute the same command
11631
+ * on a different connection picked at random from the pool of known nodes,
11632
+ * up until the retry limit is reached.
11633
  *
11634
+ * @param NodeConnectionInterface $connection Connection to a node of the cluster.
 
11635
  *
11636
+ * @return mixed
11637
+ */
11638
+ private function queryClusterNodeForSlotsMap(NodeConnectionInterface $connection)
11639
+ {
11640
+ $retries = 0;
11641
+ $command = RawCommand::create('CLUSTER', 'SLOTS');
11642
+
11643
+ RETRY_COMMAND: {
11644
+ try {
11645
+ $response = $connection->executeCommand($command);
11646
+ } catch (ConnectionException $exception) {
11647
+ $connection = $exception->getConnection();
11648
+ $connection->disconnect();
11649
+
11650
+ $this->remove($connection);
11651
+
11652
+ if ($retries === $this->retryLimit) {
11653
+ throw $exception;
11654
+ }
11655
+
11656
+ if (!$connection = $this->getRandomConnection()) {
11657
+ throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`');
11658
+ }
11659
+
11660
+ ++$retries;
11661
+ goto RETRY_COMMAND;
11662
+ }
11663
+ }
11664
+
11665
+ return $response;
11666
+ }
11667
+
11668
+ /**
11669
+ * Generates an updated slots map fetching the cluster configuration using
11670
+ * the CLUSTER SLOTS command against the specified node or a random one from
11671
+ * the pool.
11672
  *
11673
+ * @param NodeConnectionInterface $connection Optional connection instance.
11674
+ *
11675
+ * @return array
11676
  */
11677
+ public function askSlotsMap(NodeConnectionInterface $connection = null)
11678
  {
11679
+ if (!$connection && !$connection = $this->getRandomConnection()) {
11680
+ return array();
11681
+ }
11682
 
11683
+ $this->resetSlotsMap();
 
11684
 
11685
+ $response = $this->queryClusterNodeForSlotsMap($connection);
11686
+
11687
+ foreach ($response as $slots) {
11688
+ // We only support master servers for now, so we ignore subsequent
11689
+ // elements in the $slots array identifying slaves.
11690
+ list($start, $end, $master) = $slots;
11691
+
11692
+ if ($master[0] === '') {
11693
+ $this->setSlots($start, $end, (string) $connection);
11694
+ } else {
11695
+ $this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
11696
+ }
11697
  }
11698
 
11699
+ return $this->slotsMap;
11700
+ }
11701
+
11702
+ /**
11703
+ * Resets the slots map cache.
11704
+ */
11705
+ public function resetSlotsMap()
11706
+ {
11707
+ $this->slotsMap = array();
11708
+ }
11709
+
11710
+ /**
11711
+ * Returns the current slots map for the cluster.
11712
+ *
11713
+ * The order of the returned $slot => $server dictionary is not guaranteed.
11714
+ *
11715
+ * @return array
11716
+ */
11717
+ public function getSlotsMap()
11718
+ {
11719
+ if (!isset($this->slotsMap)) {
11720
+ $this->slotsMap = array();
11721
  }
11722
 
11723
+ return $this->slotsMap;
11724
  }
11725
 
11726
  /**
11727
+ * Pre-associates a connection to a slots range to avoid runtime guessing.
11728
  *
11729
+ * @param int $first Initial slot of the range.
11730
+ * @param int $last Last slot of the range.
11731
+ * @param NodeConnectionInterface|string $connection ID or connection instance.
11732
  *
11733
+ * @throws \OutOfBoundsException
11734
  */
11735
+ public function setSlots($first, $last, $connection)
11736
  {
11737
+ if ($first < 0x0000 || $first > 0x3FFF ||
11738
+ $last < 0x0000 || $last > 0x3FFF ||
11739
+ $last < $first
11740
+ ) {
11741
+ throw new \OutOfBoundsException(
11742
+ "Invalid slot range for $connection: [$first-$last]."
11743
+ );
11744
+ }
11745
+
11746
+ $slots = array_fill($first, $last - $first + 1, (string) $connection);
11747
+ $this->slotsMap = $this->getSlotsMap() + $slots;
11748
  }
11749
 
11750
  /**
11751
+ * Guesses the correct node associated to a given slot using a precalculated
11752
+ * slots map, falling back to the same logic used by Redis to initialize a
11753
+ * cluster (best-effort).
11754
  *
11755
+ * @param int $slot Slot index.
11756
  *
11757
+ * @return string Connection ID.
11758
  */
11759
+ protected function guessNode($slot)
11760
  {
11761
+ if (!$this->pool) {
11762
+ throw new ClientException('No connections available in the pool');
11763
+ }
11764
 
11765
+ if (!isset($this->slotsMap)) {
11766
+ $this->buildSlotsMap();
11767
+ }
11768
+
11769
+ if (isset($this->slotsMap[$slot])) {
11770
+ return $this->slotsMap[$slot];
11771
  }
11772
+
11773
+ $count = count($this->pool);
11774
+ $index = min((int) ($slot / (int) (16384 / $count)), $count - 1);
11775
+ $nodes = array_keys($this->pool);
11776
+
11777
+ return $nodes[$index];
11778
  }
11779
 
11780
  /**
11781
+ * Creates a new connection instance from the given connection ID.
 
11782
  *
11783
+ * @param string $connectionID Identifier for the connection.
11784
  *
11785
+ * @return NodeConnectionInterface
11786
  */
11787
+ protected function createConnection($connectionID)
11788
  {
11789
+ $separator = strrpos($connectionID, ':');
 
11790
 
11791
+ return $this->connections->create(array(
11792
+ 'host' => substr($connectionID, 0, $separator),
11793
+ 'port' => substr($connectionID, $separator + 1),
11794
+ ));
11795
+ }
11796
+
11797
+ /**
11798
+ * {@inheritdoc}
11799
+ */
11800
+ public function getConnection(CommandInterface $command)
11801
+ {
11802
+ $slot = $this->strategy->getSlot($command);
11803
+
11804
+ if (!isset($slot)) {
11805
+ throw new NotSupportedException(
11806
+ "Cannot use '{$command->getId()}' with redis-cluster."
11807
+ );
11808
  }
11809
 
11810
+ if (isset($this->slots[$slot])) {
11811
+ return $this->slots[$slot];
11812
+ } else {
11813
+ return $this->getConnectionBySlot($slot);
11814
  }
11815
  }
11816
 
11817
  /**
11818
+ * Returns the connection currently associated to a given slot.
11819
  *
11820
+ * @param int $slot Slot index.
11821
+ *
11822
+ * @throws \OutOfBoundsException
11823
+ *
11824
+ * @return NodeConnectionInterface
11825
+ */
11826
+ public function getConnectionBySlot($slot)
11827
+ {
11828
+ if ($slot < 0x0000 || $slot > 0x3FFF) {
11829
+ throw new \OutOfBoundsException("Invalid slot [$slot].");
11830
+ }
11831
+
11832
+ if (isset($this->slots[$slot])) {
11833
+ return $this->slots[$slot];
11834
+ }
11835
+
11836
+ $connectionID = $this->guessNode($slot);
11837
+
11838
+ if (!$connection = $this->getConnectionById($connectionID)) {
11839
+ $connection = $this->createConnection($connectionID);
11840
+ $this->pool[$connectionID] = $connection;
11841
+ }
11842
+
11843
+ return $this->slots[$slot] = $connection;
11844
+ }
11845
+
11846
+ /**
11847
+ * {@inheritdoc}
11848
+ */
11849
+ public function getConnectionById($connectionID)
11850
+ {
11851
+ if (isset($this->pool[$connectionID])) {
11852
+ return $this->pool[$connectionID];
11853
+ }
11854
+ }
11855
+
11856
+ /**
11857
+ * Returns a random connection from the pool.
11858
  *
11859
+ * @return NodeConnectionInterface|null
11860
  */
11861
+ protected function getRandomConnection()
11862
  {
11863
+ if ($this->pool) {
11864
+ return $this->pool[array_rand($this->pool)];
 
 
11865
  }
11866
  }
11867
 
11868
  /**
11869
+ * Permanently associates the connection instance to a new slot.
11870
+ * The connection is added to the connections pool if not yet included.
 
11871
  *
11872
+ * @param NodeConnectionInterface $connection Connection instance.
11873
+ * @param int $slot Target slot index.
11874
  */
11875
+ protected function move(NodeConnectionInterface $connection, $slot)
11876
  {
11877
+ $this->pool[(string) $connection] = $connection;
11878
+ $this->slots[(int) $slot] = $connection;
 
 
 
11879
  }
11880
 
11881
  /**
11882
+ * Handles -ERR responses returned by Redis.
11883
  *
11884
+ * @param CommandInterface $command Command that generated the -ERR response.
11885
+ * @param ErrorResponseInterface $error Redis error response object.
11886
  *
11887
+ * @return mixed
11888
  */
11889
+ protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error)
11890
  {
11891
+ $details = explode(' ', $error->getMessage(), 2);
 
11892
 
11893
+ switch ($details[0]) {
11894
+ case 'MOVED':
11895
+ return $this->onMovedResponse($command, $details[1]);
11896
+
11897
+ case 'ASK':
11898
+ return $this->onAskResponse($command, $details[1]);
11899
+
11900
+ default:
11901
+ return $error;
11902
  }
11903
  }
11904
 
11905
  /**
11906
+ * Handles -MOVED responses by executing again the command against the node
11907
+ * indicated by the Redis response.
11908
  *
11909
+ * @param CommandInterface $command Command that generated the -MOVED response.
11910
+ * @param string $details Parameters of the -MOVED response.
11911
  *
11912
+ * @return mixed
11913
  */
11914
+ protected function onMovedResponse(CommandInterface $command, $details)
11915
  {
11916
+ list($slot, $connectionID) = explode(' ', $details, 2);
11917
+
11918
+ if (!$connection = $this->getConnectionById($connectionID)) {
11919
+ $connection = $this->createConnection($connectionID);
11920
  }
11921
 
11922
+ if ($this->useClusterSlots) {
11923
+ $this->askSlotsMap($connection);
11924
  }
11925
+
11926
+ $this->move($connection, $slot);
11927
+ $response = $this->executeCommand($command);
11928
+
11929
+ return $response;
11930
  }
11931
 
11932
  /**
11933
+ * Handles -ASK responses by executing again the command against the node
11934
+ * indicated by the Redis response.
11935
+ *
11936
+ * @param CommandInterface $command Command that generated the -ASK response.
11937
+ * @param string $details Parameters of the -ASK response.
11938
+ *
11939
+ * @return mixed
11940
  */
11941
+ protected function onAskResponse(CommandInterface $command, $details)
11942
  {
11943
+ list($slot, $connectionID) = explode(' ', $details, 2);
 
 
 
11944
 
11945
+ if (!$connection = $this->getConnectionById($connectionID)) {
11946
+ $connection = $this->createConnection($connectionID);
 
 
11947
  }
11948
 
11949
+ $connection->executeCommand(RawCommand::create('ASKING'));
11950
+ $response = $connection->executeCommand($command);
11951
+
11952
+ return $response;
11953
  }
11954
 
11955
  /**
11956
+ * Ensures that a command is executed one more time on connection failure.
11957
  *
11958
+ * The connection to the node that generated the error is evicted from the
11959
+ * pool before trying to fetch an updated slots map from another node. If
11960
+ * the new slots map points to an unreachable server the client gives up and
11961
+ * throws the exception as the nodes participating in the cluster may still
11962
+ * have to agree that something changed in the configuration of the cluster.
11963
  *
11964
+ * @param CommandInterface $command Command instance.
11965
+ * @param string $method Actual method.
11966
+ *
11967
+ * @return mixed
11968
  */
11969
+ private function retryCommandOnFailure(CommandInterface $command, $method)
11970
  {
11971
+ $failure = false;
 
 
11972
 
11973
+ RETRY_COMMAND: {
11974
+ try {
11975
+ $response = $this->getConnection($command)->$method($command);
11976
+ } catch (ConnectionException $exception) {
11977
+ $connection = $exception->getConnection();
11978
+ $connection->disconnect();
11979
 
11980
+ $this->remove($connection);
 
11981
 
11982
+ if ($failure) {
11983
+ throw $exception;
11984
+ } elseif ($this->useClusterSlots) {
11985
+ $this->askSlotsMap();
11986
+ }
11987
 
11988
+ $failure = true;
11989
+
11990
+ goto RETRY_COMMAND;
11991
+ }
11992
  }
11993
 
11994
+ return $response;
11995
  }
11996
 
11997
  /**
11998
+ * {@inheritdoc}
 
 
 
 
 
11999
  */
12000
+ public function writeRequest(CommandInterface $command)
12001
  {
12002
+ $this->retryCommandOnFailure($command, __FUNCTION__);
 
 
 
 
 
 
12003
  }
 
12004
 
12005
+ /**
12006
+ * {@inheritdoc}
12007
+ */
12008
+ public function readResponse(CommandInterface $command)
12009
+ {
12010
+ return $this->retryCommandOnFailure($command, __FUNCTION__);
12011
+ }
 
12012
 
12013
  /**
12014
+ * {@inheritdoc}
12015
  */
12016
+ public function executeCommand(CommandInterface $command)
12017
  {
12018
+ $response = $this->retryCommandOnFailure($command, __FUNCTION__);
12019
 
12020
+ if ($response instanceof ErrorResponseInterface) {
12021
+ return $this->onErrorResponse($command, $response);
12022
+ }
12023
+
12024
+ return $response;
12025
  }
12026
 
12027
  /**
12028
  * {@inheritdoc}
12029
  */
12030
+ public function count()
12031
  {
12032
+ return count($this->pool);
 
 
 
 
12033
  }
12034
 
12035
  /**
12036
  * {@inheritdoc}
12037
  */
12038
+ public function getIterator()
12039
  {
12040
+ if ($this->useClusterSlots) {
12041
+ $slotsmap = $this->getSlotsMap() ?: $this->askSlotsMap();
12042
+ } else {
12043
+ $slotsmap = $this->getSlotsMap() ?: $this->buildSlotsMap();
12044
  }
12045
 
12046
+ $connections = array();
 
 
 
12047
 
12048
+ foreach (array_unique($slotsmap) as $node) {
12049
+ if (!$connection = $this->getConnectionById($node)) {
12050
+ $this->add($connection = $this->createConnection($node));
12051
  }
12052
 
12053
+ $connections[] = $connection;
12054
  }
12055
 
12056
+ return new \ArrayIterator($connections);
12057
  }
12058
 
12059
  /**
12060
+ * Returns the underlying command hash strategy used to hash commands by
12061
+ * using keys found in their arguments.
12062
+ *
12063
+ * @return StrategyInterface
12064
  */
12065
+ public function getClusterStrategy()
12066
  {
12067
+ return $this->strategy;
12068
+ }
12069
+
12070
+ /**
12071
+ * Returns the underlying connection factory used to create new connection
12072
+ * instances to Redis nodes indicated by redis-cluster.
12073
+ *
12074
+ * @return FactoryInterface
12075
+ */
12076
+ public function getConnectionFactory()
12077
+ {
12078
+ return $this->connections;
12079
+ }
12080
+
12081
+ /**
12082
+ * Enables automatic fetching of the current slots map from one of the nodes
12083
+ * using the CLUSTER SLOTS command. This option is enabled by default as
12084
+ * asking the current slots map to Redis upon -MOVED responses may reduce
12085
+ * overhead by eliminating the trial-and-error nature of the node guessing
12086
+ * procedure, mostly when targeting many keys that would end up in a lot of
12087
+ * redirections.
12088
+ *
12089
+ * The slots map can still be manually fetched using the askSlotsMap()
12090
+ * method whether or not this option is enabled.
12091
+ *
12092
+ * @param bool $value Enable or disable the use of CLUSTER SLOTS.
12093
+ */
12094
+ public function useClusterSlots($value)
12095
+ {
12096
+ $this->useClusterSlots = (bool) $value;
12097
  }
12098
  }
12099
 
12100
  /**
 
 
 
12101
  * @author Daniele Alessandri <suppakilla@gmail.com>
12102
+ * @author Ville Mattila <ville@eventio.fi>
12103
  */
12104
+ class SentinelReplication implements ReplicationInterface
12105
  {
 
 
12106
  /**
12107
+ * @var NodeConnectionInterface
12108
  */
12109
+ protected $master;
 
 
12110
 
12111
+ /**
12112
+ * @var NodeConnectionInterface[]
12113
+ */
12114
+ protected $slaves = array();
12115
 
12116
  /**
12117
+ * @var NodeConnectionInterface
12118
  */
12119
+ protected $current;
 
 
 
12120
 
12121
+ /**
12122
+ * @var string
12123
+ */
12124
+ protected $service;
12125
 
12126
  /**
12127
+ * @var ConnectionFactoryInterface
12128
  */
12129
+ protected $connectionFactory;
 
 
 
 
 
 
12130
 
12131
+ /**
12132
+ * @var ReplicationStrategy
12133
+ */
12134
+ protected $strategy;
12135
 
12136
+ /**
12137
+ * @var NodeConnectionInterface[]
12138
+ */
12139
+ protected $sentinels = array();
12140
 
12141
+ /**
12142
+ * @var NodeConnectionInterface
12143
+ */
12144
+ protected $sentinelConnection;
12145
 
 
 
 
 
 
 
 
 
12146
  /**
12147
+ * @var float
 
 
 
12148
  */
12149
+ protected $sentinelTimeout = 0.100;
12150
 
12151
  /**
12152
+ * Max number of automatic retries of commands upon server failure.
12153
  *
12154
+ * -1 = unlimited retry attempts
12155
+ * 0 = no retry attempts (fails immediatly)
12156
+ * n = fail only after n retry attempts
12157
  *
12158
+ * @var int
12159
  */
12160
+ protected $retryLimit = 20;
 
12161
 
 
 
 
 
 
 
 
 
12162
  /**
12163
+ * Time to wait in milliseconds before fetching a new configuration from one
12164
+ * of the sentinel servers.
 
12165
  *
12166
+ * @var int
12167
  */
12168
+ protected $retryWait = 1000;
 
12169
 
 
 
 
 
 
 
 
12170
  /**
12171
+ * Flag for automatic fetching of available sentinels.
12172
  *
12173
+ * @var bool
 
 
12174
  */
12175
+ protected $updateSentinels = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12176
 
12177
+ /**
12178
+ * @param string $service Name of the service for autodiscovery.
12179
+ * @param array $sentinels Sentinel servers connection parameters.
12180
+ * @param ConnectionFactoryInterface $connectionFactory Connection factory instance.
12181
+ * @param ReplicationStrategy $strategy Replication strategy instance.
12182
+ */
12183
+ public function __construct(
12184
+ $service,
12185
+ array $sentinels,
12186
+ ConnectionFactoryInterface $connectionFactory,
12187
+ ReplicationStrategy $strategy = null
12188
+ ) {
12189
+ $this->sentinels = $sentinels;
12190
+ $this->service = $service;
12191
+ $this->connectionFactory = $connectionFactory;
12192
+ $this->strategy = $strategy ?: new ReplicationStrategy();
12193
+ }
12194
 
 
 
 
 
 
 
 
12195
  /**
12196
+ * Sets a default timeout for connections to sentinels.
12197
  *
12198
+ * When "timeout" is present in the connection parameters of sentinels, its
12199
+ * value overrides the default sentinel timeout.
12200
+ *
12201
+ * @param float $timeout Timeout value.
12202
  */
12203
+ public function setSentinelTimeout($timeout)
12204
+ {
12205
+ $this->sentinelTimeout = (float) $timeout;
12206
+ }
12207
 
12208
  /**
12209
+ * Sets the maximum number of retries for commands upon server failure.
 
12210
  *
12211
+ * -1 = unlimited retry attempts
12212
+ * 0 = no retry attempts (fails immediatly)
12213
+ * n = fail only after n retry attempts
12214
+ *
12215
+ * @param int $retry Number of retry attempts.
12216
  */
12217
+ public function setRetryLimit($retry)
12218
+ {
12219
+ $this->retryLimit = (int) $retry;
12220
+ }
12221
 
12222
  /**
12223
+ * Sets the time to wait (in seconds) before fetching a new configuration
12224
+ * from one of the sentinels.
12225
  *
12226
+ * @param float $seconds Time to wait before the next attempt.
12227
  */
12228
+ public function setRetryWait($seconds)
12229
+ {
12230
+ $this->retryWait = (float) $seconds;
12231
+ }
12232
 
12233
  /**
12234
+ * Set automatic fetching of available sentinels.
12235
  *
12236
+ * @param bool $update Enable or disable automatic updates.
12237
  */
12238
+ public function setUpdateSentinels($update)
12239
+ {
12240
+ $this->updateSentinels = (bool) $update;
12241
+ }
12242
 
12243
+ /**
12244
+ * Resets the current connection.
12245
+ */
12246
+ protected function reset()
12247
+ {
12248
+ $this->current = null;
12249
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12250
 
12251
  /**
12252
+ * Wipes the current list of master and slaves nodes.
 
12253
  */
12254
+ protected function wipeServerList()
12255
+ {
12256
+ $this->reset();
12257
+
12258
+ $this->master = null;
12259
+ $this->slaves = array();
12260
  }
12261
 
12262
  /**
12263
  * {@inheritdoc}
12264
  */
12265
+ public function add(NodeConnectionInterface $connection)
12266
  {
12267
+ $alias = $connection->getParameters()->alias;
12268
+
12269
+ if ($alias === 'master') {
12270
+ $this->master = $connection;
12271
+ } else {
12272
+ $this->slaves[$alias ?: count($this->slaves)] = $connection;
12273
  }
12274
 
12275
+ $this->reset();
12276
  }
12277
 
12278
  /**
12279
  * {@inheritdoc}
12280
  */
12281
+ public function remove(NodeConnectionInterface $connection)
12282
  {
12283
+ if ($connection === $this->master) {
12284
+ $this->master = null;
12285
+ $this->reset();
12286
+
12287
+ return true;
12288
+ }
12289
+
12290
+ if (false !== $id = array_search($connection, $this->slaves, true)) {
12291
+ unset($this->slaves[$id]);
12292
+ $this->reset();
12293
+
12294
+ return true;
12295
+ }
12296
+
12297
+ return false;
12298
+ }
12299
+
12300
+ /**
12301
+ * Creates a new connection to a sentinel server.
12302
+ *
12303
+ * @return NodeConnectionInterface
12304
+ */
12305
+ protected function createSentinelConnection($parameters)
12306
+ {
12307
+ if ($parameters instanceof NodeConnectionInterface) {
12308
+ return $parameters;
12309
+ }
12310
+
12311
+ if (is_string($parameters)) {
12312
+ $parameters = Parameters::parse($parameters);
12313
+ }
12314
+
12315
+ if (is_array($parameters)) {
12316
+ // We explicitly set "database" and "password" to null,
12317
+ // so that no AUTH and SELECT command is send to the sentinels.
12318
+ $parameters['database'] = null;
12319
+ $parameters['password'] = null;
12320
+
12321
+ if (!isset($parameters['timeout'])) {
12322
+ $parameters['timeout'] = $this->sentinelTimeout;
12323
+ }
12324
+ }
12325
+
12326
+ $connection = $this->connectionFactory->create($parameters);
12327
+
12328
+ return $connection;
12329
+ }
12330
+
12331
+ /**
12332
+ * Returns the current sentinel connection.
12333
+ *
12334
+ * If there is no active sentinel connection, a new connection is created.
12335
+ *
12336
+ * @return NodeConnectionInterface
12337
+ */
12338
+ public function getSentinelConnection()
12339
+ {
12340
+ if (!$this->sentinelConnection) {
12341
+ if (!$this->sentinels) {
12342
+ throw new \Predis\ClientException('No sentinel server available for autodiscovery.');
12343
+ }
12344
+
12345
+ $sentinel = array_shift($this->sentinels);
12346
+ $this->sentinelConnection = $this->createSentinelConnection($sentinel);
12347
  }
12348
+
12349
+ return $this->sentinelConnection;
12350
  }
12351
 
12352
+ /**
12353
+ * Fetches an updated list of sentinels from a sentinel.
12354
+ */
12355
+ public function updateSentinels()
12356
+ {
12357
+ SENTINEL_QUERY: {
12358
+ $sentinel = $this->getSentinelConnection();
12359
+
12360
+ try {
12361
+ $payload = $sentinel->executeCommand(
12362
+ RawCommand::create('SENTINEL', 'sentinels', $this->service)
12363
+ );
12364
+
12365
+ $this->sentinels = array();
12366
+ // NOTE: sentinel server does not return itself, so we add it back.
12367
+ $this->sentinels[] = $sentinel->getParameters()->toArray();
12368
+
12369
+ foreach ($payload as $sentinel) {
12370
+ $this->sentinels[] = array(
12371
+ 'host' => $sentinel[3],
12372
+ 'port' => $sentinel[5],
12373
+ );
12374
+ }
12375
+ } catch (ConnectionException $exception) {
12376
+ $this->sentinelConnection = null;
12377
+
12378
+ goto SENTINEL_QUERY;
12379
+ }
12380
  }
12381
  }
12382
 
12383
  /**
12384
+ * Fetches the details for the master and slave servers from a sentinel.
12385
  */
12386
+ public function querySentinel()
12387
  {
12388
+ $this->wipeServerList();
12389
+
12390
+ $this->updateSentinels();
12391
+ $this->getMaster();
12392
+ $this->getSlaves();
12393
  }
12394
 
12395
  /**
12396
+ * Handles error responses returned by redis-sentinel.
12397
+ *
12398
+ * @param NodeConnectionInterface $sentinel Connection to a sentinel server.
12399
+ * @param ErrorResponseInterface $error Error response.
12400
  */
12401
+ private function handleSentinelErrorResponse(NodeConnectionInterface $sentinel, ErrorResponseInterface $error)
12402
  {
12403
+ if ($error->getErrorType() === 'IDONTKNOW') {
12404
+ throw new ConnectionException($sentinel, $error->getMessage());
12405
+ } else {
12406
+ throw new ServerException($error->getMessage());
 
 
 
12407
  }
 
 
12408
  }
12409
 
12410
  /**
12411
+ * Fetches the details for the master server from a sentinel.
12412
  *
12413
+ * @param NodeConnectionInterface $sentinel Connection to a sentinel server.
12414
+ * @param string $service Name of the service.
12415
  *
12416
+ * @return array
12417
  */
12418
+ protected function querySentinelForMaster(NodeConnectionInterface $sentinel, $service)
12419
  {
12420
+ $payload = $sentinel->executeCommand(
12421
+ RawCommand::create('SENTINEL', 'get-master-addr-by-name', $service)
12422
+ );
 
 
12423
 
12424
+ if ($payload === null) {
12425
+ throw new ServerException('ERR No such master with that name');
12426
  }
12427
 
12428
+ if ($payload instanceof ErrorResponseInterface) {
12429
+ $this->handleSentinelErrorResponse($sentinel, $payload);
12430
+ }
12431
+
12432
+ return array(
12433
+ 'host' => $payload[0],
12434
+ 'port' => $payload[1],
12435
+ 'alias' => 'master',
12436
+ );
12437
  }
12438
 
12439
  /**
12440
+ * Fetches the details for the slave servers from a sentinel.
 
12441
  *
12442
+ * @param NodeConnectionInterface $sentinel Connection to a sentinel server.
12443
+ * @param string $service Name of the service.
12444
+ *
12445
+ * @return array
12446
  */
12447
+ protected function querySentinelForSlaves(NodeConnectionInterface $sentinel, $service)
12448
  {
12449
+ $slaves = array();
12450
 
12451
+ $payload = $sentinel->executeCommand(
12452
+ RawCommand::create('SENTINEL', 'slaves', $service)
12453
+ );
12454
 
12455
+ if ($payload instanceof ErrorResponseInterface) {
12456
+ $this->handleSentinelErrorResponse($sentinel, $payload);
12457
+ }
12458
+
12459
+ foreach ($payload as $slave) {
12460
+ $flags = explode(',', $slave[9]);
12461
+
12462
+ if (array_intersect($flags, array('s_down', 'o_down', 'disconnected'))) {
12463
  continue;
12464
  }
12465
 
12466
+ $slaves[] = array(
12467
+ 'host' => $slave[3],
12468
+ 'port' => $slave[5],
12469
+ 'alias' => "slave-$slave[1]",
12470
+ );
12471
  }
12472
+
12473
+ return $slaves;
12474
  }
12475
 
12476
  /**
12477
+ * {@inheritdoc}
 
 
 
 
 
 
12478
  */
12479
+ public function getCurrent()
12480
  {
12481
+ return $this->current;
12482
+ }
12483
+
12484
+ /**
12485
+ * {@inheritdoc}
12486
+ */
12487
+ public function getMaster()
12488
+ {
12489
+ if ($this->master) {
12490
+ return $this->master;
12491
  }
12492
 
12493
+ if ($this->updateSentinels) {
12494
+ $this->updateSentinels();
12495
+ }
12496
 
12497
+ SENTINEL_QUERY: {
12498
+ $sentinel = $this->getSentinelConnection();
 
 
12499
 
12500
+ try {
12501
+ $masterParameters = $this->querySentinelForMaster($sentinel, $this->service);
12502
+ $masterConnection = $this->connectionFactory->create($masterParameters);
12503
+
12504
+ $this->add($masterConnection);
12505
+ } catch (ConnectionException $exception) {
12506
+ $this->sentinelConnection = null;
12507
+
12508
+ goto SENTINEL_QUERY;
12509
  }
12510
  }
12511
 
12512
+ return $masterConnection;
12513
  }
12514
 
12515
  /**
12516
+ * {@inheritdoc}
 
 
12517
  */
12518
+ public function getSlaves()
12519
  {
12520
+ if ($this->slaves) {
12521
+ return array_values($this->slaves);
12522
  }
12523
 
12524
+ if ($this->updateSentinels) {
12525
+ $this->updateSentinels();
12526
+ }
12527
+
12528
+ SENTINEL_QUERY: {
12529
+ $sentinel = $this->getSentinelConnection();
12530
+
12531
+ try {
12532
+ $slavesParameters = $this->querySentinelForSlaves($sentinel, $this->service);
12533
+
12534
+ foreach ($slavesParameters as $slaveParameters) {
12535
+ $this->add($this->connectionFactory->create($slaveParameters));
12536
+ }
12537
+ } catch (ConnectionException $exception) {
12538
+ $this->sentinelConnection = null;
12539
+
12540
+ goto SENTINEL_QUERY;
12541
+ }
12542
+ }
12543
+
12544
+ return array_values($this->slaves ?: array());
12545
  }
12546
 
12547
  /**
12548
+ * Returns a random slave.
 
 
 
 
12549
  *
12550
+ * @return NodeConnectionInterface
12551
  */
12552
+ protected function pickSlave()
12553
  {
12554
+ if ($slaves = $this->getSlaves()) {
12555
+ return $slaves[rand(1, count($slaves)) - 1];
 
 
 
 
 
12556
  }
 
 
 
12557
  }
12558
 
12559
  /**
12560
+ * Returns the connection instance in charge for the given command.
 
 
12561
  *
12562
+ * @param CommandInterface $command Command instance.
12563
  *
12564
+ * @return NodeConnectionInterface
12565
  */
12566
+ private function getConnectionInternal(CommandInterface $command)
12567
  {
12568
+ if (!$this->current) {
12569
+ if ($this->strategy->isReadOperation($command) && $slave = $this->pickSlave()) {
12570
+ $this->current = $slave;
12571
+ } else {
12572
+ $this->current = $this->getMaster();
12573
+ }
12574
+
12575
+ return $this->current;
12576
  }
12577
 
12578
+ if ($this->current === $this->master) {
12579
+ return $this->current;
12580
  }
12581
 
12582
+ if (!$this->strategy->isReadOperation($command)) {
12583
+ $this->current = $this->getMaster();
12584
+ }
12585
 
12586
+ return $this->current;
12587
  }
12588
 
12589
  /**
12590
+ * Asserts that the specified connection matches an expected role.
 
 
12591
  *
12592
+ * @param NodeConnectionInterface $sentinel Connection to a redis server.
12593
+ * @param string $role Expected role of the server ("master", "slave" or "sentinel").
12594
  */
12595
+ protected function assertConnectionRole(NodeConnectionInterface $connection, $role)
12596
  {
12597
+ $role = strtolower($role);
12598
+ $actualRole = $connection->executeCommand(RawCommand::create('ROLE'));
12599
 
12600
+ if ($role !== $actualRole[0]) {
12601
+ throw new RoleException($connection, "Expected $role but got $actualRole[0] [$connection]");
12602
+ }
12603
+ }
12604
 
12605
+ /**
12606
+ * {@inheritdoc}
12607
+ */
12608
+ public function getConnection(CommandInterface $command)
12609
+ {
12610
+ $connection = $this->getConnectionInternal($command);
12611
+
12612
+ if (!$connection->isConnected()) {
12613
+ // When we do not have any available slave in the pool we can expect
12614
+ // read-only operations to hit the master server.
12615
+ $expectedRole = $this->strategy->isReadOperation($command) && $this->slaves ? 'slave' : 'master';
12616
+ $this->assertConnectionRole($connection, $expectedRole);
12617
+ }
12618
 
12619
  return $connection;
12620
  }
12622
  /**
12623
  * {@inheritdoc}
12624
  */
12625
+ public function getConnectionById($connectionId)
12626
  {
12627
+ if ($connectionId === 'master') {
12628
+ return $this->getMaster();
 
 
 
 
12629
  }
12630
 
12631
+ $this->getSlaves();
12632
+
12633
+ if (isset($this->slaves[$connectionId])) {
12634
+ return $this->slaves[$connectionId];
12635
  }
12636
  }
12637
 
12638
  /**
12639
+ * {@inheritdoc}
 
 
 
 
 
 
12640
  */
12641
+ public function switchTo($connection)
12642
  {
12643
+ if (!$connection instanceof NodeConnectionInterface) {
12644
+ $connection = $this->getConnectionById($connection);
12645
  }
12646
 
12647
+ if ($connection && $connection === $this->current) {
12648
+ return;
12649
  }
12650
 
12651
+ if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
12652
+ throw new \InvalidArgumentException('Invalid connection or connection not found.');
12653
+ }
12654
 
12655
+ $connection->connect();
12656
+
12657
+ if ($this->current) {
12658
+ $this->current->disconnect();
12659
  }
12660
 
12661
+ $this->current = $connection;
12662
  }
12663
 
12664
  /**
12665
+ * Switches to the master server.
12666
  */
12667
+ public function switchToMaster()
12668
  {
12669
+ $this->switchTo('master');
 
 
12670
  }
12671
 
12672
  /**
12673
+ * Switches to a random slave server.
 
 
12674
  */
12675
+ public function switchToSlave()
12676
  {
12677
+ $connection = $this->pickSlave();
12678
+ $this->switchTo($connection);
 
12679
  }
12680
 
12681
  /**
12682
+ * {@inheritdoc}
 
 
 
 
12683
  */
12684
+ public function isConnected()
12685
  {
12686
+ return $this->current ? $this->current->isConnected() : false;
 
12687
  }
12688
 
12689
  /**
12690
+ * {@inheritdoc}
 
 
 
 
 
12691
  */
12692
+ public function connect()
12693
  {
12694
+ if (!$this->current) {
12695
+ if (!$this->current = $this->pickSlave()) {
12696
+ $this->current = $this->getMaster();
12697
+ }
 
 
 
 
 
 
 
12698
  }
12699
+
12700
+ $this->current->connect();
12701
  }
12702
 
12703
  /**
12704
+ * {@inheritdoc}
 
 
 
 
 
 
12705
  */
12706
+ public function disconnect()
12707
  {
12708
+ if ($this->master) {
12709
+ $this->master->disconnect();
 
 
12710
  }
12711
 
12712
+ foreach ($this->slaves as $connection) {
12713
+ $connection->disconnect();
12714
  }
 
 
 
 
 
12715
  }
12716
 
12717
  /**
12718
+ * Retries the execution of a command upon server failure after asking a new
12719
+ * configuration to one of the sentinels.
12720
  *
12721
+ * @param CommandInterface $command Command instance.
12722
+ * @param string $method Actual method.
12723
  *
12724
  * @return mixed
12725
  */
12726
+ private function retryCommandOnFailure(CommandInterface $command, $method)
12727
  {
12728
+ $retries = 0;
12729
 
12730
+ SENTINEL_RETRY: {
12731
+ try {
12732
+ $response = $this->getConnection($command)->$method($command);
12733
+ } catch (CommunicationException $exception) {
12734
+ $this->wipeServerList();
12735
+ $exception->getConnection()->disconnect();
12736
 
12737
+ if ($retries == $this->retryLimit) {
12738
+ throw $exception;
12739
+ }
12740
+
12741
+ usleep($this->retryWait * 1000);
12742
+
12743
+ ++$retries;
12744
+ goto SENTINEL_RETRY;
12745
+ }
12746
+ }
12747
 
12748
  return $response;
12749
  }
12753
  */
12754
  public function writeRequest(CommandInterface $command)
12755
  {
12756
+ $this->retryCommandOnFailure($command, __FUNCTION__);
12757
  }
12758
 
12759
  /**
12761
  */
12762
  public function readResponse(CommandInterface $command)
12763
  {
12764
+ return $this->retryCommandOnFailure($command, __FUNCTION__);
12765
  }
12766
 
12767
  /**
12769
  */
12770
  public function executeCommand(CommandInterface $command)
12771
  {
12772
+ return $this->retryCommandOnFailure($command, __FUNCTION__);
 
 
 
 
 
 
 
12773
  }
12774
 
12775
  /**
12776
+ * Returns the underlying replication strategy.
12777
+ *
12778
+ * @return ReplicationStrategy
12779
  */
12780
+ public function getReplicationStrategy()
12781
  {
12782
+ return $this->strategy;
12783
  }
12784
 
12785
  /**
12786
  * {@inheritdoc}
12787
  */
12788
+ public function __sleep()
12789
  {
12790
+ return array(
12791
+ 'master', 'slaves', 'service', 'sentinels', 'connectionFactory', 'strategy',
12792
+ );
12793
  }
12794
+ }
12795
 
12796
+ /* --------------------------------------------------------------------------- */
12797
+
12798
+ namespace Predis\Cluster;
12799
+
12800
+ use Predis\Command\CommandInterface;
12801
+ use Predis\Command\ScriptCommand;
12802
+ use Predis\Cluster\Distributor\DistributorInterface;
12803
+ use Predis\Cluster\Distributor\HashRing;
12804
+ use Predis\Cluster\Hash\CRC16;
12805
+ use Predis\Cluster\Hash\HashGeneratorInterface;
12806
+ use Predis\NotSupportedException;
12807
 
12808
+ /**
12809
+ * Interface for classes defining the strategy used to calculate an hash out of
12810
+ * keys extracted from supported commands.
12811
+ *
12812
+ * This is mostly useful to support clustering via client-side sharding.
12813
+ *
12814
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12815
+ */
12816
+ interface StrategyInterface
12817
+ {
12818
  /**
12819
+ * Returns a slot for the given command used for clustering distribution or
12820
+ * NULL when this is not possible.
12821
  *
12822
+ * @param CommandInterface $command Command instance.
12823
+ *
12824
+ * @return int
12825
  */
12826
+ public function getSlot(CommandInterface $command);
 
 
 
12827
 
12828
  /**
12829
+ * Returns a slot for the given key used for clustering distribution or NULL
12830
+ * when this is not possible.
 
 
 
 
12831
  *
12832
+ * @param string $key Key string.
 
12833
  *
12834
+ * @return int
12835
  */
12836
+ public function getSlotByKey($key);
 
 
 
12837
 
12838
  /**
12839
+ * Returns a distributor instance to be used by the cluster.
 
 
 
 
 
12840
  *
12841
+ * @return DistributorInterface
12842
  */
12843
+ public function getDistributor();
 
 
 
 
 
 
12844
  }
12845
 
12846
  /**
12847
+ * Common class implementing the logic needed to support clustering strategies.
 
12848
  *
12849
  * @author Daniele Alessandri <suppakilla@gmail.com>
 
 
12850
  */
12851
+ abstract class ClusterStrategy implements StrategyInterface
12852
  {
12853
+ protected $commands;
 
 
12854
 
12855
  /**
12856
+ *
12857
  */
12858
+ public function __construct()
12859
  {
12860
+ $this->commands = $this->getDefaultCommands();
 
 
12861
  }
12862
 
12863
  /**
12864
+ * Returns the default map of supported commands with their handlers.
12865
+ *
12866
+ * @return array
12867
  */
12868
+ protected function getDefaultCommands()
12869
  {
12870
+ $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
12871
+ $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
 
 
 
12872
 
12873
+ return array(
12874
+ /* commands operating on the key space */
12875
+ 'EXISTS' => $getKeyFromAllArguments,
12876
+ 'DEL' => $getKeyFromAllArguments,
12877
+ 'TYPE' => $getKeyFromFirstArgument,
12878
+ 'EXPIRE' => $getKeyFromFirstArgument,
12879
+ 'EXPIREAT' => $getKeyFromFirstArgument,
12880
+ 'PERSIST' => $getKeyFromFirstArgument,
12881
+ 'PEXPIRE' => $getKeyFromFirstArgument,
12882
+ 'PEXPIREAT' => $getKeyFromFirstArgument,
12883
+ 'TTL' => $getKeyFromFirstArgument,
12884
+ 'PTTL' => $getKeyFromFirstArgument,
12885
+ 'SORT' => array($this, 'getKeyFromSortCommand'),
12886
+ 'DUMP' => $getKeyFromFirstArgument,
12887
+ 'RESTORE' => $getKeyFromFirstArgument,
12888
+
12889
+ /* commands operating on string values */
12890
+ 'APPEND' => $getKeyFromFirstArgument,
12891
+ 'DECR' => $getKeyFromFirstArgument,
12892
+ 'DECRBY' => $getKeyFromFirstArgument,
12893
+ 'GET' => $getKeyFromFirstArgument,
12894
+ 'GETBIT' => $getKeyFromFirstArgument,
12895
+ 'MGET' => $getKeyFromAllArguments,
12896
+ 'SET' => $getKeyFromFirstArgument,
12897
+ 'GETRANGE' => $getKeyFromFirstArgument,
12898
+ 'GETSET' => $getKeyFromFirstArgument,
12899
+ 'INCR' => $getKeyFromFirstArgument,
12900
+ 'INCRBY' => $getKeyFromFirstArgument,
12901
+ 'INCRBYFLOAT' => $getKeyFromFirstArgument,
12902
+ 'SETBIT' => $getKeyFromFirstArgument,
12903
+ 'SETEX' => $getKeyFromFirstArgument,
12904
+ 'MSET' => array($this, 'getKeyFromInterleavedArguments'),
12905
+ 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
12906
+ 'SETNX' => $getKeyFromFirstArgument,
12907
+ 'SETRANGE' => $getKeyFromFirstArgument,
12908
+ 'STRLEN' => $getKeyFromFirstArgument,
12909
+ 'SUBSTR' => $getKeyFromFirstArgument,
12910
+ 'BITOP' => array($this, 'getKeyFromBitOp'),
12911
+ 'BITCOUNT' => $getKeyFromFirstArgument,
12912
+ 'BITFIELD' => $getKeyFromFirstArgument,
12913
+
12914
+ /* commands operating on lists */
12915
+ 'LINSERT' => $getKeyFromFirstArgument,
12916
+ 'LINDEX' => $getKeyFromFirstArgument,
12917
+ 'LLEN' => $getKeyFromFirstArgument,
12918
+ 'LPOP' => $getKeyFromFirstArgument,
12919
+ 'RPOP' => $getKeyFromFirstArgument,
12920
+ 'RPOPLPUSH' => $getKeyFromAllArguments,
12921
+ 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
12922
+ 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
12923
+ 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
12924
+ 'LPUSH' => $getKeyFromFirstArgument,
12925
+ 'LPUSHX' => $getKeyFromFirstArgument,
12926
+ 'RPUSH' => $getKeyFromFirstArgument,
12927
+ 'RPUSHX' => $getKeyFromFirstArgument,
12928
+ 'LRANGE' => $getKeyFromFirstArgument,
12929
+ 'LREM' => $getKeyFromFirstArgument,
12930
+ 'LSET' => $getKeyFromFirstArgument,
12931
+ 'LTRIM' => $getKeyFromFirstArgument,
12932
+
12933
+ /* commands operating on sets */
12934
+ 'SADD' => $getKeyFromFirstArgument,
12935
+ 'SCARD' => $getKeyFromFirstArgument,
12936
+ 'SDIFF' => $getKeyFromAllArguments,
12937
+ 'SDIFFSTORE' => $getKeyFromAllArguments,
12938
+ 'SINTER' => $getKeyFromAllArguments,
12939
+ 'SINTERSTORE' => $getKeyFromAllArguments,
12940
+ 'SUNION' => $getKeyFromAllArguments,
12941
+ 'SUNIONSTORE' => $getKeyFromAllArguments,
12942
+ 'SISMEMBER' => $getKeyFromFirstArgument,
12943
+ 'SMEMBERS' => $getKeyFromFirstArgument,
12944
+ 'SSCAN' => $getKeyFromFirstArgument,
12945
+ 'SPOP' => $getKeyFromFirstArgument,
12946
+ 'SRANDMEMBER' => $getKeyFromFirstArgument,
12947
+ 'SREM' => $getKeyFromFirstArgument,
12948
 
12949
+ /* commands operating on sorted sets */
12950
+ 'ZADD' => $getKeyFromFirstArgument,
12951
+ 'ZCARD' => $getKeyFromFirstArgument,
12952
+ 'ZCOUNT' => $getKeyFromFirstArgument,
12953
+ 'ZINCRBY' => $getKeyFromFirstArgument,
12954
+ 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
12955
+ 'ZRANGE' => $getKeyFromFirstArgument,
12956
+ 'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
12957
+ 'ZRANK' => $getKeyFromFirstArgument,
12958
+ 'ZREM' => $getKeyFromFirstArgument,
12959
+ 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
12960
+ 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
12961
+ 'ZREVRANGE' => $getKeyFromFirstArgument,
12962
+ 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
12963
+ 'ZREVRANK' => $getKeyFromFirstArgument,
12964
+ 'ZSCORE' => $getKeyFromFirstArgument,
12965
+ 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
12966
+ 'ZSCAN' => $getKeyFromFirstArgument,
12967
+ 'ZLEXCOUNT' => $getKeyFromFirstArgument,
12968
+ 'ZRANGEBYLEX' => $getKeyFromFirstArgument,
12969
+ 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
12970
+ 'ZREVRANGEBYLEX' => $getKeyFromFirstArgument,
12971
 
12972
+ /* commands operating on hashes */
12973
+ 'HDEL' => $getKeyFromFirstArgument,
12974
+ 'HEXISTS' => $getKeyFromFirstArgument,
12975
+ 'HGET' => $getKeyFromFirstArgument,
12976
+ 'HGETALL' => $getKeyFromFirstArgument,
12977
+ 'HMGET' => $getKeyFromFirstArgument,
12978
+ 'HMSET' => $getKeyFromFirstArgument,
12979
+ 'HINCRBY' => $getKeyFromFirstArgument,
12980
+ 'HINCRBYFLOAT' => $getKeyFromFirstArgument,
12981
+ 'HKEYS' => $getKeyFromFirstArgument,
12982
+ 'HLEN' => $getKeyFromFirstArgument,
12983
+ 'HSET' => $getKeyFromFirstArgument,
12984
+ 'HSETNX' => $getKeyFromFirstArgument,
12985
+ 'HVALS' => $getKeyFromFirstArgument,
12986
+ 'HSCAN' => $getKeyFromFirstArgument,
12987
+ 'HSTRLEN' => $getKeyFromFirstArgument,
12988
 
12989
+ /* commands operating on HyperLogLog */
12990
+ 'PFADD' => $getKeyFromFirstArgument,
12991
+ 'PFCOUNT' => $getKeyFromAllArguments,
12992
+ 'PFMERGE' => $getKeyFromAllArguments,
 
 
12993
 
12994
+ /* scripting */
12995
+ 'EVAL' => array($this, 'getKeyFromScriptingCommands'),
12996
+ 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
 
 
12997
 
12998
+ /* commands performing geospatial operations */
12999
+ 'GEOADD' => $getKeyFromFirstArgument,
13000
+ 'GEOHASH' => $getKeyFromFirstArgument,
13001
+ 'GEOPOS' => $getKeyFromFirstArgument,
13002
+ 'GEODIST' => $getKeyFromFirstArgument,
13003
+ 'GEORADIUS' => array($this, 'getKeyFromGeoradiusCommands'),
13004
+ 'GEORADIUSBYMEMBER' => array($this, 'getKeyFromGeoradiusCommands'),
13005
+ );
13006
  }
13007
 
13008
  /**
13009
+ * Returns the list of IDs for the supported commands.
13010
+ *
13011
+ * @return array
13012
  */
13013
+ public function getSupportedCommands()
13014
  {
13015
+ return array_keys($this->commands);
 
 
 
 
 
 
 
13016
  }
13017
 
13018
  /**
13019
+ * Sets an handler for the specified command ID.
13020
  *
13021
+ * The signature of the callback must have a single parameter of type
13022
+ * Predis\Command\CommandInterface.
13023
  *
13024
+ * When the callback argument is omitted or NULL, the previously associated
13025
+ * handler for the specified command ID is removed.
13026
+ *
13027
+ * @param string $commandID Command ID.
13028
+ * @param mixed $callback A valid callable object, or NULL to unset the handler.
13029
+ *
13030
+ * @throws \InvalidArgumentException
13031
  */
13032
+ public function setCommandHandler($commandID, $callback = null)
13033
  {
13034
+ $commandID = strtoupper($commandID);
 
 
13035
 
13036
+ if (!isset($callback)) {
13037
+ unset($this->commands[$commandID]);
13038
 
13039
+ return;
13040
+ }
 
 
 
 
13041
 
13042
+ if (!is_callable($callback)) {
13043
+ throw new \InvalidArgumentException(
13044
+ 'The argument must be a callable object or NULL.'
13045
  );
13046
  }
13047
 
13048
+ $this->commands[$commandID] = $callback;
 
 
13049
  }
13050
 
13051
  /**
13052
+ * Extracts the key from the first argument of a command instance.
13053
+ *
13054
+ * @param CommandInterface $command Command instance.
13055
+ *
13056
+ * @return string
13057
  */
13058
+ protected function getKeyFromFirstArgument(CommandInterface $command)
13059
  {
13060
+ return $command->getArgument(0);
13061
  }
13062
 
13063
  /**
13064
+ * Extracts the key from a command with multiple keys only when all keys in
13065
+ * the arguments array produce the same hash.
13066
  *
13067
+ * @param CommandInterface $command Command instance.
13068
  *
13069
+ * @return string|null
13070
  */
13071
+ protected function getKeyFromAllArguments(CommandInterface $command)
13072
  {
13073
+ $arguments = $command->getArguments();
 
13074
 
13075
+ if ($this->checkSameSlotForKeys($arguments)) {
13076
+ return $arguments[0];
13077
+ }
13078
  }
13079
 
13080
  /**
13081
+ * Extracts the key from a command with multiple keys only when all keys in
13082
+ * the arguments array produce the same hash.
13083
  *
13084
+ * @param CommandInterface $command Command instance.
13085
+ *
13086
+ * @return string|null
13087
  */
13088
+ protected function getKeyFromInterleavedArguments(CommandInterface $command)
13089
  {
13090
+ $arguments = $command->getArguments();
13091
+ $keys = array();
13092
 
13093
+ for ($i = 0; $i < count($arguments); $i += 2) {
13094
+ $keys[] = $arguments[$i];
13095
+ }
 
 
 
 
13096
 
13097
+ if ($this->checkSameSlotForKeys($keys)) {
13098
+ return $arguments[0];
13099
+ }
 
 
 
13100
  }
13101
 
13102
  /**
13103
+ * Extracts the key from SORT command.
13104
+ *
13105
+ * @param CommandInterface $command Command instance.
13106
+ *
13107
+ * @return string|null
13108
  */
13109
+ protected function getKeyFromSortCommand(CommandInterface $command)
13110
  {
13111
+ $arguments = $command->getArguments();
13112
+ $firstKey = $arguments[0];
13113
 
13114
+ if (1 === $argc = count($arguments)) {
13115
+ return $firstKey;
13116
+ }
 
 
 
 
13117
 
13118
+ $keys = array($firstKey);
13119
+
13120
+ for ($i = 1; $i < $argc; ++$i) {
13121
+ if (strtoupper($arguments[$i]) === 'STORE') {
13122
+ $keys[] = $arguments[++$i];
13123
+ }
13124
+ }
13125
+
13126
+ if ($this->checkSameSlotForKeys($keys)) {
13127
+ return $firstKey;
13128
+ }
13129
  }
13130
 
13131
  /**
13132
+ * Extracts the key from BLPOP and BRPOP commands.
13133
  *
13134
+ * @param CommandInterface $command Command instance.
13135
  *
13136
+ * @return string|null
13137
  */
13138
+ protected function getKeyFromBlockingListCommands(CommandInterface $command)
13139
  {
13140
+ $arguments = $command->getArguments();
13141
 
13142
+ if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
13143
+ return $arguments[0];
13144
  }
 
 
13145
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13146
 
13147
  /**
13148
+ * Extracts the key from BITOP command.
13149
+ *
13150
+ * @param CommandInterface $command Command instance.
13151
+ *
13152
+ * @return string|null
13153
  */
13154
+ protected function getKeyFromBitOp(CommandInterface $command)
13155
  {
13156
+ $arguments = $command->getArguments();
13157
+
13158
+ if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) {
13159
+ return $arguments[1];
13160
+ }
13161
  }
13162
 
13163
  /**
13164
+ * Extracts the key from GEORADIUS and GEORADIUSBYMEMBER commands.
13165
+ *
13166
+ * @param CommandInterface $command Command instance.
13167
+ *
13168
+ * @return string|null
13169
  */
13170
+ protected function getKeyFromGeoradiusCommands(CommandInterface $command)
13171
  {
13172
+ $arguments = $command->getArguments();
13173
+ $argc = count($arguments);
13174
+ $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
13175
+
13176
+ if ($argc > $startIndex) {
13177
+ $keys = array($arguments[0]);
13178
+
13179
+ for ($i = $startIndex; $i < $argc; ++$i) {
13180
+ $argument = strtoupper($arguments[$i]);
13181
+ if ($argument === 'STORE' || $argument === 'STOREDIST') {
13182
+ $keys[] = $arguments[++$i];
13183
+ }
13184
+ }
13185
+
13186
+ if ($this->checkSameSlotForKeys($keys)) {
13187
+ return $arguments[0];
13188
+ } else {
13189
+ return;
13190
+ }
13191
  }
13192
+
13193
+ return $arguments[0];
13194
  }
13195
 
13196
  /**
13197
+ * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
13198
+ *
13199
+ * @param CommandInterface $command Command instance.
13200
+ *
13201
+ * @return string|null
13202
  */
13203
+ protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
13204
  {
13205
+ $arguments = $command->getArguments();
13206
+ $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
13207
+
13208
+ if ($this->checkSameSlotForKeys($keys)) {
13209
+ return $arguments[0];
13210
+ }
13211
  }
13212
 
13213
  /**
13214
+ * Extracts the key from EVAL and EVALSHA commands.
13215
+ *
13216
+ * @param CommandInterface $command Command instance.
13217
+ *
13218
+ * @return string|null
13219
  */
13220
+ protected function getKeyFromScriptingCommands(CommandInterface $command)
13221
  {
13222
+ if ($command instanceof ScriptCommand) {
13223
+ $keys = $command->getKeys();
 
 
13224
  } else {
13225
+ $keys = array_slice($args = $command->getArguments(), 2, $args[1]);
13226
  }
13227
 
13228
+ if ($keys && $this->checkSameSlotForKeys($keys)) {
13229
+ return $keys[0];
13230
+ }
13231
  }
13232
 
13233
  /**
13234
  * {@inheritdoc}
13235
  */
13236
+ public function getSlot(CommandInterface $command)
13237
  {
13238
+ $slot = $command->getSlot();
 
 
13239
 
13240
+ if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) {
13241
+ $key = call_user_func($this->commands[$cmdID], $command);
 
 
 
13242
 
13243
+ if (isset($key)) {
13244
+ $slot = $this->getSlotByKey($key);
13245
+ $command->setSlot($slot);
13246
  }
13247
  }
13248
 
13249
+ return $slot;
13250
  }
13251
 
13252
  /**
13253
+ * Checks if the specified array of keys will generate the same hash.
13254
+ *
13255
+ * @param array $keys Array of keys.
13256
+ *
13257
+ * @return bool
13258
  */
13259
+ protected function checkSameSlotForKeys(array $keys)
13260
  {
13261
+ if (!$count = count($keys)) {
13262
+ return false;
 
 
 
 
 
13263
  }
13264
 
13265
+ $currentSlot = $this->getSlotByKey($keys[0]);
 
 
13266
 
13267
+ for ($i = 1; $i < $count; ++$i) {
13268
+ $nextSlot = $this->getSlotByKey($keys[$i]);
13269
+
13270
+ if ($currentSlot !== $nextSlot) {
13271
+ return false;
13272
+ }
13273
+
13274
+ $currentSlot = $nextSlot;
13275
  }
13276
 
13277
+ return true;
13278
  }
13279
 
13280
  /**
13281
+ * Returns only the hashable part of a key (delimited by "{...}"), or the
13282
+ * whole key if a key tag is not found in the string.
13283
+ *
13284
+ * @param string $key A key.
13285
+ *
13286
+ * @return string
13287
  */
13288
+ protected function extractKeyTag($key)
13289
  {
13290
+ if (false !== $start = strpos($key, '{')) {
13291
+ if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
13292
+ $key = substr($key, $start, $end - $start);
13293
+ }
 
 
13294
  }
13295
 
13296
+ return $key;
13297
  }
13298
+ }
13299
+
13300
+ /**
13301
+ * Default cluster strategy used by Predis to handle client-side sharding.
13302
+ *
13303
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13304
+ */
13305
+ class PredisStrategy extends ClusterStrategy
13306
+ {
13307
+ protected $distributor;
13308
 
13309
  /**
13310
+ * @param DistributorInterface $distributor Optional distributor instance.
13311
  */
13312
+ public function __construct(DistributorInterface $distributor = null)
13313
  {
13314
+ parent::__construct();
 
 
 
 
 
 
 
13315
 
13316
+ $this->distributor = $distributor ?: new HashRing();
13317
  }
13318
 
13319
  /**
13320
  * {@inheritdoc}
13321
  */
13322
+ public function getSlotByKey($key)
13323
  {
13324
+ $key = $this->extractKeyTag($key);
13325
+ $hash = $this->distributor->hash($key);
13326
+ $slot = $this->distributor->getSlot($hash);
13327
+
13328
+ return $slot;
13329
  }
13330
 
13331
  /**
13332
  * {@inheritdoc}
13333
  */
13334
+ protected function checkSameSlotForKeys(array $keys)
13335
  {
13336
+ if (!$count = count($keys)) {
13337
+ return false;
13338
+ }
13339
+
13340
+ $currentKey = $this->extractKeyTag($keys[0]);
13341
+
13342
+ for ($i = 1; $i < $count; ++$i) {
13343
+ $nextKey = $this->extractKeyTag($keys[$i]);
13344
+
13345
+ if ($currentKey !== $nextKey) {
13346
+ return false;
13347
+ }
13348
+
13349
+ $currentKey = $nextKey;
13350
+ }
13351
+
13352
+ return true;
13353
  }
13354
 
13355
  /**
13356
  * {@inheritdoc}
13357
  */
13358
+ public function getDistributor()
13359
  {
13360
+ return $this->distributor;
13361
  }
13362
+ }
13363
 
13364
+ /**
13365
+ * Default class used by Predis to calculate hashes out of keys of
13366
+ * commands supported by redis-cluster.
13367
+ *
13368
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13369
+ */
13370
+ class RedisStrategy extends ClusterStrategy
13371
+ {
13372
+ protected $hashGenerator;
13373
 
13374
  /**
13375
+ * @param HashGeneratorInterface $hashGenerator Hash generator instance.
 
 
13376
  */
13377
+ public function __construct(HashGeneratorInterface $hashGenerator = null)
13378
  {
13379
+ parent::__construct();
 
13380
 
13381
+ $this->hashGenerator = $hashGenerator ?: new CRC16();
 
 
 
 
 
13382
  }
13383
 
13384
  /**
13385
  * {@inheritdoc}
13386
  */
13387
+ public function getSlotByKey($key)
13388
  {
13389
+ $key = $this->extractKeyTag($key);
13390
+ $slot = $this->hashGenerator->hash($key) & 0x3FFF;
 
 
13391
 
13392
+ return $slot;
13393
  }
13394
 
13395
  /**
13396
  * {@inheritdoc}
13397
  */
13398
+ public function getDistributor()
13399
  {
13400
+ throw new NotSupportedException(
13401
+ 'This cluster strategy does not provide an external distributor'
13402
+ );
 
 
 
 
13403
  }
13404
+ }
13405
+
13406
+ /* --------------------------------------------------------------------------- */
13407
+
13408
+ namespace Predis\Protocol;
13409
+
13410
+ use Predis\CommunicationException;
13411
+ use Predis\Command\CommandInterface;
13412
+ use Predis\Connection\CompositeConnectionInterface;
13413
 
13414
+ /**
13415
+ * Defines a pluggable protocol processor capable of serializing commands and
13416
+ * deserializing responses into PHP objects directly from a connection.
13417
+ *
13418
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13419
+ */
13420
+ interface ProtocolProcessorInterface
13421
+ {
13422
  /**
13423
+ * Writes a request over a connection to Redis.
13424
+ *
13425
+ * @param CompositeConnectionInterface $connection Redis connection.
13426
+ * @param CommandInterface $command Command instance.
13427
  */
13428
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command);
 
 
 
13429
 
13430
  /**
13431
+ * Reads a response from a connection to Redis.
13432
+ *
13433
+ * @param CompositeConnectionInterface $connection Redis connection.
13434
+ *
13435
+ * @return mixed
13436
  */
13437
+ public function read(CompositeConnectionInterface $connection);
13438
+ }
 
 
13439
 
13440
+ /**
13441
+ * Defines a pluggable serializer for Redis commands.
13442
+ *
13443
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13444
+ */
13445
+ interface RequestSerializerInterface
13446
+ {
13447
  /**
13448
+ * Serializes a Redis command.
13449
+ *
13450
+ * @param CommandInterface $command Redis command.
13451
+ *
13452
+ * @return string
13453
  */
13454
+ public function serialize(CommandInterface $command);
13455
+ }
 
 
13456
 
13457
+ /**
13458
+ * Defines a pluggable reader capable of parsing responses returned by Redis and
13459
+ * deserializing them to PHP objects.
13460
+ *
13461
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13462
+ */
13463
+ interface ResponseReaderInterface
13464
+ {
13465
  /**
13466
+ * Reads a response from a connection to Redis.
13467
+ *
13468
+ * @param CompositeConnectionInterface $connection Redis connection.
13469
+ *
13470
+ * @return mixed
13471
  */
13472
+ public function read(CompositeConnectionInterface $connection);
13473
+ }
13474
+
13475
+ /**
13476
+ * Exception used to indentify errors encountered while parsing the Redis wire
13477
+ * protocol.
13478
+ *
13479
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13480
+ */
13481
+ class ProtocolException extends CommunicationException
13482
+ {
13483
  }
13484
 
13485
  /* --------------------------------------------------------------------------- */
13726
  }
13727
 
13728
  /**
13729
+ * Command pipeline wrapped into a MULTI / EXEC transaction.
13730
  *
13731
  * @author Daniele Alessandri <suppakilla@gmail.com>
13732
  */
13733
+ class Atomic extends Pipeline
13734
  {
13735
+ /**
13736
+ * {@inheritdoc}
13737
+ */
13738
+ public function __construct(ClientInterface $client)
13739
+ {
13740
+ if (!$client->getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) {
13741
+ throw new ClientException(
13742
+ "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'."
13743
+ );
13744
+ }
13745
+
13746
+ parent::__construct($client);
13747
+ }
13748
+
13749
+ /**
13750
+ * {@inheritdoc}
13751
+ */
13752
+ protected function getConnection()
13753
+ {
13754
+ $connection = $this->getClient()->getConnection();
13755
+
13756
+ if (!$connection instanceof NodeConnectionInterface) {
13757
+ $class = __CLASS__;
13758
+
13759
+ throw new ClientException("The class '$class' does not support aggregate connections.");
13760
+ }
13761
+
13762
+ return $connection;
13763
+ }
13764
+
13765
  /**
13766
  * {@inheritdoc}
13767
  */
13768
  protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands)
13769
  {
13770
+ $profile = $this->getClient()->getProfile();
13771
+ $connection->executeCommand($profile->createCommand('multi'));
13772
+
13773
+ foreach ($commands as $command) {
13774
+ $connection->writeRequest($command);
13775
  }
13776
 
13777
+ foreach ($commands as $command) {
13778
+ $response = $connection->readResponse($command);
13779
 
13780
+ if ($response instanceof ErrorResponseInterface) {
13781
+ $connection->executeCommand($profile->createCommand('discard'));
13782
+ throw new ServerException($response->getMessage());
13783
+ }
13784
+ }
13785
+
13786
+ $executed = $connection->executeCommand($profile->createCommand('exec'));
13787
+
13788
+ if (!isset($executed)) {
13789
+ // TODO: should be throwing a more appropriate exception.
13790
+ throw new ClientException(
13791
+ 'The underlying transaction has been aborted by the server.'
13792
+ );
13793
+ }
13794
+
13795
+ if (count($executed) !== count($commands)) {
13796
+ $expected = count($commands);
13797
+ $received = count($executed);
13798
+
13799
+ throw new ClientException(
13800
+ "Invalid number of responses [expected $expected, received $received]."
13801
+ );
13802
+ }
13803
+
13804
+ $responses = array();
13805
+ $sizeOfPipe = count($commands);
13806
+ $exceptions = $this->throwServerExceptions();
13807
+
13808
+ for ($i = 0; $i < $sizeOfPipe; ++$i) {
13809
+ $command = $commands->dequeue();
13810
+ $response = $executed[$i];
13811
+
13812
+ if (!$response instanceof ResponseInterface) {
13813
+ $responses[] = $command->parseResponse($response);
13814
+ } elseif ($response instanceof ErrorResponseInterface && $exceptions) {
13815
+ $this->exception($connection, $response);
13816
+ } else {
13817
+ $responses[] = $response;
13818
+ }
13819
+
13820
+ unset($executed[$i]);
13821
+ }
13822
+
13823
+ return $responses;
13824
  }
13825
  }
13826
 
13929
  } catch (CommunicationException $exception) {
13930
  $responses[$i] = $exception;
13931
  $exceptions[$connectionHash] = $exception;
13932
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13933
  }
13934
 
13935
  return $responses;
13936
  }
13937
  }
13938
 
13939
+ /**
13940
+ * Command pipeline that writes commands to the servers but discards responses.
13941
+ *
13942
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13943
+ */
13944
+ class FireAndForget extends Pipeline
13945
+ {
13946
+ /**
13947
+ * {@inheritdoc}
13948
+ */
13949
+ protected function executePipeline(ConnectionInterface $connection, \SplQueue $commands)
13950
+ {
13951
+ while (!$commands->isEmpty()) {
13952
+ $connection->writeRequest($commands->dequeue());
13953
+ }
13954
+
13955
+ $connection->disconnect();
13956
+
13957
+ return array();
13958
+ }
13959
+ }
13960
+
13961
  /* --------------------------------------------------------------------------- */
13962
 
13963
  namespace Predis\Cluster\Distributor;
14288
  }
14289
  }
14290
 
14291
+ /**
14292
+ * Exception class that identifies empty rings.
14293
+ *
14294
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14295
+ */
14296
+ class EmptyRingException extends \Exception
14297
+ {
14298
+ }
14299
+
14300
  /**
14301
  * This class implements an hashring-based distributor that uses the same
14302
  * algorithm of libketama to distribute keys in a cluster using client-side
14356
  }
14357
  }
14358
 
 
 
 
 
 
 
 
 
 
14359
  /* --------------------------------------------------------------------------- */
14360
 
14361
  namespace Predis\Response\Iterator;
14556
  throw new \InvalidArgumentException(
14557
  'Cannot initialize a tuple iterator using an already initiated iterator.'
14558
  );
14559
+ }
14560
+
14561
+ if (($size = count($iterator)) % 2 !== 0) {
14562
+ throw new \UnexpectedValueException('Invalid response size for a tuple iterator.');
14563
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14564
  }
14565
 
14566
  /**
14567
  * {@inheritdoc}
14568
  */
14569
+ public function getInnerIterator()
14570
  {
14571
+ return $this->iterator;
14572
  }
14573
 
14574
  /**
14575
  * {@inheritdoc}
14576
  */
14577
+ public function __destruct()
14578
  {
14579
+ $this->iterator->drop(true);
14580
  }
14581
 
14582
  /**
14583
  * {@inheritdoc}
14584
  */
14585
+ protected function getValue()
14586
  {
14587
+ $k = $this->iterator->current();
14588
+ $this->iterator->next();
 
 
 
 
14589
 
14590
+ $v = $this->iterator->current();
14591
+ $this->iterator->next();
14592
+
14593
+ return array($k, $v);
14594
  }
14595
+ }
14596
+
14597
+ /* --------------------------------------------------------------------------- */
14598
+
14599
+ namespace Predis\Command\Processor;
14600
+
14601
+ use Predis\Command\CommandInterface;
14602
+ use Predis\Command\PrefixableCommandInterface;
14603
 
14604
+ /**
14605
+ * A command processor processes Redis commands before they are sent to Redis.
14606
+ *
14607
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14608
+ */
14609
+ interface ProcessorInterface
14610
+ {
14611
  /**
14612
+ * Processes the given Redis command.
14613
+ *
14614
+ * @param CommandInterface $command Command instance.
14615
  */
14616
+ public function process(CommandInterface $command);
 
 
 
 
14617
  }
14618
 
14619
  /**
14635
  $this->prefix = $prefix;
14636
  $this->commands = array(
14637
  /* ---------------- Redis 1.2 ---------------- */
14638
+ 'EXISTS' => 'static::all',
14639
  'DEL' => 'static::all',
14640
  'TYPE' => 'static::first',
14641
  'KEYS' => 'static::first',
14761
  'BITPOS' => 'static::first',
14762
  /* ---------------- Redis 3.2 ---------------- */
14763
  'HSTRLEN' => 'static::first',
14764
+ 'BITFIELD' => 'static::first',
14765
+ 'GEOADD' => 'static::first',
14766
+ 'GEOHASH' => 'static::first',
14767
+ 'GEOPOS' => 'static::first',
14768
+ 'GEODIST' => 'static::first',
14769
+ 'GEORADIUS' => 'static::georadius',
14770
+ 'GEORADIUSBYMEMBER' => 'static::georadius',
14771
  );
14772
  }
14773
 
14916
  }
14917
 
14918
  /**
14919
+ * Applies the specified prefix to all the arguments but the last one.
14920
+ *
14921
+ * @param CommandInterface $command Command instance.
14922
+ * @param string $prefix Prefix string.
14923
+ */
14924
+ public static function skipLast(CommandInterface $command, $prefix)
14925
+ {
14926
+ if ($arguments = $command->getArguments()) {
14927
+ $length = count($arguments);
14928
+
14929
+ for ($i = 0; $i < $length - 1; ++$i) {
14930
+ $arguments[$i] = "$prefix{$arguments[$i]}";
14931
+ }
14932
+
14933
+ $command->setRawArguments($arguments);
14934
+ }
14935
+ }
14936
+
14937
+ /**
14938
+ * Applies the specified prefix to the keys of a SORT command.
14939
+ *
14940
+ * @param CommandInterface $command Command instance.
14941
+ * @param string $prefix Prefix string.
14942
+ */
14943
+ public static function sort(CommandInterface $command, $prefix)
14944
+ {
14945
+ if ($arguments = $command->getArguments()) {
14946
+ $arguments[0] = "$prefix{$arguments[0]}";
14947
+
14948
+ if (($count = count($arguments)) > 1) {
14949
+ for ($i = 1; $i < $count; ++$i) {
14950
+ switch (strtoupper($arguments[$i])) {
14951
+ case 'BY':
14952
+ case 'STORE':
14953
+ $arguments[$i] = "$prefix{$arguments[++$i]}";
14954
+ break;
14955
+
14956
+ case 'GET':
14957
+ $value = $arguments[++$i];
14958
+ if ($value !== '#') {
14959
+ $arguments[$i] = "$prefix$value";
14960
+ }
14961
+ break;
14962
+
14963
+ case 'LIMIT';
14964
+ $i += 2;
14965
+ break;
14966
+ }
14967
+ }
14968
+ }
14969
+
14970
+ $command->setRawArguments($arguments);
14971
+ }
14972
+ }
14973
+
14974
+ /**
14975
+ * Applies the specified prefix to the keys of an EVAL-based command.
14976
+ *
14977
+ * @param CommandInterface $command Command instance.
14978
+ * @param string $prefix Prefix string.
14979
+ */
14980
+ public static function evalKeys(CommandInterface $command, $prefix)
14981
+ {
14982
+ if ($arguments = $command->getArguments()) {
14983
+ for ($i = 2; $i < $arguments[1] + 2; ++$i) {
14984
+ $arguments[$i] = "$prefix{$arguments[$i]}";
14985
+ }
14986
+
14987
+ $command->setRawArguments($arguments);
14988
+ }
14989
+ }
14990
+
14991
+ /**
14992
+ * Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE.
14993
+ *
14994
+ * @param CommandInterface $command Command instance.
14995
+ * @param string $prefix Prefix string.
14996
+ */
14997
+ public static function zsetStore(CommandInterface $command, $prefix)
14998
+ {
14999
+ if ($arguments = $command->getArguments()) {
15000
+ $arguments[0] = "$prefix{$arguments[0]}";
15001
+ $length = ((int) $arguments[1]) + 2;
15002
+
15003
+ for ($i = 2; $i < $length; ++$i) {
15004
+ $arguments[$i] = "$prefix{$arguments[$i]}";
15005
+ }
15006
+
15007
+ $command->setRawArguments($arguments);
15008
+ }
15009
+ }
15010
+
15011
+ /**
15012
+ * Applies the specified prefix to the key of a MIGRATE command.
15013
+ *
15014
+ * @param CommandInterface $command Command instance.
15015
+ * @param string $prefix Prefix string.
15016
+ */
15017
+ public static function migrate(CommandInterface $command, $prefix)
15018
+ {
15019
+ if ($arguments = $command->getArguments()) {
15020
+ $arguments[2] = "$prefix{$arguments[2]}";
15021
+ $command->setRawArguments($arguments);
15022
+ }
15023
+ }
15024
+
15025
+ /**
15026
+ * Applies the specified prefix to the key of a GEORADIUS command.
15027
+ *
15028
+ * @param CommandInterface $command Command instance.
15029
+ * @param string $prefix Prefix string.
15030
+ */
15031
+ public static function georadius(CommandInterface $command, $prefix)
15032
+ {
15033
+ if ($arguments = $command->getArguments()) {
15034
+ $arguments[0] = "$prefix{$arguments[0]}";
15035
+ $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
15036
+
15037
+ if (($count = count($arguments)) > $startIndex) {
15038
+ for ($i = $startIndex; $i < $count; ++$i) {
15039
+ switch (strtoupper($arguments[$i])) {
15040
+ case 'STORE':
15041
+ case 'STOREDIST':
15042
+ $arguments[$i] = "$prefix{$arguments[++$i]}";
15043
+ break;
15044
+
15045
+ }
15046
+ }
15047
+ }
15048
+
15049
+ $command->setRawArguments($arguments);
15050
+ }
15051
+ }
15052
+ }
15053
+
15054
+ /**
15055
+ * Default implementation of a command processors chain.
15056
+ *
15057
+ * @author Daniele Alessandri <suppakilla@gmail.com>
15058
+ */
15059
+ class ProcessorChain implements \ArrayAccess, ProcessorInterface
15060
+ {
15061
+ private $processors = array();
15062
+
15063
+ /**
15064
+ * @param array $processors List of instances of ProcessorInterface.
15065
+ */
15066
+ public function __construct($processors = array())
15067
+ {
15068
+ foreach ($processors as $processor) {
15069
+ $this->add($processor);
15070
+ }
15071
+ }
15072
+
15073
+ /**
15074
+ * {@inheritdoc}
15075
+ */
15076
+ public function add(ProcessorInterface $processor)
15077
+ {
15078
+ $this->processors[] = $processor;
15079
+ }
15080
+
15081
+ /**
15082
+ * {@inheritdoc}
15083
+ */
15084
+ public function remove(ProcessorInterface $processor)
15085
+ {
15086
+ if (false !== $index = array_search($processor, $this->processors, true)) {
15087
+ unset($this[$index]);
15088
+ }
15089
+ }
15090
+
15091
+ /**
15092
+ * {@inheritdoc}
15093
+ */
15094
+ public function process(CommandInterface $command)
15095
+ {
15096
+ for ($i = 0; $i < $count = count($this->processors); ++$i) {
15097
+ $this->processors[$i]->process($command);
15098
+ }
15099
+ }
15100
+
15101
+ /**
15102
+ * {@inheritdoc}
15103
+ */
15104
+ public function getProcessors()
15105
+ {
15106
+ return $this->processors;
15107
+ }
15108
+
15109
+ /**
15110
+ * Returns an iterator over the list of command processor in the chain.
15111
+ *
15112
+ * @return \ArrayIterator
15113
+ */
15114
+ public function getIterator()
15115
+ {
15116
+ return new \ArrayIterator($this->processors);
15117
+ }
15118
+
15119
+ /**
15120
+ * Returns the number of command processors in the chain.
15121
+ *
15122
+ * @return int
15123
+ */
15124
+ public function count()
15125
+ {
15126
+ return count($this->processors);
15127
+ }
15128
+
15129
+ /**
15130
+ * {@inheritdoc}
15131
  */
15132
+ public function offsetExists($index)
15133
  {
15134
+ return isset($this->processors[$index]);
 
 
 
 
 
 
 
 
15135
  }
15136
 
15137
  /**
15138
+ * {@inheritdoc}
 
 
 
15139
  */
15140
+ public function offsetGet($index)
15141
  {
15142
+ return $this->processors[$index];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15143
  }
15144
 
15145
  /**
15146
+ * {@inheritdoc}
 
 
 
15147
  */
15148
+ public function offsetSet($index, $processor)
15149
  {
15150
+ if (!$processor instanceof ProcessorInterface) {
15151
+ throw new \InvalidArgumentException(
15152
+ 'A processor chain accepts only instances of '.
15153
+ "'Predis\Command\Processor\ProcessorInterface'."
15154
+ );
 
15155
  }
15156
+
15157
+ $this->processors[$index] = $processor;
15158
  }
15159
 
15160
  /**
15161
+ * {@inheritdoc}
 
 
 
15162
  */
15163
+ public function offsetUnset($index)
15164
  {
15165
+ unset($this->processors[$index]);
15166
+ $this->processors = array_values($this->processors);
15167
+ }
15168
+ }
15169
 
15170
+ /* --------------------------------------------------------------------------- */
 
 
15171
 
15172
+ namespace Predis\Cluster\Hash;
 
 
15173
 
15174
+ /**
15175
+ * An hash generator implements the logic used to calculate the hash of a key to
15176
+ * distribute operations among Redis nodes.
15177
+ *
15178
+ * @author Daniele Alessandri <suppakilla@gmail.com>
15179
+ */
15180
+ interface HashGeneratorInterface
15181
+ {
15182
  /**
15183
+ * Generates an hash from a string to be used for distribution.
15184
  *
15185
+ * @param string $value String value.
15186
+ *
15187
+ * @return int
15188
  */
15189
+ public function hash($value);
15190
+ }
15191
+
15192
+ /**
15193
+ * Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
15194
+ *
15195
+ * @author Daniele Alessandri <suppakilla@gmail.com>
15196
+ */
15197
+ class CRC16 implements HashGeneratorInterface
15198
+ {
15199
+ private static $CCITT_16 = array(
15200
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
15201
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
15202
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
15203
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
15204
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
15205
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
15206
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
15207
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
15208
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
15209
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
15210
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
15211
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
15212
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
15213
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
15214
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
15215
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
15216
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
15217
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
15218
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
15219
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
15220
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
15221
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
15222
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
15223
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
15224
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
15225
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
15226
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
15227
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
15228
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
15229
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
15230
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
15231
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
15232
+ );
15233
+
15234
+ /**
15235
+ * {@inheritdoc}
15236
+ */
15237
+ public function hash($value)
15238
  {
15239
+ // CRC-CCITT-16 algorithm
15240
+ $crc = 0;
15241
+ $CCITT_16 = self::$CCITT_16;
15242
+ $strlen = strlen($value);
15243
+
15244
+ for ($i = 0; $i < $strlen; ++$i) {
15245
+ $crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
15246
  }
15247
+
15248
+ return $crc;
15249
  }
15250
  }
15251
 
15265
  use Predis\Response\Status as StatusResponse;
15266
 
15267
  /**
15268
+ * Composite protocol processor for the standard Redis wire protocol using
15269
+ * pluggable handlers to serialize requests and deserialize responses.
15270
  *
15271
  * @link http://redis.io/topics/protocol
15272
  *
15273
  * @author Daniele Alessandri <suppakilla@gmail.com>
15274
  */
15275
+ class CompositeProtocolProcessor implements ProtocolProcessorInterface
15276
  {
15277
+ /*
15278
+ * @var RequestSerializerInterface
15279
+ */
15280
+ protected $serializer;
15281
+
15282
+ /*
15283
+ * @var ResponseReaderInterface
15284
+ */
15285
+ protected $reader;
15286
 
15287
  /**
15288
+ * @param RequestSerializerInterface $serializer Request serializer.
15289
+ * @param ResponseReaderInterface $reader Response reader.
15290
  */
15291
+ public function __construct(
15292
+ RequestSerializerInterface $serializer = null,
15293
+ ResponseReaderInterface $reader = null
15294
+ ) {
15295
+ $this->setRequestSerializer($serializer ?: new RequestSerializer());
15296
+ $this->setResponseReader($reader ?: new ResponseReader());
15297
  }
15298
 
15299
  /**
15300
+ * {@inheritdoc}
 
 
15301
  */
15302
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command)
15303
  {
15304
+ $connection->writeBuffer($this->serializer->serialize($command));
 
 
 
 
 
 
15305
  }
15306
 
15307
  /**
15308
+ * {@inheritdoc}
 
 
 
15309
  */
15310
+ public function read(CompositeConnectionInterface $connection)
15311
  {
15312
+ return $this->reader->read($connection);
15313
  }
15314
 
15315
  /**
15316
+ * Sets the request serializer used by the protocol processor.
 
 
15317
  *
15318
+ * @param RequestSerializerInterface $serializer Request serializer.
15319
  */
15320
+ public function setRequestSerializer(RequestSerializerInterface $serializer)
15321
  {
15322
+ $this->serializer = $serializer;
 
 
 
 
15323
  }
15324
 
15325
  /**
15326
+ * Returns the request serializer used by the protocol processor.
15327
+ *
15328
+ * @return RequestSerializerInterface
15329
  */
15330
+ public function getRequestSerializer()
15331
+ {
15332
+ return $this->serializer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15333
  }
15334
 
15335
  /**
15336
+ * Sets the response reader used by the protocol processor.
 
15337
  *
15338
+ * @param ResponseReaderInterface $reader Response reader.
 
15339
  */
15340
+ public function setResponseReader(ResponseReaderInterface $reader)
15341
  {
15342
+ $this->reader = $reader;
 
 
15343
  }
 
15344
 
 
 
 
 
 
 
 
 
 
15345
  /**
15346
+ * Returns the Response reader used by the protocol processor.
15347
+ *
15348
+ * @return ResponseReaderInterface
15349
  */
15350
+ public function getResponseReader()
15351
  {
15352
+ return $this->reader;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15353
  }
15354
  }
15355
 
15423
  return $multibulk;
15424
 
15425
  case ':':
15426
+ $integer = (int) $payload;
15427
+ return $integer == $payload ? $integer : $payload;
15428
 
15429
  case '-':
15430
  return new ErrorResponse($payload);
15456
  }
15457
 
15458
  /**
15459
+ * Request serializer for the standard Redis wire protocol.
 
15460
  *
15461
  * @link http://redis.io/topics/protocol
15462
  *
15463
  * @author Daniele Alessandri <suppakilla@gmail.com>
15464
  */
15465
+ class RequestSerializer implements RequestSerializerInterface
15466
  {
15467
+ /**
15468
+ * {@inheritdoc}
15469
  */
15470
+ public function serialize(CommandInterface $command)
15471
+ {
15472
+ $commandID = $command->getId();
15473
+ $arguments = $command->getArguments();
15474
 
15475
+ $cmdlen = strlen($commandID);
15476
+ $reqlen = count($arguments) + 1;
 
 
15477
 
15478
+ $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
15479
+
15480
+ foreach ($arguments as $argument) {
15481
+ $arglen = strlen($argument);
15482
+ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
15483
+ }
15484
+
15485
+ return $buffer;
 
 
15486
  }
15487
+ }
15488
+
15489
+ /**
15490
+ * Response reader for the standard Redis wire protocol.
15491
+ *
15492
+ * @link http://redis.io/topics/protocol
15493
+ *
15494
+ * @author Daniele Alessandri <suppakilla@gmail.com>
15495
+ */
15496
+ class ResponseReader implements ResponseReaderInterface
15497
+ {
15498
+ protected $handlers;
15499
 
15500
  /**
15501
+ *
15502
  */
15503
+ public function __construct()
15504
  {
15505
+ $this->handlers = $this->getDefaultHandlers();
15506
  }
15507
 
15508
  /**
15509
+ * Returns the default handlers for the supported type of responses.
15510
+ *
15511
+ * @return array
15512
  */
15513
+ protected function getDefaultHandlers()
15514
  {
15515
+ return array(
15516
+ '+' => new Handler\StatusResponse(),
15517
+ '-' => new Handler\ErrorResponse(),
15518
+ ':' => new Handler\IntegerResponse(),
15519
+ '$' => new Handler\BulkResponse(),
15520
+ '*' => new Handler\MultiBulkResponse(),
15521
+ );
15522
  }
15523
 
15524
  /**
15525
+ * Sets the handler for the specified prefix identifying the response type.
15526
  *
15527
+ * @param string $prefix Identifier of the type of response.
15528
+ * @param Handler\ResponseHandlerInterface $handler Response handler.
15529
  */
15530
+ public function setHandler($prefix, Handler\ResponseHandlerInterface $handler)
15531
  {
15532
+ $this->handlers[$prefix] = $handler;
15533
  }
15534
 
15535
  /**
15536
+ * Returns the response handler associated to a certain type of response.
15537
  *
15538
+ * @param string $prefix Identifier of the type of response.
15539
+ *
15540
+ * @return Handler\ResponseHandlerInterface
15541
  */
15542
+ public function getHandler($prefix)
15543
  {
15544
+ if (isset($this->handlers[$prefix])) {
15545
+ return $this->handlers[$prefix];
15546
+ }
15547
+
15548
+ return;
15549
  }
15550
 
15551
  /**
15552
+ * {@inheritdoc}
 
 
15553
  */
15554
+ public function read(CompositeConnectionInterface $connection)
15555
  {
15556
+ $header = $connection->readLine();
15557
+
15558
+ if ($header === '') {
15559
+ $this->onProtocolError($connection, 'Unexpected empty reponse header.');
15560
+ }
15561
+
15562
+ $prefix = $header[0];
15563
+
15564
+ if (!isset($this->handlers[$prefix])) {
15565
+ $this->onProtocolError($connection, "Unknown response prefix: '$prefix'.");
15566
+ }
15567
+
15568
+ $payload = $this->handlers[$prefix]->handle($connection, substr($header, 1));
15569
+
15570
+ return $payload;
15571
  }
15572
 
15573
  /**
15574
+ * Handles protocol errors generated while reading responses from a
15575
+ * connection.
15576
  *
15577
+ * @param CompositeConnectionInterface $connection Redis connection that generated the error.
15578
+ * @param string $message Error message.
15579
  */
15580
+ protected function onProtocolError(CompositeConnectionInterface $connection, $message)
15581
  {
15582
+ CommunicationException::handle(
15583
+ new ProtocolException($connection, $message)
15584
+ );
15585
  }
15586
  }
15587
 
15602
  */
15603
  abstract class AbstractConsumer implements \Iterator
15604
  {
15605
+ const SUBSCRIBE = 'subscribe';
15606
+ const UNSUBSCRIBE = 'unsubscribe';
15607
+ const PSUBSCRIBE = 'psubscribe';
15608
  const PUNSUBSCRIBE = 'punsubscribe';
15609
+ const MESSAGE = 'message';
15610
+ const PMESSAGE = 'pmessage';
15611
+ const PONG = 'pong';
15612
 
15613
+ const STATUS_VALID = 1; // 0b0001
15614
+ const STATUS_SUBSCRIBED = 2; // 0b0010
15615
+ const STATUS_PSUBSCRIBED = 4; // 0b0100
15616
 
15617
  private $position = null;
15618
  private $statusFlags = self::STATUS_VALID;
15741
  }
15742
 
15743
  /**
15744
+ * Returns the last message payload retrieved from the server and generated
15745
+ * by one of the active subscriptions.
15746
+ *
15747
+ * @return array
15748
+ */
15749
+ public function current()
15750
+ {
15751
+ return $this->getValue();
15752
+ }
15753
+
15754
+ /**
15755
+ * {@inheritdoc}
15756
+ */
15757
+ public function key()
15758
+ {
15759
+ return $this->position;
15760
+ }
15761
+
15762
+ /**
15763
+ * {@inheritdoc}
15764
+ */
15765
+ public function next()
15766
+ {
15767
+ if ($this->valid()) {
15768
+ ++$this->position;
15769
+ }
15770
+
15771
+ return $this->position;
15772
+ }
15773
+
15774
+ /**
15775
+ * Checks if the the consumer is still in a valid state to continue.
15776
+ *
15777
+ * @return bool
15778
+ */
15779
+ public function valid()
15780
+ {
15781
+ $isValid = $this->isFlagSet(self::STATUS_VALID);
15782
+ $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED;
15783
+ $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0;
15784
+
15785
+ return $isValid && $hasSubscriptions;
15786
+ }
15787
+
15788
+ /**
15789
+ * Resets the state of the consumer.
15790
+ */
15791
+ protected function invalidate()
15792
+ {
15793
+ $this->statusFlags = 0; // 0b0000;
15794
+ }
15795
+
15796
+ /**
15797
+ * Waits for a new message from the server generated by one of the active
15798
+ * subscriptions and returns it when available.
15799
+ *
15800
+ * @return array
15801
+ */
15802
+ abstract protected function getValue();
15803
+ }
15804
+
15805
+ /**
15806
+ * PUB/SUB consumer abstraction.
15807
+ *
15808
+ * @author Daniele Alessandri <suppakilla@gmail.com>
15809
+ */
15810
+ class Consumer extends AbstractConsumer
15811
+ {
15812
+ private $client;
15813
+ private $options;
15814
+
15815
+ /**
15816
+ * @param ClientInterface $client Client instance used by the consumer.
15817
+ * @param array $options Options for the consumer initialization.
15818
+ */
15819
+ public function __construct(ClientInterface $client, array $options = null)
15820
+ {
15821
+ $this->checkCapabilities($client);
15822
+
15823
+ $this->options = $options ?: array();
15824
+ $this->client = $client;
15825
+
15826
+ $this->genericSubscribeInit('subscribe');
15827
+ $this->genericSubscribeInit('psubscribe');
15828
+ }
15829
+
15830
+ /**
15831
+ * Returns the underlying client instance used by the pub/sub iterator.
15832
  *
15833
+ * @return ClientInterface
15834
  */
15835
+ public function getClient()
15836
  {
15837
+ return $this->client;
15838
  }
15839
 
15840
  /**
15841
+ * Checks if the client instance satisfies the required conditions needed to
15842
+ * initialize a PUB/SUB consumer.
15843
+ *
15844
+ * @param ClientInterface $client Client instance used by the consumer.
15845
+ *
15846
+ * @throws NotSupportedException
15847
  */
15848
+ private function checkCapabilities(ClientInterface $client)
15849
  {
15850
+ if ($client->getConnection() instanceof AggregateConnectionInterface) {
15851
+ throw new NotSupportedException(
15852
+ 'Cannot initialize a PUB/SUB consumer over aggregate connections.'
15853
+ );
15854
+ }
15855
+
15856
+ $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
15857
+
15858
+ if ($client->getProfile()->supportsCommands($commands) === false) {
15859
+ throw new NotSupportedException(
15860
+ 'The current profile does not support PUB/SUB related commands.'
15861
+ );
15862
+ }
15863
  }
15864
 
15865
  /**
15866
+ * This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE.
15867
+ *
15868
+ * @param string $subscribeAction Type of subscription.
15869
  */
15870
+ private function genericSubscribeInit($subscribeAction)
15871
  {
15872
+ if (isset($this->options[$subscribeAction])) {
15873
+ $this->$subscribeAction($this->options[$subscribeAction]);
15874
  }
 
 
15875
  }
15876
 
15877
  /**
15878
+ * {@inheritdoc}
 
 
15879
  */
15880
+ protected function writeRequest($method, $arguments)
15881
  {
15882
+ $this->client->getConnection()->writeRequest(
15883
+ $this->client->createCommand($method,
15884
+ Command::normalizeArguments($arguments)
15885
+ )
15886
+ );
15887
  }
15888
 
15889
  /**
15890
+ * {@inheritdoc}
15891
  */
15892
+ protected function disconnect()
15893
  {
15894
+ $this->client->disconnect();
15895
  }
15896
 
15897
  /**
15898
+ * {@inheritdoc}
 
 
 
15899
  */
15900
+ protected function getValue()
15901
+ {
15902
+ $response = $this->client->getConnection()->read();
15903
+
15904
+ switch ($response[0]) {
15905
+ case self::SUBSCRIBE:
15906
+ case self::UNSUBSCRIBE:
15907
+ case self::PSUBSCRIBE:
15908
+ case self::PUNSUBSCRIBE:
15909
+ if ($response[2] === 0) {
15910
+ $this->invalidate();
15911
+ }
15912
+ // The missing break here is intentional as we must process
15913
+ // subscriptions and unsubscriptions as standard messages.
15914
+ // no break
15915
+
15916
+ case self::MESSAGE:
15917
+ return (object) array(
15918
+ 'kind' => $response[0],
15919
+ 'channel' => $response[1],
15920
+ 'payload' => $response[2],
15921
+ );
15922
+
15923
+ case self::PMESSAGE:
15924
+ return (object) array(
15925
+ 'kind' => $response[0],
15926
+ 'pattern' => $response[1],
15927
+ 'channel' => $response[2],
15928
+ 'payload' => $response[3],
15929
+ );
15930
+
15931
+ case self::PONG:
15932
+ return (object) array(
15933
+ 'kind' => $response[0],
15934
+ 'payload' => $response[1],
15935
+ );
15936
+
15937
+ default:
15938
+ throw new ClientException(
15939
+ "Unknown message type '{$response[0]}' received in the PUB/SUB context."
15940
+ );
15941
+ }
15942
+ }
15943
  }
15944
 
15945
  /**
16022
  * Binds a callback to a channel.
16023
  *
16024
  * @param string $channel Channel name.
16025
+ * @param callable $callback A callback.
16026
  */
16027
  public function attachCallback($channel, $callback)
16028
  {
16100
  }
16101
  }
16102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16103
  /* --------------------------------------------------------------------------- */
16104
 
16105
+ namespace Predis\Replication;
16106
 
 
 
16107
  use Predis\ClientException;
 
16108
  use Predis\Command\CommandInterface;
 
 
16109
  use Predis\NotSupportedException;
16110
+ use Predis\CommunicationException;
 
 
 
16111
 
16112
  /**
16113
+ * Exception class that identifies when master is missing in a replication setup.
16114
  *
16115
  * @author Daniele Alessandri <suppakilla@gmail.com>
16116
  */
16117
+ class MissingMasterException extends ClientException
16118
  {
16119
+ }
 
 
 
 
16120
 
16121
+ /**
16122
+ * Defines a strategy for master/slave replication.
16123
+ *
16124
+ * @author Daniele Alessandri <suppakilla@gmail.com>
16125
+ */
16126
+ class ReplicationStrategy
16127
+ {
16128
+ protected $disallowed;
16129
+ protected $readonly;
16130
+ protected $readonlySHA1;
16131
 
16132
  /**
16133
  *
16134
  */
16135
  public function __construct()
16136
  {
16137
+ $this->disallowed = $this->getDisallowedOperations();
16138
+ $this->readonly = $this->getReadOnlyOperations();
16139
+ $this->readonlySHA1 = array();
16140
  }
16141
 
16142
  /**
16143
+ * Returns if the specified command will perform a read-only operation
16144
+ * on Redis or not.
16145
  *
16146
+ * @param CommandInterface $command Command instance.
16147
+ *
16148
+ * @throws NotSupportedException
16149
+ *
16150
+ * @return bool
16151
  */
16152
+ public function isReadOperation(CommandInterface $command)
16153
  {
16154
+ if (isset($this->disallowed[$id = $command->getId()])) {
16155
+ throw new NotSupportedException(
16156
+ "The command '$id' is not allowed in replication mode."
16157
+ );
16158
+ }
16159
+
16160
+ if (isset($this->readonly[$id])) {
16161
+ if (true === $readonly = $this->readonly[$id]) {
16162
+ return true;
16163
+ }
16164
+
16165
+ return call_user_func($readonly, $command);
16166
+ }
16167
+
16168
+ if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
16169
+ $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
16170
+
16171
+ if (isset($this->readonlySHA1[$sha1])) {
16172
+ if (true === $readonly = $this->readonlySHA1[$sha1]) {
16173
+ return true;
16174
+ }
16175
+
16176
+ return call_user_func($readonly, $command);
16177
+ }
16178
+ }
16179
+
16180
+ return false;
16181
  }
16182
 
16183
  /**
16184
+ * Returns if the specified command is not allowed for execution in a master
16185
+ * / slave replication context.
16186
  *
16187
+ * @param CommandInterface $command Command instance.
16188
+ *
16189
+ * @return bool
16190
  */
16191
+ public function isDisallowedOperation(CommandInterface $command)
16192
  {
16193
+ return isset($this->disallowed[$command->getId()]);
16194
  }
16195
 
16196
  /**
16197
+ * Checks if a SORT command is a readable operation by parsing the arguments
16198
+ * array of the specified commad instance.
16199
  *
16200
+ * @param CommandInterface $command Command instance.
16201
+ *
16202
+ * @return bool
16203
  */
16204
+ protected function isSortReadOnly(CommandInterface $command)
16205
  {
16206
+ $arguments = $command->getArguments();
16207
+ $argc = count($arguments);
16208
+
16209
+ if ($argc > 1) {
16210
+ for ($i = 1; $i < $argc; ++$i) {
16211
+ $argument = strtoupper($arguments[$i]);
16212
+ if ($argument === 'STORE') {
16213
+ return false;
16214
+ }
16215
+ }
16216
+ }
16217
+
16218
+ return true;
16219
  }
16220
 
16221
  /**
16222
+ * Checks if BITFIELD performs a read-only operation by looking for certain
16223
+ * SET and INCRYBY modifiers in the arguments array of the command.
16224
  *
16225
+ * @param CommandInterface $command Command instance.
16226
+ *
16227
+ * @return bool
16228
  */
16229
+ protected function isBitfieldReadOnly(CommandInterface $command)
16230
  {
16231
+ $arguments = $command->getArguments();
16232
+ $argc = count($arguments);
16233
+
16234
+ if ($argc >= 2) {
16235
+ for ($i = 1; $i < $argc; ++$i) {
16236
+ $argument = strtoupper($arguments[$i]);
16237
+ if ($argument === 'SET' || $argument === 'INCRBY') {
16238
+ return false;
16239
+ }
16240
+ }
16241
+ }
16242
+
16243
+ return true;
16244
  }
16245
 
16246
  /**
16247
+ * Checks if a GEORADIUS command is a readable operation by parsing the
16248
+ * arguments array of the specified commad instance.
16249
  *
16250
+ * @param CommandInterface $command Command instance.
16251
  *
16252
  * @return bool
16253
  */
16254
+ protected function isGeoradiusReadOnly(CommandInterface $command)
16255
  {
16256
+ $arguments = $command->getArguments();
16257
+ $argc = count($arguments);
16258
+ $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
16259
+
16260
+ if ($argc > $startIndex) {
16261
+ for ($i = $startIndex; $i < $argc; ++$i) {
16262
+ $argument = strtoupper($arguments[$i]);
16263
+ if ($argument === 'STORE' || $argument === 'STOREDIST') {
16264
+ return false;
16265
+ }
16266
+ }
16267
+ }
16268
+
16269
+ return true;
16270
  }
16271
 
16272
  /**
16273
+ * Marks a command as a read-only operation.
16274
+ *
16275
+ * When the behavior of a command can be decided only at runtime depending
16276
+ * on its arguments, a callable object can be provided to dynamically check
16277
+ * if the specified command performs a read or a write operation.
16278
+ *
16279
+ * @param string $commandID Command ID.
16280
+ * @param mixed $readonly A boolean value or a callable object.
16281
  */
16282
+ public function setCommandReadOnly($commandID, $readonly = true)
16283
  {
16284
+ $commandID = strtoupper($commandID);
16285
+
16286
+ if ($readonly) {
16287
+ $this->readonly[$commandID] = $readonly;
16288
+ } else {
16289
+ unset($this->readonly[$commandID]);
16290
+ }
16291
+ }
16292
+
16293
+ /**
16294
+ * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
16295
+ * the behaviour of a script can be decided only at runtime depending on
16296
+ * its arguments, a callable object can be provided to dynamically check
16297
+ * if the passed instance of EVAL or EVALSHA performs write operations or
16298
+ * not.
16299
+ *
16300
+ * @param string $script Body of the Lua script.
16301
+ * @param mixed $readonly A boolean value or a callable object.
16302
+ */
16303
+ public function setScriptReadOnly($script, $readonly = true)
16304
+ {
16305
+ $sha1 = sha1($script);
16306
+
16307
+ if ($readonly) {
16308
+ $this->readonlySHA1[$sha1] = $readonly;
16309
+ } else {
16310
+ unset($this->readonlySHA1[$sha1]);
16311
+ }
16312
+ }
16313
+
16314
+ /**
16315
+ * Returns the default list of disallowed commands.
16316
+ *
16317
+ * @return array
16318
+ */
16319
+ protected function getDisallowedOperations()
16320
+ {
16321
+ return array(
16322
+ 'SHUTDOWN' => true,
16323
+ 'INFO' => true,
16324
+ 'DBSIZE' => true,
16325
+ 'LASTSAVE' => true,
16326
+ 'CONFIG' => true,
16327
+ 'MONITOR' => true,
16328
+ 'SLAVEOF' => true,
16329
+ 'SAVE' => true,
16330
+ 'BGSAVE' => true,
16331
+ 'BGREWRITEAOF' => true,
16332
+ 'SLOWLOG' => true,
16333
+ );
16334
+ }
16335
+
16336
+ /**
16337
+ * Returns the default list of commands performing read-only operations.
16338
+ *
16339
+ * @return array
16340
+ */
16341
+ protected function getReadOnlyOperations()
16342
+ {
16343
+ return array(
16344
+ 'EXISTS' => true,
16345
+ 'TYPE' => true,
16346
+ 'KEYS' => true,
16347
+ 'SCAN' => true,
16348
+ 'RANDOMKEY' => true,
16349
+ 'TTL' => true,
16350
+ 'GET' => true,
16351
+ 'MGET' => true,
16352
+ 'SUBSTR' => true,
16353
+ 'STRLEN' => true,
16354
+ 'GETRANGE' => true,
16355
+ 'GETBIT' => true,
16356
+ 'LLEN' => true,
16357
+ 'LRANGE' => true,
16358
+ 'LINDEX' => true,
16359
+ 'SCARD' => true,
16360
+ 'SISMEMBER' => true,
16361
+ 'SINTER' => true,
16362
+ 'SUNION' => true,
16363
+ 'SDIFF' => true,
16364
+ 'SMEMBERS' => true,
16365
+ 'SSCAN' => true,
16366
+ 'SRANDMEMBER' => true,
16367
+ 'ZRANGE' => true,
16368
+ 'ZREVRANGE' => true,
16369
+ 'ZRANGEBYSCORE' => true,
16370
+ 'ZREVRANGEBYSCORE' => true,
16371
+ 'ZCARD' => true,
16372
+ 'ZSCORE' => true,
16373
+ 'ZCOUNT' => true,
16374
+ 'ZRANK' => true,
16375
+ 'ZREVRANK' => true,
16376
+ 'ZSCAN' => true,
16377
+ 'ZLEXCOUNT' => true,
16378
+ 'ZRANGEBYLEX' => true,
16379
+ 'ZREVRANGEBYLEX' => true,
16380
+ 'HGET' => true,
16381
+ 'HMGET' => true,
16382
+ 'HEXISTS' => true,
16383
+ 'HLEN' => true,
16384
+ 'HKEYS' => true,
16385
+ 'HVALS' => true,
16386
+ 'HGETALL' => true,
16387
+ 'HSCAN' => true,
16388
+ 'HSTRLEN' => true,
16389
+ 'PING' => true,
16390
+ 'AUTH' => true,
16391
+ 'SELECT' => true,
16392
+ 'ECHO' => true,
16393
+ 'QUIT' => true,
16394
+ 'OBJECT' => true,
16395
+ 'BITCOUNT' => true,
16396
+ 'BITPOS' => true,
16397
+ 'TIME' => true,
16398
+ 'PFCOUNT' => true,
16399
+ 'SORT' => array($this, 'isSortReadOnly'),
16400
+ 'BITFIELD' => array($this, 'isBitfieldReadOnly'),
16401
+ 'GEOHASH' => true,
16402
+ 'GEOPOS' => true,
16403
+ 'GEODIST' => true,
16404
+ 'GEORADIUS' => array($this, 'isGeoradiusReadOnly'),
16405
+ 'GEORADIUSBYMEMBER' => array($this, 'isGeoradiusReadOnly'),
16406
+ );
16407
  }
16408
+ }
16409
 
16410
+ /**
16411
+ * Exception class that identifies a role mismatch when connecting to node
16412
+ * managed by redis-sentinel.
16413
+ *
16414
+ * @author Daniele Alessandri <suppakilla@gmail.com>
16415
+ */
16416
+ class RoleException extends CommunicationException
16417
+ {
16418
+ }
16419
 
16420
+ /* --------------------------------------------------------------------------- */
 
 
 
 
 
 
 
 
16421
 
16422
+ namespace Predis\Transaction;
 
 
 
 
 
 
 
 
16423
 
16424
+ use Predis\PredisException;
16425
+ use Predis\ClientContextInterface;
16426
+ use Predis\ClientException;
16427
+ use Predis\ClientInterface;
16428
+ use Predis\Command\CommandInterface;
16429
+ use Predis\CommunicationException;
16430
+ use Predis\Connection\AggregateConnectionInterface;
16431
+ use Predis\NotSupportedException;
16432
+ use Predis\Protocol\ProtocolException;
16433
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
16434
+ use Predis\Response\ServerException;
16435
+ use Predis\Response\Status as StatusResponse;
16436
 
16437
+ /**
16438
+ * Exception class that identifies a MULTI / EXEC transaction aborted by Redis.
16439
+ *
16440
+ * @author Daniele Alessandri <suppakilla@gmail.com>
16441
+ */
16442
+ class AbortedMultiExecException extends PredisException
16443
+ {
16444
+ private $transaction;
 
16445
 
16446
  /**
16447
+ * @param MultiExec $transaction Transaction that generated the exception.
16448
+ * @param string $message Error message.
16449
+ * @param int $code Error code.
16450
  */
16451
+ public function __construct(MultiExec $transaction, $message, $code = null)
16452
  {
16453
+ parent::__construct($message, $code);
16454
+ $this->transaction = $transaction;
16455
  }
16456
 
16457
  /**
16458
+ * Returns the transaction that generated the exception.
16459
  *
16460
+ * @return MultiExec
16461
  */
16462
+ public function getTransaction()
16463
  {
16464
+ return $this->transaction;
16465
  }
16466
  }
16467
 
16903
  }
16904
 
16905
  /**
16906
+ * Utility class used to track the state of a MULTI / EXEC transaction.
16907
  *
16908
  * @author Daniele Alessandri <suppakilla@gmail.com>
16909
  */
16910
+ class MultiExecState
16911
  {
16912
+ const INITIALIZED = 1; // 0b00001
16913
+ const INSIDEBLOCK = 2; // 0b00010
16914
+ const DISCARDED = 4; // 0b00100
16915
+ const CAS = 8; // 0b01000
16916
+ const WATCH = 16; // 0b10000
16917
+
16918
+ private $flags;
16919
 
16920
  /**
16921
+ *
 
 
16922
  */
16923
+ public function __construct()
16924
  {
16925
+ $this->flags = 0;
 
16926
  }
16927
 
16928
  /**
16929
+ * Sets the internal state flags.
16930
  *
16931
+ * @param int $flags Set of flags
16932
  */
16933
+ public function set($flags)
16934
  {
16935
+ $this->flags = $flags;
16936
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16937
 
16938
  /**
16939
+ * Gets the internal state flags.
16940
+ *
16941
+ * @return int
16942
  */
16943
+ public function get()
16944
  {
16945
+ return $this->flags;
 
 
 
 
 
 
16946
  }
16947
 
16948
  /**
16949
+ * Sets one or more flags.
16950
+ *
16951
+ * @param int $flags Set of flags
16952
  */
16953
+ public function flag($flags)
16954
  {
16955
+ $this->flags |= $flags;
 
 
 
 
 
 
 
 
 
 
 
16956
  }
16957
 
16958
  /**
16959
+ * Resets one or more flags.
16960
+ *
16961
+ * @param int $flags Set of flags
16962
  */
16963
+ public function unflag($flags)
16964
  {
16965
+ $this->flags &= ~$flags;
 
16966
  }
16967
 
16968
  /**
16969
+ * Returns if the specified flag or set of flags is set.
16970
+ *
16971
+ * @param int $flags Flag
16972
+ *
16973
+ * @return bool
16974
  */
16975
+ public function check($flags)
16976
  {
16977
+ return ($this->flags & $flags) === $flags;
 
16978
  }
16979
 
16980
  /**
16981
+ * Resets the state of a transaction.
16982
  */
16983
+ public function reset()
16984
  {
16985
+ $this->flags = 0;
 
16986
  }
16987
 
16988
  /**
16989
+ * Returns the state of the RESET flag.
16990
+ *
16991
+ * @return bool
16992
  */
16993
+ public function isReset()
16994
  {
16995
+ return $this->flags === 0;
 
 
 
 
16996
  }
16997
+
16998
  /**
16999
+ * Returns the state of the INITIALIZED flag.
17000
+ *
17001
+ * @return bool
17002
  */
17003
+ public function isInitialized()
17004
  {
17005
+ return $this->check(self::INITIALIZED);
17006
+ }
17007
 
17008
+ /**
17009
+ * Returns the state of the INSIDEBLOCK flag.
17010
+ *
17011
+ * @return bool
17012
+ */
17013
+ public function isExecuting()
17014
+ {
17015
+ return $this->check(self::INSIDEBLOCK);
17016
  }
17017
 
17018
  /**
17019
+ * Returns the state of the CAS flag.
17020
+ *
17021
+ * @return bool
17022
  */
17023
+ public function isCAS()
17024
  {
17025
+ return $this->check(self::CAS);
17026
+ }
17027
 
17028
+ /**
17029
+ * Returns if WATCH is allowed in the current state.
17030
+ *
17031
+ * @return bool
17032
+ */
17033
+ public function isWatchAllowed()
17034
+ {
17035
+ return $this->check(self::INITIALIZED) && !$this->check(self::CAS);
17036
  }
17037
 
17038
  /**
17039
+ * Returns the state of the WATCH flag.
17040
  *
17041
+ * @return bool
17042
  */
17043
+ public function isWatching()
17044
  {
17045
+ return $this->check(self::WATCH);
17046
  }
17047
 
17048
  /**
17049
+ * Returns the state of the DISCARDED flag.
17050
  *
17051
+ * @return bool
17052
  */
17053
+ public function isDiscarded()
17054
  {
17055
+ return $this->check(self::DISCARDED);
17056
  }
17057
  }
17058
 
17148
  /**
17149
  * Returns the last message payload retrieved from the server.
17150
  *
17151
+ * @return object
17152
  */
17153
  public function current()
17154
  {
17185
  * Waits for a new message from the server generated by MONITOR and returns
17186
  * it when available.
17187
  *
17188
+ * @return object
17189
  */
17190
  private function getValue()
17191
  {
17223
 
17224
  /* --------------------------------------------------------------------------- */
17225
 
17226
+ namespace Predis\Session;
17227
 
17228
+ use Predis\ClientInterface;
 
17229
 
17230
  /**
17231
+ * Session handler class that relies on Predis\Client to store PHP's sessions
17232
+ * data into one or multiple Redis servers.
17233
+ *
17234
+ * This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3
17235
+ * provided that a polyfill for `SessionHandlerInterface` is defined by either
17236
+ * you or an external package such as `symfony/http-foundation`.
17237
  *
17238
  * @author Daniele Alessandri <suppakilla@gmail.com>
17239
  */
17240
+ class Handler implements \SessionHandlerInterface
17241
  {
17242
+ protected $client;
17243
+ protected $ttl;
 
17244
 
17245
  /**
17246
+ * @param ClientInterface $client Fully initialized client instance.
17247
+ * @param array $options Session handler options.
17248
  */
17249
+ public function __construct(ClientInterface $client, array $options = array())
17250
  {
17251
+ $this->client = $client;
17252
+
17253
+ if (isset($options['gc_maxlifetime'])) {
17254
+ $this->ttl = (int) $options['gc_maxlifetime'];
17255
+ } else {
17256
+ $this->ttl = ini_get('session.gc_maxlifetime');
17257
+ }
17258
  }
17259
 
17260
  /**
17261
+ * Registers this instance as the current session handler.
 
 
 
 
 
 
 
17262
  */
17263
+ public function register()
17264
  {
17265
+ if (PHP_VERSION_ID >= 50400) {
17266
+ session_set_save_handler($this, true);
17267
+ } else {
17268
+ session_set_save_handler(
17269
+ array($this, 'open'),
17270
+ array($this, 'close'),
17271
+ array($this, 'read'),
17272
+ array($this, 'write'),
17273
+ array($this, 'destroy'),
17274
+ array($this, 'gc')
17275
  );
17276
  }
17277
+ }
17278
 
17279
+ /**
17280
+ * {@inheritdoc}
17281
+ */
17282
+ public function open($save_path, $session_id)
17283
+ {
17284
+ // NOOP
17285
+ return true;
17286
+ }
 
 
 
 
 
 
 
 
 
 
 
17287
 
17288
+ /**
17289
+ * {@inheritdoc}
17290
+ */
17291
+ public function close()
17292
+ {
17293
+ // NOOP
17294
+ return true;
17295
  }
17296
 
17297
  /**
17298
+ * {@inheritdoc}
 
 
 
 
 
17299
  */
17300
+ public function gc($maxlifetime)
17301
  {
17302
+ // NOOP
17303
+ return true;
17304
  }
17305
 
17306
  /**
17307
+ * {@inheritdoc}
 
 
 
 
 
17308
  */
17309
+ public function read($session_id)
17310
  {
17311
+ if ($data = $this->client->get($session_id)) {
17312
+ return $data;
17313
+ }
17314
 
17315
+ return '';
17316
  }
 
17317
  /**
17318
+ * {@inheritdoc}
 
 
 
 
 
 
 
17319
  */
17320
+ public function write($session_id, $session_data)
17321
  {
17322
+ $this->client->setex($session_id, $this->ttl, $session_data);
17323
 
17324
+ return true;
 
 
 
 
17325
  }
17326
 
17327
  /**
17328
+ * {@inheritdoc}
 
 
 
 
 
 
 
17329
  */
17330
+ public function destroy($session_id)
17331
  {
17332
+ $this->client->del($session_id);
17333
 
17334
+ return true;
 
 
 
 
17335
  }
17336
 
17337
  /**
17338
+ * Returns the underlying client instance.
17339
  *
17340
+ * @return ClientInterface
17341
  */
17342
+ public function getClient()
17343
  {
17344
+ return $this->client;
 
 
 
 
 
 
 
 
 
 
 
 
17345
  }
17346
 
17347
  /**
17348
+ * Returns the session max lifetime value.
17349
  *
17350
+ * @return int
17351
  */
17352
+ public function getMaxLifeTime()
17353
  {
17354
+ return $this->ttl;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17355
  }
17356
  }
17357
 
languages/redis-cache.pot CHANGED
@@ -2,9 +2,9 @@
2
  # This file is distributed under the same license as the Redis Object Cache package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Redis Object Cache 1.3\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/redis-cache\n"
7
- "POT-Creation-Date: 2016-04-29 01:44:38+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
@@ -12,7 +12,7 @@ msgstr ""
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
- #. #-#-#-#-# plugin.pot (Redis Object Cache 1.3) #-#-#-#-#
16
  #. Plugin Name of the plugin/theme
17
  #: includes/admin-page.php:6 redis-cache.php:50
18
  msgid "Redis Object Cache"
@@ -135,7 +135,7 @@ msgid "Drop-in could not be updated."
135
  msgstr ""
136
 
137
  #. Plugin URI of the plugin/theme
138
- msgid "http://wordpress.org/plugins/redis-cache/"
139
  msgstr ""
140
 
141
  #. Description of the plugin/theme
@@ -149,5 +149,5 @@ msgid "Till Krüss"
149
  msgstr ""
150
 
151
  #. Author URI of the plugin/theme
152
- msgid "http://till.kruss.me/"
153
  msgstr ""
2
  # This file is distributed under the same license as the Redis Object Cache package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Redis Object Cache 1.3.3\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/redis-cache\n"
7
+ "POT-Creation-Date: 2016-08-22 17:35:09+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
 
15
+ #. #-#-#-#-# plugin.pot (Redis Object Cache 1.3.3) #-#-#-#-#
16
  #. Plugin Name of the plugin/theme
17
  #: includes/admin-page.php:6 redis-cache.php:50
18
  msgid "Redis Object Cache"
135
  msgstr ""
136
 
137
  #. Plugin URI of the plugin/theme
138
+ msgid "https://wordpress.org/plugins/redis-cache/"
139
  msgstr ""
140
 
141
  #. Description of the plugin/theme
149
  msgstr ""
150
 
151
  #. Author URI of the plugin/theme
152
+ msgid "https://till.im/"
153
  msgstr ""
readme.txt CHANGED
@@ -4,16 +4,16 @@ Donate link: https://www.paypal.me/tillkruss
4
  Tags: redis, predis, hhvm, pecl, caching, cache, object cache, wp object cache, server, performance, optimize, speed, load, replication, clustering
5
  Requires at least: 3.3
6
  Tested up to: 4.6
7
- Stable tag: 1.3.2
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. Supports Predis, PhpRedis, HHVM, replication and clustering.
12
 
13
 
14
  == Description ==
15
 
16
- A persistent object cache backend powered by Redis. Supports [Predis](https://github.com/nrk/predis/), [HHVM's Redis extension](https://github.com/facebook/hhvm/tree/master/hphp/system/php/redis) and the [PECL Redis Extension](https://github.com/phpredis/phpredis).
17
 
18
  To adjust the connection parameters, prefix cache keys or configure replication/clustering, please see [Other Notes](http://wordpress.org/extend/plugins/redis-cache/other_notes/).
19
 
@@ -79,12 +79,22 @@ To adjust the configuration, define any of the following constants in your `wp-c
79
 
80
  Set maximum time-to-live (in seconds) for cache keys with an expiration time of `0`.
81
 
 
 
 
 
 
 
 
 
82
 
83
  == Replication & Clustering ==
84
 
85
  To use Replication and Clustering, make sure your server is running PHP7, your setup is using Predis to connect to Redis and you consulted the [Predis documentation](https://github.com/nrk/predis).
86
 
87
- For replication, use the `WP_REDIS_SERVERS` constant and for clustering the `WP_REDIS_CLUSTER` constant. You can use a named array or an URI string to specify the parameters.
 
 
88
 
89
  __Master-Slave Replication Example:__
90
 
@@ -104,11 +114,26 @@ __Clustering via Client-side Sharding Example:__
104
 
105
  == Screenshots ==
106
 
107
- 1. Plugin settings page.
 
 
108
 
109
 
110
  == Changelog ==
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  = 1.3.2 =
113
 
114
  * Make sure `$result` is not `false` in `WP_Object_Cache::get()`
@@ -186,6 +211,10 @@ __Clustering via Client-side Sharding Example:__
186
 
187
  == Upgrade Notice ==
188
 
 
 
 
 
189
  = 1.3.2 =
190
 
191
  This update includes a critical fix for PhpRedis.
4
  Tags: redis, predis, hhvm, pecl, caching, cache, object cache, wp object cache, server, performance, optimize, speed, load, replication, clustering
5
  Requires at least: 3.3
6
  Tested up to: 4.6
7
+ Stable tag: 1.3.3
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. Supports Predis, PhpRedis (PECL), HHVM, replication and clustering.
12
 
13
 
14
  == Description ==
15
 
16
+ A persistent object cache backend powered by Redis. Supports [Predis](https://github.com/nrk/predis/), [PhpRedis (PECL)](https://github.com/phpredis/phpredis), [HHVM](https://github.com/facebook/hhvm/tree/master/hphp/system/php/redis), replication and clustering.
17
 
18
  To adjust the connection parameters, prefix cache keys or configure replication/clustering, please see [Other Notes](http://wordpress.org/extend/plugins/redis-cache/other_notes/).
19
 
79
 
80
  Set maximum time-to-live (in seconds) for cache keys with an expiration time of `0`.
81
 
82
+ * `WP_REDIS_GLOBAL_GROUPS` (default: `['users', 'userlogins', 'usermeta', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss']`)
83
+
84
+ Set the list of network-wide cache groups that should not be prefixed with the blog-id _(Multisite only)_.
85
+
86
+ * `WP_REDIS_IGNORED_GROUPS` (default: `['comment', 'counts']`)
87
+
88
+ Set the cache groups that should not be cached in Redis.
89
+
90
 
91
  == Replication & Clustering ==
92
 
93
  To use Replication and Clustering, make sure your server is running PHP7, your setup is using Predis to connect to Redis and you consulted the [Predis documentation](https://github.com/nrk/predis).
94
 
95
+ For replication use the `WP_REDIS_SERVERS` constant and for clustering the `WP_REDIS_CLUSTER` constant. You can use a named array or an URI string to specify the parameters.
96
+
97
+ For authentication use the `WP_REDIS_PASSWORD` constant.
98
 
99
  __Master-Slave Replication Example:__
100
 
114
 
115
  == Screenshots ==
116
 
117
+ 1. Plugin settings, connected to a single Redis server.
118
+
119
+ 2. Plugin settings, not connected to a Redis cluster.
120
 
121
 
122
  == Changelog ==
123
 
124
+ = 1.3.3 =
125
+
126
+ * Updated Predis to `v1.1.1`
127
+ * Added `redis_instance()` method
128
+ * Added `incr()` method alias for Batcache compatibility
129
+ * Added `WP_REDIS_GLOBAL_GROUPS` and `WP_REDIS_IGNORED_GROUPS` constant
130
+ * Added `redis_object_cache_delete` action
131
+ * Use `WP_PLUGIN_DIR` with `WP_CONTENT_DIR` as fallback
132
+ * Set password when using a cluster or replication
133
+ * Show Redis client in `stats()`
134
+ * Change visibility of `$cache` to public
135
+ * Use old array syntax, just in case
136
+
137
  = 1.3.2 =
138
 
139
  * Make sure `$result` is not `false` in `WP_Object_Cache::get()`
211
 
212
  == Upgrade Notice ==
213
 
214
+ = 1.3.3 =
215
+
216
+ This update contains several improvements.
217
+
218
  = 1.3.2 =
219
 
220
  This update includes a critical fix for PhpRedis.
redis-cache.php CHANGED
@@ -1,9 +1,9 @@
1
  <?php
2
  /*
3
  Plugin Name: Redis Object Cache
4
- Plugin URI: http://wordpress.org/plugins/redis-cache/
5
  Description: A persistent object cache backend powered by Redis. Supports Predis, PhpRedis, HHVM, replication and clustering.
6
- Version: 1.3.2
7
  Text Domain: redis-cache
8
  Domain Path: /languages
9
  Author: Till Krüss
1
  <?php
2
  /*
3
  Plugin Name: Redis Object Cache
4
+ Plugin URI: https://wordpress.org/plugins/redis-cache/
5
  Description: A persistent object cache backend powered by Redis. Supports Predis, PhpRedis, HHVM, replication and clustering.
6
+ Version: 1.3.3
7
  Text Domain: redis-cache
8
  Domain Path: /languages
9
  Author: Till Krüss