Lib_Credis - Version 1.9.2.0

Version Notes

1.9.2.0

Download this release

Release Info

Developer Magento Core Team
Extension Lib_Credis
Version 1.9.2.0
Comparing to
See all releases


Code changes from version 1.8.0.0 to 1.9.2.0

Files changed (2) hide show
  1. lib/Credis/Client.php +412 -55
  2. package.xml +5 -5
lib/Credis/Client.php CHANGED
@@ -25,7 +25,20 @@ if( ! defined('CRLF')) define('CRLF', sprintf('%s%s', chr(13), chr(10)));
25
  /**
26
  * Credis-specific errors, wraps native Redis errors
27
  */
28
- class CredisException extends Exception {
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  /**
@@ -126,12 +139,13 @@ class CredisException extends Exception {
126
  * TODO
127
  *
128
  * Pub/Sub
129
- * TODO
 
130
  *
131
  * Scripting:
132
  * @method string|int script(string $command, string $arg1 = null)
133
- * @method string|int|array|bool eval(string $script, int $numkeys, string $key = null, string $arg = null)
134
- * @method string|int|array|bool evalSha(string $sha1, int $numkeys, string $key = null, string $arg = null)
135
  */
136
  class Credis_Client {
137
 
@@ -155,7 +169,7 @@ class Credis_Client {
155
  * @var string
156
  */
157
  protected $host;
158
-
159
  /**
160
  * Port on which the Redis server is running
161
  * @var integer
@@ -244,7 +258,23 @@ class Credis_Client {
244
  * Aliases for backwards compatibility with phpredis
245
  * @var array
246
  */
247
- protected $aliasedMethods = array('delete' => 'del', 'getkeys' => 'keys', 'sremove' => 'srem');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
  /**
250
  * Creates a Redisent connection to the Redis server on host {@link $host} and port {@link $port}.
@@ -254,14 +284,19 @@ class Credis_Client {
254
  * @param integer $port The port number of the Redis server
255
  * @param float $timeout Timeout period in seconds
256
  * @param string $persistent Flag to establish persistent connection
 
 
257
  */
258
- public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '')
259
  {
260
  $this->host = (string) $host;
261
  $this->port = (int) $port;
262
  $this->timeout = $timeout;
263
  $this->persistent = (string) $persistent;
264
  $this->standalone = ! extension_loaded('redis');
 
 
 
265
  }
266
 
267
  public function __destruct()
@@ -270,7 +305,47 @@ class Credis_Client {
270
  $this->close();
271
  }
272
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  /**
275
  * @throws CredisException
276
  * @return Credis_Client
@@ -303,24 +378,16 @@ class Credis_Client {
303
  $this->closeOnDestruct = $flag;
304
  return $this;
305
  }
306
-
307
- /**
308
- * @throws CredisException
309
- * @return Credis_Client
310
- */
311
- public function connect()
312
  {
313
- if ($this->connected) {
314
- return $this;
315
- }
316
  if (preg_match('#^(tcp|unix)://(.*)$#', $this->host, $matches)) {
317
  if($matches[1] == 'tcp') {
318
- if ( ! preg_match('#^(.*)(?::(\d+))?(?:/(.*))?$#', $matches[2], $matches)) {
319
- throw new CredisException('Invalid host format; expected tcp://host[:port][/persistent]');
320
  }
321
  $this->host = $matches[1];
322
- $this->port = (int) (isset($matches[2]) ? $matches[2] : 6379);
323
- $this->persistent = isset($matches[3]) ? $matches[3] : '';
324
  } else {
325
  $this->host = $matches[2];
326
  $this->port = NULL;
@@ -332,6 +399,16 @@ class Credis_Client {
332
  if ($this->port !== NULL && substr($this->host,0,1) == '/') {
333
  $this->port = NULL;
334
  }
 
 
 
 
 
 
 
 
 
 
335
  if ($this->standalone) {
336
  $flags = STREAM_CLIENT_CONNECT;
337
  $remote_socket = $this->port === NULL
@@ -363,7 +440,7 @@ class Credis_Client {
363
  }
364
  $failures = $this->connectFailures;
365
  $this->connectFailures = 0;
366
- throw new CredisException("Connection to Redis failed after $failures failures.");
367
  }
368
 
369
  $this->connectFailures = 0;
@@ -374,28 +451,44 @@ class Credis_Client {
374
  $this->setReadTimeout($this->readTimeout);
375
  }
376
 
 
 
 
 
 
 
377
  return $this;
378
  }
379
-
380
  /**
381
- * Set the read timeout for the connection. If falsey, a timeout will not be set. Negative values not supported.
 
 
 
 
 
 
 
 
382
  *
383
- * @param $timeout
384
  * @throws CredisException
385
  * @return Credis_Client
386
  */
387
  public function setReadTimeout($timeout)
388
  {
389
- if ($timeout < 0) {
390
- throw new CredisException('Negative read timeout values are not supported.');
391
  }
392
  $this->readTimeout = $timeout;
393
  if ($this->connected) {
394
  if ($this->standalone) {
 
 
395
  stream_set_timeout($this->redis, (int) floor($timeout), ($timeout - floor($timeout)) * 1000000);
396
  } else if (defined('Redis::OPT_READ_TIMEOUT')) {
397
- // Not supported at time of writing, but hopefully this pull request will someday be merged:
398
- // https://github.com/nicolasff/phpredis/pull/260
 
399
  $this->redis->setOption(Redis::OPT_READ_TIMEOUT, $timeout);
400
  }
401
  }
@@ -419,14 +512,81 @@ class Credis_Client {
419
  return $result;
420
  }
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  /**
423
  * @param string $password
424
  * @return bool
425
  */
426
  public function auth($password)
427
  {
 
428
  $this->authPassword = $password;
429
- $response = $this->__call('auth', array($this->authPassword));
430
  return $response;
431
  }
432
 
@@ -436,10 +596,128 @@ class Credis_Client {
436
  */
437
  public function select($index)
438
  {
 
439
  $this->selectedDb = (int) $index;
440
- $response = $this->__call('select', array($this->selectedDb));
441
  return $response;
442
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
  public function __call($name, $args)
445
  {
@@ -451,6 +729,15 @@ class Credis_Client {
451
  // Send request via native PHP
452
  if($this->standalone)
453
  {
 
 
 
 
 
 
 
 
 
454
  // Flatten arguments
455
  $argsFlat = NULL;
456
  foreach($args as $index => $arg) {
@@ -459,12 +746,12 @@ class Credis_Client {
459
  $argsFlat = array_slice($args, 0, $index);
460
  }
461
  if($name == 'mset' || $name == 'msetnx' || $name == 'hmset') {
462
- foreach($arg as $key => $value) {
463
- $argsFlat[] = $key;
464
- $argsFlat[] = $value;
465
- }
466
  } else {
467
- $argsFlat = array_merge($argsFlat, $arg);
468
  }
469
  } else if($argsFlat !== NULL) {
470
  $argsFlat[] = $arg;
@@ -484,7 +771,7 @@ class Credis_Client {
484
  else if($name == 'exec') {
485
  if($this->isMulti) {
486
  $this->commandNames[] = $name;
487
- $this->commands .= self::_prepare_command(array($name));
488
  }
489
 
490
  // Write request
@@ -510,7 +797,7 @@ class Credis_Client {
510
  if($name == 'multi') {
511
  $this->isMulti = TRUE;
512
  }
513
- array_unshift($args, $name);
514
  $this->commandNames[] = $name;
515
  $this->commands .= self::_prepare_command($args);
516
  return $this;
@@ -532,7 +819,7 @@ class Credis_Client {
532
  }
533
 
534
  // Non-pipeline mode
535
- array_unshift($args, $name);
536
  $command = self::_prepare_command($args);
537
  $this->write_command($command);
538
  $response = $this->read_reply($name);
@@ -576,6 +863,27 @@ class Credis_Client {
576
  case 'lrem':
577
  $args = array($args[0], $args[2], $args[1]);
578
  break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  default:
580
  // Flatten arguments
581
  $argsFlat = NULL;
@@ -614,8 +922,8 @@ class Credis_Client {
614
  }
615
 
616
  // Use aliases to be compatible with phpredis wrapper
617
- if(isset($this->aliasedMethods[$name])) {
618
- $name = $this->aliasedMethods[$name];
619
  }
620
 
621
  // Multi and pipeline return self for chaining
@@ -624,11 +932,28 @@ class Credis_Client {
624
  return $this;
625
  }
626
 
627
- $response = call_user_func_array(array($this->redis, $name), $args);
 
 
 
 
 
 
 
 
 
 
 
 
628
  }
629
- // Wrap exceptions
630
  catch(RedisException $e) {
631
- throw new CredisException($e->getMessage(), $e->getCode());
 
 
 
 
 
632
  }
633
 
634
  #echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n";
@@ -639,17 +964,38 @@ class Credis_Client {
639
  case 'hmget':
640
  $response = array_values($response);
641
  break;
 
642
  case 'type':
643
  $typeMap = array(
644
- self::TYPE_NONE,
645
- self::TYPE_STRING,
646
- self::TYPE_SET,
647
- self::TYPE_LIST,
648
- self::TYPE_ZSET,
649
- self::TYPE_HASH,
650
  );
651
  $response = $typeMap[$response];
652
  break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  }
654
  }
655
 
@@ -681,6 +1027,7 @@ class Credis_Client {
681
  for ($written = 0; $written < $commandLen; $written += $fwrite) {
682
  $fwrite = fwrite($this->redis, substr($command, $written));
683
  if ($fwrite === FALSE || $fwrite == 0 ) {
 
684
  throw new CredisException('Failed to write entire command to stream');
685
  }
686
  }
@@ -690,7 +1037,13 @@ class Credis_Client {
690
  {
691
  $reply = fgets($this->redis);
692
  if($reply === FALSE) {
693
- throw new CredisException('Lost connection to Redis server.');
 
 
 
 
 
 
694
  }
695
  $reply = rtrim($reply, CRLF);
696
  #echo "> $name: $reply\n";
@@ -700,15 +1053,17 @@ class Credis_Client {
700
  case '-':
701
  if($this->isMulti || $this->usePipeline) {
702
  $response = FALSE;
 
 
703
  } else {
704
- throw new CredisException(substr($reply, 4));
705
  }
706
  break;
707
  /* Inline reply */
708
  case '+':
709
  $response = substr($reply, 1);
710
  if($response == 'OK' || $response == 'QUEUED') {
711
- return TRUE;
712
  }
713
  break;
714
  /* Bulk reply */
@@ -716,8 +1071,10 @@ class Credis_Client {
716
  if ($reply == '$-1') return FALSE;
717
  $size = (int) substr($reply, 1);
718
  $response = stream_get_contents($this->redis, $size + 2);
719
- if( ! $response)
 
720
  throw new CredisException('Error reading reply.');
 
721
  $response = substr($response, 0, $size);
722
  break;
723
  /* Multi-bulk reply */
@@ -727,7 +1084,7 @@ class Credis_Client {
727
 
728
  $response = array();
729
  for ($i = 0; $i < $count; $i++) {
730
- $response[] = $this->read_reply();
731
  }
732
  break;
733
  /* Integer reply */
@@ -758,7 +1115,7 @@ class Credis_Client {
758
  $response = array();
759
  foreach($lines as $line) {
760
  if ( ! $line || substr($line, 0, 1) == '#') {
761
- continue;
762
  }
763
  list($key, $value) = explode(':', $line, 2);
764
  $response[$key] = $value;
25
  /**
26
  * Credis-specific errors, wraps native Redis errors
27
  */
28
+ class CredisException extends Exception
29
+ {
30
+
31
+ const CODE_TIMED_OUT = 1;
32
+ const CODE_DISCONNECTED = 2;
33
+
34
+ public function __construct($message, $code = 0, $exception = NULL)
35
+ {
36
+ if ($exception && get_class($exception) == 'RedisException' && $message == 'read error on connection') {
37
+ $code = CredisException::CODE_DISCONNECTED;
38
+ }
39
+ parent::__construct($message, $code, $exception);
40
+ }
41
+
42
  }
43
 
44
  /**
139
  * TODO
140
  *
141
  * Pub/Sub
142
+ * @method int publish(string $channel, string $message)
143
+ * @method int|array pubsub(string $subCommand, $arg = NULL)
144
  *
145
  * Scripting:
146
  * @method string|int script(string $command, string $arg1 = null)
147
+ * @method string|int|array|bool eval(string $script, array $keys = NULL, array $args = NULL)
148
+ * @method string|int|array|bool evalSha(string $script, array $keys = NULL, array $args = NULL)
149
  */
150
  class Credis_Client {
151
 
169
  * @var string
170
  */
171
  protected $host;
172
+
173
  /**
174
  * Port on which the Redis server is running
175
  * @var integer
258
  * Aliases for backwards compatibility with phpredis
259
  * @var array
260
  */
261
+ protected $wrapperMethods = array('delete' => 'del', 'getkeys' => 'keys', 'sremove' => 'srem');
262
+
263
+ /**
264
+ * @var array
265
+ */
266
+ protected $renamedCommands;
267
+
268
+ /**
269
+ * @var int
270
+ */
271
+ protected $requests = 0;
272
+
273
+ /**
274
+ * @var bool
275
+ */
276
+ protected $subscribed = false;
277
+
278
 
279
  /**
280
  * Creates a Redisent connection to the Redis server on host {@link $host} and port {@link $port}.
284
  * @param integer $port The port number of the Redis server
285
  * @param float $timeout Timeout period in seconds
286
  * @param string $persistent Flag to establish persistent connection
287
+ * @param int $db The selected datbase of the Redis server
288
+ * @param string $password The authentication password of the Redis server
289
  */
290
+ public function __construct($host = '127.0.0.1', $port = 6379, $timeout = null, $persistent = '', $db = 0, $password = null)
291
  {
292
  $this->host = (string) $host;
293
  $this->port = (int) $port;
294
  $this->timeout = $timeout;
295
  $this->persistent = (string) $persistent;
296
  $this->standalone = ! extension_loaded('redis');
297
+ $this->authPassword = $password;
298
+ $this->selectedDb = (int)$db;
299
+ $this->convertHost();
300
  }
301
 
302
  public function __destruct()
305
  $this->close();
306
  }
307
  }
308
+
309
+ /**
310
+ * @return bool
311
+ */
312
+ public function isSubscribed()
313
+ {
314
+ return $this->subscribed;
315
+ }
316
+
317
+ /**
318
+ * Return the host of the Redis instance
319
+ * @return string
320
+ */
321
+ public function getHost()
322
+ {
323
+ return $this->host;
324
+ }
325
+ /**
326
+ * Return the port of the Redis instance
327
+ * @return int
328
+ */
329
+ public function getPort()
330
+ {
331
+ return $this->port;
332
+ }
333
 
334
+ /**
335
+ * Return the selected database
336
+ * @return int
337
+ */
338
+ public function getSelectedDb()
339
+ {
340
+ return $this->selectedDb;
341
+ }
342
+ /**
343
+ * @return string
344
+ */
345
+ public function getPersistence()
346
+ {
347
+ return $this->persistent;
348
+ }
349
  /**
350
  * @throws CredisException
351
  * @return Credis_Client
378
  $this->closeOnDestruct = $flag;
379
  return $this;
380
  }
381
+ protected function convertHost()
 
 
 
 
 
382
  {
 
 
 
383
  if (preg_match('#^(tcp|unix)://(.*)$#', $this->host, $matches)) {
384
  if($matches[1] == 'tcp') {
385
+ if ( ! preg_match('#^([^:]+)(:([0-9]+))?(/(.+))?$#', $matches[2], $matches)) {
386
+ throw new CredisException('Invalid host format; expected tcp://host[:port][/persistence_identifier]');
387
  }
388
  $this->host = $matches[1];
389
+ $this->port = (int) (isset($matches[3]) ? $matches[3] : 6379);
390
+ $this->persistent = isset($matches[5]) ? $matches[5] : '';
391
  } else {
392
  $this->host = $matches[2];
393
  $this->port = NULL;
399
  if ($this->port !== NULL && substr($this->host,0,1) == '/') {
400
  $this->port = NULL;
401
  }
402
+ }
403
+ /**
404
+ * @throws CredisException
405
+ * @return Credis_Client
406
+ */
407
+ public function connect()
408
+ {
409
+ if ($this->connected) {
410
+ return $this;
411
+ }
412
  if ($this->standalone) {
413
  $flags = STREAM_CLIENT_CONNECT;
414
  $remote_socket = $this->port === NULL
440
  }
441
  $failures = $this->connectFailures;
442
  $this->connectFailures = 0;
443
+ throw new CredisException("Connection to Redis failed after $failures failures." . (isset($errno) && isset($errstr) ? "Last Error : ({$errno}) {$errstr}" : ""));
444
  }
445
 
446
  $this->connectFailures = 0;
451
  $this->setReadTimeout($this->readTimeout);
452
  }
453
 
454
+ if($this->authPassword !== null) {
455
+ $this->auth($this->authPassword);
456
+ }
457
+ if($this->selectedDb !== 0) {
458
+ $this->select($this->selectedDb);
459
+ }
460
  return $this;
461
  }
 
462
  /**
463
+ * @return bool
464
+ */
465
+ public function isConnected()
466
+ {
467
+ return $this->connected;
468
+ }
469
+ /**
470
+ * Set the read timeout for the connection. Use 0 to disable timeouts entirely (or use a very long timeout
471
+ * if not supported).
472
  *
473
+ * @param int $timeout 0 (or -1) for no timeout, otherwise number of seconds
474
  * @throws CredisException
475
  * @return Credis_Client
476
  */
477
  public function setReadTimeout($timeout)
478
  {
479
+ if ($timeout < -1) {
480
+ throw new CredisException('Timeout values less than -1 are not accepted.');
481
  }
482
  $this->readTimeout = $timeout;
483
  if ($this->connected) {
484
  if ($this->standalone) {
485
+ $timeout = $timeout <= 0 ? 315360000 : $timeout; // Ten-year timeout
486
+ stream_set_blocking($this->redis, TRUE);
487
  stream_set_timeout($this->redis, (int) floor($timeout), ($timeout - floor($timeout)) * 1000000);
488
  } else if (defined('Redis::OPT_READ_TIMEOUT')) {
489
+ // supported in phpredis 2.2.3
490
+ // a timeout value of -1 means reads will not timeout
491
+ $timeout = $timeout == 0 ? -1 : $timeout;
492
  $this->redis->setOption(Redis::OPT_READ_TIMEOUT, $timeout);
493
  }
494
  }
512
  return $result;
513
  }
514
 
515
+ /**
516
+ * Enabled command renaming and provide mapping method. Supported methods are:
517
+ *
518
+ * 1. renameCommand('foo') // Salted md5 hash for all commands -> md5('foo'.$command)
519
+ * 2. renameCommand(function($command){ return 'my'.$command; }); // Callable
520
+ * 3. renameCommand('get', 'foo') // Single command -> alias
521
+ * 4. renameCommand(['get' => 'foo', 'set' => 'bar']) // Full map of [command -> alias]
522
+ *
523
+ * @param string|callable|array $command
524
+ * @param string|null $alias
525
+ * @return $this
526
+ */
527
+ public function renameCommand($command, $alias = NULL)
528
+ {
529
+ if ( ! $this->standalone) {
530
+ $this->forceStandalone();
531
+ }
532
+ if ($alias === NULL) {
533
+ $this->renamedCommands = $command;
534
+ } else {
535
+ if ( ! $this->renamedCommands) {
536
+ $this->renamedCommands = array();
537
+ }
538
+ $this->renamedCommands[$command] = $alias;
539
+ }
540
+ return $this;
541
+ }
542
+
543
+ /**
544
+ * @param $command
545
+ */
546
+ public function getRenamedCommand($command)
547
+ {
548
+ static $map;
549
+
550
+ // Command renaming not enabled
551
+ if ($this->renamedCommands === NULL) {
552
+ return $command;
553
+ }
554
+
555
+ // Initialize command map
556
+ if ($map === NULL) {
557
+ if (is_array($this->renamedCommands)) {
558
+ $map = $this->renamedCommands;
559
+ } else {
560
+ $map = array();
561
+ }
562
+ }
563
+
564
+ // Generate and return cached result
565
+ if ( ! isset($map[$command])) {
566
+ // String means all commands are hashed with salted md5
567
+ if (is_string($this->renamedCommands)) {
568
+ $map[$command] = md5($this->renamedCommands.$command);
569
+ }
570
+ // Would already be set in $map if it was intended to be renamed
571
+ else if (is_array($this->renamedCommands)) {
572
+ return $command;
573
+ }
574
+ // User-supplied function
575
+ else if (is_callable($this->renamedCommands)) {
576
+ $map[$command] = call_user_func($this->renamedCommands, $command);
577
+ }
578
+ }
579
+ return $map[$command];
580
+ }
581
+
582
  /**
583
  * @param string $password
584
  * @return bool
585
  */
586
  public function auth($password)
587
  {
588
+ $response = $this->__call('auth', array($password));
589
  $this->authPassword = $password;
 
590
  return $response;
591
  }
592
 
596
  */
597
  public function select($index)
598
  {
599
+ $response = $this->__call('select', array($index));
600
  $this->selectedDb = (int) $index;
 
601
  return $response;
602
  }
603
+
604
+ /**
605
+ * @param string|array $pattern
606
+ * @return array
607
+ */
608
+ public function pUnsubscribe()
609
+ {
610
+ list($command, $channel, $subscribedChannels) = $this->__call('punsubscribe', func_get_args());
611
+ $this->subscribed = $subscribedChannels > 0;
612
+ return array($command, $channel, $subscribedChannels);
613
+ }
614
+
615
+ /**
616
+ * @param string|array $patterns
617
+ * @param $callback
618
+ * @return $this|array|bool|Credis_Client|mixed|null|string
619
+ * @throws CredisException
620
+ */
621
+ public function pSubscribe($patterns, $callback)
622
+ {
623
+ if ( ! $this->standalone) {
624
+ return $this->__call('pSubscribe', array((array)$patterns, $callback));
625
+ }
626
+
627
+ // Standalone mode: use infinite loop to subscribe until timeout
628
+ $patternCount = is_array($patterns) ? count($patterns) : 1;
629
+ while ($patternCount--) {
630
+ if (isset($status)) {
631
+ list($command, $pattern, $status) = $this->read_reply();
632
+ } else {
633
+ list($command, $pattern, $status) = $this->__call('psubscribe', array($patterns));
634
+ }
635
+ $this->subscribed = $status > 0;
636
+ if ( ! $status) {
637
+ throw new CredisException('Invalid pSubscribe response.');
638
+ }
639
+ }
640
+ try {
641
+ while ($this->subscribed) {
642
+ list($type, $pattern, $channel, $message) = $this->read_reply();
643
+ if ($type != 'pmessage') {
644
+ throw new CredisException('Received non-pmessage reply.');
645
+ }
646
+ $callback($this, $pattern, $channel, $message);
647
+ }
648
+ } catch (CredisException $e) {
649
+ if ($e->getCode() == CredisException::CODE_TIMED_OUT) {
650
+ try {
651
+ list($command, $pattern, $status) = $this->pUnsubscribe($patterns);
652
+ while ($status !== 0) {
653
+ list($command, $pattern, $status) = $this->read_reply();
654
+ }
655
+ } catch (CredisException $e2) {
656
+ throw $e2;
657
+ }
658
+ }
659
+ throw $e;
660
+ }
661
+ }
662
+
663
+ /**
664
+ * @param string|array $pattern
665
+ * @return array
666
+ */
667
+ public function unsubscribe()
668
+ {
669
+ list($command, $channel, $subscribedChannels) = $this->__call('unsubscribe', func_get_args());
670
+ $this->subscribed = $subscribedChannels > 0;
671
+ return array($command, $channel, $subscribedChannels);
672
+ }
673
+
674
+ /**
675
+ * @param string|array $channels
676
+ * @param $callback
677
+ * @throws CredisException
678
+ * @return $this|array|bool|Credis_Client|mixed|null|string
679
+ */
680
+ public function subscribe($channels, $callback)
681
+ {
682
+ if ( ! $this->standalone) {
683
+ return $this->__call('subscribe', array((array)$channels, $callback));
684
+ }
685
+
686
+ // Standalone mode: use infinite loop to subscribe until timeout
687
+ $channelCount = is_array($channels) ? count($channels) : 1;
688
+ while ($channelCount--) {
689
+ if (isset($status)) {
690
+ list($command, $channel, $status) = $this->read_reply();
691
+ } else {
692
+ list($command, $channel, $status) = $this->__call('subscribe', array($channels));
693
+ }
694
+ $this->subscribed = $status > 0;
695
+ if ( ! $status) {
696
+ throw new CredisException('Invalid subscribe response.');
697
+ }
698
+ }
699
+ try {
700
+ while ($this->subscribed) {
701
+ list($type, $channel, $message) = $this->read_reply();
702
+ if ($type != 'message') {
703
+ throw new CredisException('Received non-message reply.');
704
+ }
705
+ $callback($this, $channel, $message);
706
+ }
707
+ } catch (CredisException $e) {
708
+ if ($e->getCode() == CredisException::CODE_TIMED_OUT) {
709
+ try {
710
+ list($command, $channel, $status) = $this->unsubscribe($channels);
711
+ while ($status !== 0) {
712
+ list($command, $channel, $status) = $this->read_reply();
713
+ }
714
+ } catch (CredisException $e2) {
715
+ throw $e2;
716
+ }
717
+ }
718
+ throw $e;
719
+ }
720
+ }
721
 
722
  public function __call($name, $args)
723
  {
729
  // Send request via native PHP
730
  if($this->standalone)
731
  {
732
+ switch ($name) {
733
+ case 'eval':
734
+ case 'evalsha':
735
+ $script = array_shift($args);
736
+ $keys = (array) array_shift($args);
737
+ $eArgs = (array) array_shift($args);
738
+ $args = array($script, count($keys), $keys, $eArgs);
739
+ break;
740
+ }
741
  // Flatten arguments
742
  $argsFlat = NULL;
743
  foreach($args as $index => $arg) {
746
  $argsFlat = array_slice($args, 0, $index);
747
  }
748
  if($name == 'mset' || $name == 'msetnx' || $name == 'hmset') {
749
+ foreach($arg as $key => $value) {
750
+ $argsFlat[] = $key;
751
+ $argsFlat[] = $value;
752
+ }
753
  } else {
754
+ $argsFlat = array_merge($argsFlat, $arg);
755
  }
756
  } else if($argsFlat !== NULL) {
757
  $argsFlat[] = $arg;
771
  else if($name == 'exec') {
772
  if($this->isMulti) {
773
  $this->commandNames[] = $name;
774
+ $this->commands .= self::_prepare_command(array($this->getRenamedCommand($name)));
775
  }
776
 
777
  // Write request
797
  if($name == 'multi') {
798
  $this->isMulti = TRUE;
799
  }
800
+ array_unshift($args, $this->getRenamedCommand($name));
801
  $this->commandNames[] = $name;
802
  $this->commands .= self::_prepare_command($args);
803
  return $this;
819
  }
820
 
821
  // Non-pipeline mode
822
+ array_unshift($args, $this->getRenamedCommand($name));
823
  $command = self::_prepare_command($args);
824
  $this->write_command($command);
825
  $response = $this->read_reply($name);
863
  case 'lrem':
864
  $args = array($args[0], $args[2], $args[1]);
865
  break;
866
+ case 'eval':
867
+ case 'evalsha':
868
+ if (isset($args[1]) && is_array($args[1])) {
869
+ $cKeys = $args[1];
870
+ } elseif (isset($args[1]) && is_string($args[1])) {
871
+ $cKeys = array($args[1]);
872
+ } else {
873
+ $cKeys = array();
874
+ }
875
+ if (isset($args[2]) && is_array($args[2])) {
876
+ $cArgs = $args[2];
877
+ } elseif (isset($args[2]) && is_string($args[2])) {
878
+ $cArgs = array($args[2]);
879
+ } else {
880
+ $cArgs = array();
881
+ }
882
+ $args = array($args[0], array_merge($cKeys, $cArgs), count($cKeys));
883
+ break;
884
+ case 'subscribe':
885
+ case 'psubscribe':
886
+ break;
887
  default:
888
  // Flatten arguments
889
  $argsFlat = NULL;
922
  }
923
 
924
  // Use aliases to be compatible with phpredis wrapper
925
+ if(isset($this->wrapperMethods[$name])) {
926
+ $name = $this->wrapperMethods[$name];
927
  }
928
 
929
  // Multi and pipeline return self for chaining
932
  return $this;
933
  }
934
 
935
+ // Send request, retry one time when using persistent connections on the first request only
936
+ $this->requests++;
937
+ try {
938
+ $response = call_user_func_array(array($this->redis, $name), $args);
939
+ } catch (RedisException $e) {
940
+ if ($this->persistent && $this->requests == 1 && $e->getMessage() == 'read error on connection') {
941
+ $this->connected = FALSE;
942
+ $this->connect();
943
+ $response = call_user_func_array(array($this->redis, $name), $args);
944
+ } else {
945
+ throw $e;
946
+ }
947
+ }
948
  }
949
+ // Wrap exceptions
950
  catch(RedisException $e) {
951
+ $code = 0;
952
+ if ( ! ($result = $this->redis->IsConnected())) {
953
+ $this->connected = FALSE;
954
+ $code = CredisException::CODE_DISCONNECTED;
955
+ }
956
+ throw new CredisException($e->getMessage(), $code, $e);
957
  }
958
 
959
  #echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n";
964
  case 'hmget':
965
  $response = array_values($response);
966
  break;
967
+
968
  case 'type':
969
  $typeMap = array(
970
+ self::TYPE_NONE,
971
+ self::TYPE_STRING,
972
+ self::TYPE_SET,
973
+ self::TYPE_LIST,
974
+ self::TYPE_ZSET,
975
+ self::TYPE_HASH,
976
  );
977
  $response = $typeMap[$response];
978
  break;
979
+
980
+ // Handle scripting errors
981
+ case 'eval':
982
+ case 'evalsha':
983
+ case 'script':
984
+ $error = $this->redis->getLastError();
985
+ $this->redis->clearLastError();
986
+ if ($error && substr($error,0,8) == 'NOSCRIPT') {
987
+ $response = NULL;
988
+ } else if ($error) {
989
+ throw new CredisException($error);
990
+ }
991
+ break;
992
+ default:
993
+ $error = $this->redis->getLastError();
994
+ $this->redis->clearLastError();
995
+ if ($error) {
996
+ throw new CredisException($error);
997
+ }
998
+ break;
999
  }
1000
  }
1001
 
1027
  for ($written = 0; $written < $commandLen; $written += $fwrite) {
1028
  $fwrite = fwrite($this->redis, substr($command, $written));
1029
  if ($fwrite === FALSE || $fwrite == 0 ) {
1030
+ $this->connected = FALSE;
1031
  throw new CredisException('Failed to write entire command to stream');
1032
  }
1033
  }
1037
  {
1038
  $reply = fgets($this->redis);
1039
  if($reply === FALSE) {
1040
+ $info = stream_get_meta_data($this->redis);
1041
+ if ($info['timed_out']) {
1042
+ throw new CredisException('Read operation timed out.', CredisException::CODE_TIMED_OUT);
1043
+ } else {
1044
+ $this->connected = FALSE;
1045
+ throw new CredisException('Lost connection to Redis server.', CredisException::CODE_DISCONNECTED);
1046
+ }
1047
  }
1048
  $reply = rtrim($reply, CRLF);
1049
  #echo "> $name: $reply\n";
1053
  case '-':
1054
  if($this->isMulti || $this->usePipeline) {
1055
  $response = FALSE;
1056
+ } else if ($name == 'evalsha' && substr($reply,0,9) == '-NOSCRIPT') {
1057
+ $response = NULL;
1058
  } else {
1059
+ throw new CredisException(substr($reply,0,4) == '-ERR' ? substr($reply, 5) : substr($reply,1));
1060
  }
1061
  break;
1062
  /* Inline reply */
1063
  case '+':
1064
  $response = substr($reply, 1);
1065
  if($response == 'OK' || $response == 'QUEUED') {
1066
+ return TRUE;
1067
  }
1068
  break;
1069
  /* Bulk reply */
1071
  if ($reply == '$-1') return FALSE;
1072
  $size = (int) substr($reply, 1);
1073
  $response = stream_get_contents($this->redis, $size + 2);
1074
+ if( ! $response) {
1075
+ $this->connected = FALSE;
1076
  throw new CredisException('Error reading reply.');
1077
+ }
1078
  $response = substr($response, 0, $size);
1079
  break;
1080
  /* Multi-bulk reply */
1084
 
1085
  $response = array();
1086
  for ($i = 0; $i < $count; $i++) {
1087
+ $response[] = $this->read_reply();
1088
  }
1089
  break;
1090
  /* Integer reply */
1115
  $response = array();
1116
  foreach($lines as $line) {
1117
  if ( ! $line || substr($line, 0, 1) == '#') {
1118
+ continue;
1119
  }
1120
  list($key, $value) = explode(':', $line, 2);
1121
  $response[$key] = $value;
package.xml CHANGED
@@ -1,18 +1,18 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Lib_Credis</name>
4
- <version>1.8.0.0</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">OSL v3.0</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Credis Library</summary>
10
  <description>Credis Library</description>
11
- <notes>1.8.0.0</notes>
12
  <authors><author><name>Magento Core Team</name><user>core</user><email>core@magentocommerce.com</email></author></authors>
13
- <date>2013-09-24</date>
14
- <time>09:09:42</time>
15
- <contents><target name="magelib"><dir name="Credis"><file name="Client.php" hash="7bf903229fda050175c4f52b317c13b3"/></dir></target></contents>
16
  <compatible/>
17
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
18
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Lib_Credis</name>
4
+ <version>1.9.2.0</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">OSL v3.0</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Credis Library</summary>
10
  <description>Credis Library</description>
11
+ <notes>1.9.2.0</notes>
12
  <authors><author><name>Magento Core Team</name><user>core</user><email>core@magentocommerce.com</email></author></authors>
13
+ <date>2015-06-26</date>
14
+ <time>13:48:52</time>
15
+ <contents><target name="magelib"><dir name="Credis"><file name="Client.php" hash="1fde64fdcd768d51758ec437d5952861"/></dir></target></contents>
16
  <compatible/>
17
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
18
  </package>