Kaynağa Gözat

Backported changes from the mainline library to the PHP 5.2 branch (up to commit c781e91)

Daniele Alessandri 15 yıl önce
ebeveyn
işleme
654b11ba6d

+ 44 - 20
CHANGELOG

@@ -1,7 +1,12 @@
-v0.6.0
-  * Completely switched to the new multi-bulk request protocol for all of the 
-    commands in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests 
-    are now deprecated as they will be removed in future releases of Redis.
+v0.6.0 (2010-05-??)
+  * Switched to the new multi-bulk request protocol for all of the commands 
+    in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now 
+    deprecated as they will be removed in future releases of Redis.
+
+  * The default server profile is "2.0" (targeting Redis 2.0.x). If you are 
+    using older versions of Redis, it is highly recommended that you specify 
+    which server profile the client should use (e.g. "1.2" when connecting 
+    to instances of Redis 1.2.x).
 
   * Support for Redis 1.0 is now optional and it is provided by requiring 
     'Predis_Compatibility.php' before creating an instance of Predis_Client.
@@ -24,13 +29,13 @@ v0.6.0
         specifies which server profile to use when connecting to Redis. This 
         option accepts an instance of Predis_RedisServerProfile or a string 
         that indicates the target version.
-      - key_distribution [default: Predis_Utilities_HashRing]
-        specifies which key distribution algorithm to use to distribute keys 
+      - key_distribution [default: Predis_Distribution_HashRing]
+        specifies which key distribution strategy to use to distribute keys 
         among the servers that compose a cluster. This option accepts an 
-        instance of Predis_IDistributionAlgorithm so that users can implement 
-        their own key distribution strategy. Optionally, the new 
-        Predis_Utilities_KetamaPureRing class also provides a pure-PHP 
-        implementation of the Ketama algorithm.
+        instance of Predis_Distribution_IDistributionStrategy so that users 
+        can implement their own key distribution strategy. Optionally, the new 
+        Predis_Distribution_KetamaPureRing class also provides a pure-PHP 
+        implementation of the same algorithm used by libketama.
       - throw_on_error [default: TRUE]
         server errors can optionally be handled "silently": instead of throwing 
         an exception, the client returns an error response type.
@@ -58,24 +63,43 @@ v0.6.0
         lifecycle. Persistent connections can lead to unpredictable or strange 
         behaviours, so they should be used with extreme care.
 
-  * Connections now support float values for the connection_timeout parameter 
-    to express timeouts with a microsecond resolution.
+  * Introduced the Predis_Pipeline_IPipelineExecutor interface. Classes that 
+    implements this interface are used internally by the Predis_CommandPipeline 
+    class to change the behaviour of the pipeline when writing/reading commands 
+    from one or multiple servers. Here is the list of the default executors:
+      - Predis_Pipeline_StandardExecutor
+        Exceptions generated by server errors might be thrown depending on the 
+        options passed to the client (see "throw_on_error"). Instead, protocol 
+        or network errors always throw exceptions. This is the default executor 
+        for single and clustered connections and shares the same behaviour of 
+        Predis 0.5.x.
+      - Predis_Pipeline_SafeExecutor
+        Exceptions generated by server, protocol or network errors are not 
+        thrown, instead they are returned in the response array as instances of 
+        ResponseError or CommunicationException.
+      - Predis_Pipeline_SafeClusterExecutor
+        This executor shares the same behaviour of Predis_Pipeline_SafeExecutor 
+        but it is geared towards clustered connections.
+
+  * Support for PUBSUB is handled by the new Predis_PubSubContext class, which 
+    could also be used to build a callback dispatcher for PUBSUB scenarios.
+
+  * When connected to a cluster of connections, it is now possible to get a 
+    new Predis_Client instance for a single connection of the cluster by 
+    passing its alias/index to the new Predis_Client::getClientFor() method.
 
   * CommandPipeline and MultiExecBlock return their instances when invoking 
     commands, thus allowing method chaining in pipelines and multi-exec blocks.
 
   * MultiExecBlock instances can handle the new DISCARD command.
 
+  * Connections now support float values for the connection_timeout parameter 
+    to express timeouts with a microsecond resolution.
+
   * The GET parameter for the SORT command now accepts also multiple key 
     patterns by passing an array of strings.
 
-  * KEYS will return a multibulk reply starting from Redis 2.0 (DEV). Predis 
-    handles this change in a backwards-compatible way.
-
-  * Switched to class-based handlers instead of anonymous functions to 
-    handle the various server response types.
-
-v0.5.1
+v0.5.1 (2010-01-27)
   * RPOPLPUSH has been changed from bulk command to inline command in Redis
     1.2.1, so ListPopLastPushHead now extends InlineCommand. The old RPOPLPUSH
     behavior is still available via the ListPopLastPushHeadBulk class so that
@@ -87,5 +111,5 @@ v0.5.1
   * Implemented a factory method for the RedisServerProfile class to ease the 
     creation of new server profile instances based on a version string.
 
-v0.5.0
+v0.5.0 (2010-01-09)
   * First versioned release of Predis

+ 7 - 6
README.markdown

@@ -17,10 +17,11 @@ to be implemented soon in Predis.
 
 ## Main features ##
 
-- Client-side sharding (support for consistent hashing and custom distribution algorithms)
-- Command pipelining on single and multiple connections (transparent)
-- Lazy connections (connections to Redis instances are only established just in time)
-- Flexible system to define and register your own set of commands to a client instance
+- Full support for Redis 2.0. Different versions of Redis are supported via server profiles.
+- Client-side sharding (support for consistent hashing and custom distribution strategies).
+- Command pipelining on single and multiple connections (transparent).
+- Lazy connections (connections to Redis instances are only established just in time).
+- Flexible system to define and register your own set of commands to a client instance.
 
 
 ## Quick examples ##
@@ -81,7 +82,7 @@ its way into a stable Predis release, then you can start off by creating a new
 class that matches the command type and its behaviour and then bind it to a 
 client instance at runtime. Actually, it is easier done than said:
 
-    class BrandNewRedisCommand extends Predis_InlineCommand {
+    class BrandNewRedisCommand extends Predis_MultiBulkCommand {
         public function getCommandId() { return 'NEWCMD'; }
     }
 
@@ -105,7 +106,7 @@ they are not the preferred way to contribute to Predis.
 
 When modifying Predis please be sure that no warnings or notices are emitted by PHP 
 by running the interpreter in your development environment with the "error_reporting"
-variable set to E_ALL.
+variable set to E_ALL | E_STRICT.
 
 
 ## Dependencies ##

+ 3 - 5
TODO

@@ -1,9 +1,7 @@
 * Documentation! The README is obviously not enought to show how to use 
   Predis as it does not cover all of its features.
 
-* The included test suite covers almost all the Redis server commands, but a 
-  full battery of tests targeting specific functions of this library is still 
-  missing.
+* Add more tests targeting specific functions of the library.
 
-* Missing tests for new commands: 
-    PubSub  : PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE
+* Missing tests for commands: 
+    PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE

+ 1 - 0
VERSION

@@ -0,0 +1 @@
+0.6.0

+ 3 - 3
examples/CommandPipeline.php

@@ -1,10 +1,10 @@
 <?php
 require_once 'SharedConfigurations.php';
 
-// when you have a whole set of consecutive commands to send to 
+// When you have a whole set of consecutive commands to send to 
 // a redis server, you can use a pipeline to improve performances.
 
-$redis = Predis_Client::create($configurations);
+$redis = new Predis_Client($single_server);
 
 $pipe = $redis->pipeline();
 $pipe->ping();
@@ -35,4 +35,4 @@ Array
 
 )
 */
-?>
+?>

+ 68 - 0
examples/CustomDistributionStrategy.php

@@ -0,0 +1,68 @@
+<?php
+require_once 'SharedConfigurations.php';
+
+// Developers can customize the distribution strategy used by the client 
+// to distribute keys among a cluster of servers simply by creating a class 
+// that implements the Predis_Distribution_IDistributionAlgorithm interface.
+
+class NaiveDistributionStrategy
+    implements Predis_Distribution_IDistributionStrategy {
+
+    private $_nodes, $_nodesCount;
+
+    public function __constructor() {
+        $this->_nodes = array();
+        $this->_nodesCount = 0;
+    }
+
+    public function add($node, $weight = null) {
+        $this->_nodes[] = $node;
+        $this->_nodesCount++;
+    }
+
+    private static function array_remove($array, $value) {
+        $newArray = array();
+        foreach ($array as $k => $v) {
+            if ($v !== $value) {
+                $newArray[] = $v;
+            }
+        }
+        return $newArray;
+    }
+
+    public function remove($node) {
+        $this->_nodes = self::array_remove($this->_nodes, $node);
+        $this->_nodesCount = count($this->_nodes);
+    }
+
+    public function get($key) {
+        $count = $this->_nodesCount;
+        if ($count === 0) {
+            throw new RuntimeException('No connections');
+        }
+        return $this->_nodes[$count > 1 ? abs(crc32($key) % $count) : 0];
+    }
+
+    public function generateKey($value) {
+        return crc32($value);
+    }
+}
+
+$options = array(
+    'key_distribution' => new NaiveDistributionStrategy(),
+);
+
+$redis = new Predis_Client($multiple_servers, $options);
+
+for ($i = 0; $i < 100; $i++) {
+    $redis->set("key:$i", str_pad($i, 4, '0', 0));
+    $redis->get("key:$i");
+}
+
+$server1 = $redis->getClientFor('first')->info();
+$server2 = $redis->getClientFor('second')->info();
+
+printf("Server '%s' has %d keys while server '%s' has %d keys.\n", 
+    'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
+);
+?>

+ 2 - 2
examples/MultipleSetAndGet.php

@@ -11,7 +11,7 @@ $mkv = array(
     'usr:0003' => 'Third user' 
 );
 
-$redis = Predis_Client::create($configurations);
+$redis = new Predis_Client($single_server);
 
 $redis->mset($mkv);
 $retval = $redis->mget(array_keys($mkv));
@@ -26,4 +26,4 @@ Array
     [2] => Third user
 )
 */
-?>
+?>

+ 6 - 3
examples/PubSubContext.php

@@ -1,8 +1,11 @@
 <?php
-require_once '../lib/Predis.php';
+require_once 'SharedConfigurations.php';
+
+// Redis 2.0 features new commands that allow clients to subscribe for 
+// events published on certain channels (PUBSUB).
 
 // Create a client and disable r/w timeout on the socket
-$redis  = new Predis_Client('redis://127.0.0.1:6379/?read_write_timeout=-1', 'dev');
+$redis  = new Predis_Client($single_server + array('read_write_timeout' => -1));
 
 // Initialize a new pubsub context
 $pubsub = $redis->pubSubContext();
@@ -27,7 +30,7 @@ foreach ($pubsub as $message) {
                     $pubsub->unsubscribe();
                 }
                 else {
-                    echo "Received an unregognized command: {$message->payload}.\n";
+                    echo "Received an unrecognized command: {$message->payload}.\n";
                 }
             }
             else {

+ 17 - 2
examples/SharedConfigurations.php

@@ -1,9 +1,24 @@
 <?php
 require_once '../lib/Predis.php';
 
-$configurations = array(
+$single_server = array(
     'host'     => '127.0.0.1', 
     'port'     => 6379, 
     'database' => 15
 );
-?>
+
+$multiple_servers = array(
+    array(
+       'host'     => '127.0.0.1',
+       'port'     => 6379,
+       'database' => 15,
+       'alias'    => 'first',
+    ),
+    array(
+       'host'     => '127.0.0.1',
+       'port'     => 6380,
+       'database' => 15,
+       'alias'    => 'second',
+    ),
+);
+?>

+ 2 - 2
examples/SimpleSetAndGet.php

@@ -3,7 +3,7 @@ require_once 'SharedConfigurations.php';
 
 // simple set and get scenario
 
-$redis = Predis_Client::create($configurations);
+$redis = new Predis_Client($single_server);
 
 $redis->set('library', 'predis');
 $retval = $redis->get('library');
@@ -13,4 +13,4 @@ print_r($retval);
 /* OUTPUT
 predis
 */
-?>
+?>

+ 186 - 36
lib/Predis.php

@@ -7,7 +7,7 @@ class Predis_ClientException extends PredisException { }
 // Server-side errors
 class Predis_ServerException extends PredisException {
     public function toResponseError() {
-        return new ResponseError($this->getMessage());
+        return new Predis_ResponseError($this->getMessage());
     }
 }
 
@@ -154,6 +154,26 @@ class Predis_Client {
         return $this->_responseReader;
     }
 
+    public function getClientFor($connectionAlias) {
+        if (!($this->_connection instanceof Predis_ConnectionCluster)) {
+            throw new Predis_ClientException(
+                'This method is supported only when the client is connected to a cluster of connections.'
+            );
+        }
+
+        $connection = $this->_connection->getConnectionById($connectionAlias);
+        if ($connection === null) {
+            throw new InvalidArgumentException(
+                "Invalid connection alias: '$connectionAlias'"
+            );
+        }
+
+        $newClient = new Predis_Client();
+        $newClient->setupClient($this->_options);
+        $newClient->setConnection($this->getConnection($connectionAlias));
+        return $newClient;
+    }
+
     public function connect() {
         $this->_connection->connect();
     }
@@ -186,11 +206,11 @@ class Predis_Client {
         return $this->_serverProfile->createCommand($method, $arguments);
     }
 
-    public function executeCommand(Command $command) {
+    public function executeCommand(Predis_Command $command) {
         return $this->_connection->executeCommand($command);
     }
 
-    public function executeCommandOnShards(Command $command) {
+    public function executeCommandOnShards(Predis_Command $command) {
         $replies = array();
         if ($this->_connection instanceof Predis_ConnectionCluster) {
             foreach($this->_connection as $connection) {
@@ -211,8 +231,20 @@ class Predis_Client {
     }
 
     public function pipeline($pipelineBlock = null) {
-        $pipeline = new Predis_CommandPipeline($this);
-        return $pipelineBlock !== null ? $pipeline->execute($pipelineBlock) : $pipeline;
+        return $this->pipelineExecute(new Predis_CommandPipeline($this), $pipelineBlock);
+    }
+
+    public function pipelineSafe($pipelineBlock = null) {
+        $connection = $this->getConnection();
+        $pipeline   = new Predis_CommandPipeline($this, $connection instanceof Predis_Connection
+            ? new Predis_Pipeline_SafeExecutor($connection)
+            : new Predis_Pipeline_SafeClusterExecutor($connection)
+        );
+        return $this->pipelineExecute($pipeline, $pipelineBlock);
+    }
+
+    private function pipelineExecute(Predis_CommandPipeline $pipeline, $block) {
+        return $block !== null ? $pipeline->execute($block) : $pipeline;
     }
 
     public function multiExec($multiExecBlock = null) {
@@ -250,12 +282,12 @@ class Predis_ClientOptionsProfile implements Predis_IClientOptionsHandler {
 
 class Predis_ClientOptionsKeyDistribution implements Predis_IClientOptionsHandler {
     public function validate($option, $value) {
-        if ($value instanceof Predis_Distribution_IDistributionAlgorithm) {
+        if ($value instanceof Predis_Distribution_IDistributionStrategy) {
             return $value;
         }
         if (is_string($value)) {
             $valueReflection = new ReflectionClass($value);
-            if ($valueReflection->isSubclassOf('Predis_Distribution_IDistributionAlgorithm')) {
+            if ($valueReflection->isSubclassOf('Predis_Distribution_IDistributionStrategy')) {
                 return new $value;
             }
         }
@@ -360,12 +392,14 @@ abstract class Predis_Command {
         return true;
     }
 
-    public function getHash(Predis_Distribution_IDistributionAlgorithm $distributor) {
+    public function getHash(Predis_Distribution_IDistributionStrategy $distributor) {
         if (isset($this->_hash)) {
             return $this->_hash;
         }
         else {
             if (isset($this->_arguments[0])) {
+                // TODO: should we throw an exception if the command does 
+                //       not support sharding?
                 $key = $this->_arguments[0];
 
                 $start = strpos($key, '{');
@@ -422,7 +456,10 @@ abstract class Predis_InlineCommand extends Predis_Command {
         if (isset($arguments[0]) && is_array($arguments[0])) {
             $arguments[0] = implode($arguments[0], ' ');
         }
-        return $command . ' ' . implode($arguments, ' ') . Predis_Protocol::NEWLINE;
+        return $command . (count($arguments) > 0
+            ? ' ' . implode($arguments, ' ') . Predis_Protocol::NEWLINE 
+            : Predis_Protocol::NEWLINE
+        );
     }
 }
 
@@ -661,10 +698,11 @@ class Predis_ResponseQueued {
 /* ------------------------------------------------------------------------- */
 
 class Predis_CommandPipeline {
-    private $_redisClient, $_pipelineBuffer, $_returnValues, $_running;
+    private $_redisClient, $_pipelineBuffer, $_returnValues, $_running, $_executor;
 
-    public function __construct(Predis_Client $redisClient) {
+    public function __construct(Predis_Client $redisClient, Predis_Pipeline_IPipelineExecutor $executor = null) {
         $this->_redisClient    = $redisClient;
+        $this->_executor       = $executor !== null ? $executor : new Predis_Pipeline_StandardExecutor();
         $this->_pipelineBuffer = array();
         $this->_returnValues   = array();
     }
@@ -684,27 +722,14 @@ class Predis_CommandPipeline {
     }
 
     public function flushPipeline() {
-        $sizeofPipe = count($this->_pipelineBuffer);
-        if ($sizeofPipe === 0) {
-            return;
-        }
-
-        $connection = $this->_redisClient->getConnection();
-        $commands   = &$this->_pipelineBuffer;
-
-        foreach ($commands as $command) {
-            $connection->writeCommand($command);
-        }
-        for ($i = 0; $i < $sizeofPipe; $i++) {
-            $response = $connection->readResponse($commands[$i]);
-            $this->_returnValues[] = ($response instanceof Iterator
-                ? iterator_to_array($response)
-                : $response
+        if (count($this->_pipelineBuffer) > 0) {
+            $connection = $this->_redisClient->getConnection();
+            $this->_returnValues = array_merge(
+                $this->_returnValues, 
+                $this->_executor->execute($connection, $this->_pipelineBuffer)
             );
-            unset($commands[$i]);
+            $this->_pipelineBuffer = array();
         }
-        $this->_pipelineBuffer = array();
-
         return $this;
     }
 
@@ -712,7 +737,6 @@ class Predis_CommandPipeline {
         if ($bool == true && $this->_running == true) {
             throw new Predis_ClientException("This pipeline is already opened");
         }
-
         $this->_running = $bool;
     }
 
@@ -732,7 +756,6 @@ class Predis_CommandPipeline {
             $this->flushPipeline();
         }
         catch (Exception $exception) {
-            // TODO: client/server desync on ServerException
             $pipelineBlockException = $exception;
         }
 
@@ -1223,7 +1246,7 @@ class Predis_Connection implements Predis_IConnection {
 class Predis_ConnectionCluster implements Predis_IConnection, IteratorAggregate {
     private $_pool, $_distributor;
 
-    public function __construct(Predis_Distribution_IDistributionAlgorithm $distributor = null) {
+    public function __construct(Predis_Distribution_IDistributionStrategy $distributor = null) {
         $this->_pool = array();
         $this->_distributor = $distributor !== null ? $distributor : new Predis_Distribution_HashRing();
     }
@@ -1270,7 +1293,8 @@ class Predis_ConnectionCluster implements Predis_IConnection, IteratorAggregate
     }
 
     public function getConnectionById($id = null) {
-        return $this->_pool[$id === null ? 0 : $id];
+        $alias = $id !== null ? $id : 0;
+        return isset($this->_pool[$alias]) ? $this->_pool[$alias] : null;
     }
 
     public function getIterator() {
@@ -1644,7 +1668,133 @@ class Predis_RedisServer_vNext extends Predis_RedisServer_v2_0 {
 
 /* ------------------------------------------------------------------------- */
 
-interface Predis_Distribution_IDistributionAlgorithm {
+interface Predis_Pipeline_IPipelineExecutor {
+    public function execute(Predis_IConnection $connection, &$commands);
+}
+
+class Predis_Pipeline_StandardExecutor implements Predis_Pipeline_IPipelineExecutor {
+    public function execute(Predis_IConnection $connection, &$commands) {
+        $sizeofPipe = count($commands);
+        $values = array();
+
+        foreach ($commands as $command) {
+            $connection->writeCommand($command);
+        }
+        try {
+            for ($i = 0; $i < $sizeofPipe; $i++) {
+                $response = $connection->readResponse($commands[$i]);
+                $values[] = $response instanceof Iterator
+                    ? iterator_to_array($response)
+                    : $response;
+                unset($commands[$i]);
+            }
+        }
+        catch (Predis_ServerException $exception) {
+            // force disconnection to prevent protocol desynchronization
+            $connection->disconnect();
+            throw $exception;
+        }
+
+        return $values;
+    }
+}
+
+class Predis_Pipeline_SafeExecutor implements Predis_Pipeline_IPipelineExecutor {
+    public function execute(Predis_IConnection $connection, &$commands) {
+        $firstServerException = null;
+        $sizeofPipe = count($commands);
+        $values = array();
+
+        foreach ($commands as $command) {
+            try {
+                $connection->writeCommand($command);
+            }
+            catch (Predis_CommunicationException $exception) {
+                return array_fill(0, $sizeofPipe, $exception);
+            }
+        }
+
+        for ($i = 0; $i < $sizeofPipe; $i++) {
+            $command = $commands[$i];
+            unset($commands[$i]);
+            try {
+                $response = $connection->readResponse($command);
+                $values[] = ($response instanceof Iterator
+                    ? iterator_to_array($response)
+                    : $response
+                );
+            }
+            catch (Predis_ServerException $exception) {
+                $values[] = $exception->toResponseError();
+            }
+            catch (Predis_CommunicationException $exception) {
+                if ($throwExceptions) {
+                    throw $exception;
+                }
+                $toAdd  = count($commands) - count($values);
+                $values = array_merge($values, array_fill(0, $toAdd, $exception));
+                break;
+            }
+        }
+
+        return $values;
+    }
+}
+
+class Predis_Pipeline_SafeClusterExecutor implements Predis_Pipeline_IPipelineExecutor {
+    public function execute(Predis_IConnection $connection, &$commands) {
+        $connectionExceptions = array();
+        $sizeofPipe = count($commands);
+        $values = array();
+
+        foreach ($commands as $command) {
+            $cmdConnection = $connection->getConnection($command);
+            if (isset($connectionExceptions[spl_object_hash($cmdConnection)])) {
+                continue;
+            }
+            try {
+                $cmdConnection->writeCommand($command);
+            }
+            catch (Predis_CommunicationException $exception) {
+                $connectionExceptions[spl_object_hash($cmdConnection)] = $exception;
+            }
+        }
+
+        for ($i = 0; $i < $sizeofPipe; $i++) {
+            $command = $commands[$i];
+            unset($commands[$i]);
+
+            $cmdConnection = $connection->getConnection($command);
+            $connectionObjectHash = spl_object_hash($cmdConnection);
+
+            if (isset($connectionExceptions[$connectionObjectHash])) {
+                $values[] = $connectionExceptions[$connectionObjectHash];
+                continue;
+            }
+
+            try {
+                $response = $cmdConnection->readResponse($command);
+                $values[] = ($response instanceof Iterator
+                    ? iterator_to_array($response)
+                    : $response
+                );
+            }
+            catch (Predis_ServerException $exception) {
+                $values[] = $exception->toResponseError();
+            }
+            catch (Predis_CommunicationException $exception) {
+                $values[] = $exception;
+                $connectionExceptions[$connectionObjectHash] = $exception;
+            }
+        }
+
+        return $values;
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+
+interface Predis_Distribution_IDistributionStrategy {
     public function add($node, $weight = null);
     public function remove($node);
     public function get($key);
@@ -1653,7 +1803,7 @@ interface Predis_Distribution_IDistributionAlgorithm {
 
 class Predis_Distribution_EmptyRingException extends Exception { }
 
-class Predis_Distribution_HashRing implements Predis_Distribution_IDistributionAlgorithm {
+class Predis_Distribution_HashRing implements Predis_Distribution_IDistributionStrategy {
     const DEFAULT_REPLICAS = 128;
     const DEFAULT_WEIGHT   = 100;
     private $_nodes, $_ring, $_ringKeys, $_ringKeysCount, $_replicas;

+ 1 - 1
test/PredisClientFeatures.php

@@ -129,7 +129,7 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
         $this->assertEquals('MSET', $cmd->getCommandId());
         $this->assertFalse($cmd->closesConnection());
         $this->assertFalse($cmd->canBeHashed());
-        $this->assertNull($cmd->getHash(new Predis_Distribution_HashRing()));
+        $this->assertNotNull($cmd->getHash(new Predis_Distribution_HashRing()));
         $this->assertEquals("*5\r\n$4\r\nMSET\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n", $cmd->invoke());
     }