فهرست منبع

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

Daniele Alessandri 15 سال پیش
والد
کامیت
7806614041
9فایلهای تغییر یافته به همراه1966 افزوده شده و 381 حذف شده
  1. 77 0
      CHANGELOG
  2. 1 1
      LICENSE
  3. 6 5
      README.markdown
  4. 5 0
      TODO
  5. 522 146
      lib/Predis.php
  6. 496 0
      lib/Predis_Compatibility.php
  7. 503 0
      test/PredisClientFeatures.php
  8. 66 9
      test/PredisShared.php
  9. 290 220
      test/RedisCommandsTest.php

+ 77 - 0
CHANGELOG

@@ -1,3 +1,80 @@
+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.
+
+  * Support for Redis 1.0 is now optional and it is provided by requiring 
+    'Predis_Compatibility.php' before creating an instance of Predis_Client.
+
+  * New commands added to the Redis 2.0 profile since Predis 0.5.1:
+      - Strings: SETEX, APPEND, SUBSTR
+      - ZSets  : ZCOUNT, ZRANK, ZUNIONSTORE, ZINTERSTORE, ZREMBYRANK, ZREVRANK
+      - Hashes : HSET, HSETNX, HMSET, HINCRBY, HGET, HMGET, HDEL, HEXISTS, 
+                 HLEN, HKEYS, HVALS, HGETALL
+      - PubSub : PUBLISH, SUBSCRIBE, UNSUBSCRIBE
+      - Misc.  : DISCARD, CONFIG
+
+  * Introduced client-level options with the new Predis_ClientOptions class. 
+    Options can be passed to Predis_Client::__construct in its second argument 
+    as an array or an instance of Predis_ClientOptions. For brevity's sake and 
+    compatibility with older versions, the constructor of Predis_Client still 
+    accepts an instance of Predis_RedisServerProfile in its second argument.
+    The currently supported client options are:
+      - profile [default: "2.0" as of Predis 0.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 
+        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.
+      - throw_on_error [default: TRUE]
+        server errors can optionally be handled "silently": instead of throwing 
+        an exception, the client returns an error response type.
+      - iterable_multibulk [EXPERIMENTAL - default: FALSE]
+        in addition to the classic way of fetching a whole multibulk reply 
+        into an array, the client can now optionally stream a multibulk reply 
+        down to the user code by using PHP iterators. It is just a little bit 
+        slower, but it can save a lot of memory in certain scenarios.
+
+  * New parameters for connections: 
+      - alias [default: not set]
+        every connection can now be identified by an alias that is useful to 
+        get a certain connection when connected to a cluster of Redis servers.
+      - weight [default: not set]
+        allows the client to balance the keys asymmetrically across multiple 
+        servers. This might be useful when you have servers with different 
+        amounts of memory and you want to distribute the load of your keys 
+        accordingly.
+      - connection_async [default: FALSE]
+        estabilish connections to servers in a non-blocking way, so that the 
+        client is not blocked while the underlying resource performs the actual 
+        connection.
+      - connection_persistent [default: FALSE]
+        the underlying connection resource is left open when a script ends its 
+        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.
+
+  * 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.
+
+  * 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
   * RPOPLPUSH has been changed from bulk command to inline command in Redis
     1.2.1, so ListPopLastPushHead now extends InlineCommand. The old RPOPLPUSH

+ 1 - 1
LICENSE

@@ -1,5 +1,5 @@
 Copyright (c) 2009-2010 Daniele Alessandri
-qq
+
 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without

+ 6 - 5
README.markdown

@@ -3,9 +3,7 @@
 ## About ##
 
 Predis is a flexible and feature-complete PHP client library for the Redis key-value 
-database.
-
-Predis is currently a work-in-progress and it comes in two flavors:
+database. It currently comes in two flavors:
 
  - the mainline client library, which targets PHP 5.3.x and leverages a lot of the 
    features introduced in this new version of the PHP interpreter.
@@ -17,9 +15,9 @@ Please refer to the TODO file to see which issues are still pending and what is
 to be implemented soon in Predis.
 
 
-## Features ##
+## Main features ##
 
-- Client-side sharding (support for consistent hashing of keys)
+- 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
@@ -133,6 +131,9 @@ variable set to E_ALL.
 
 [Daniele Alessandri](mailto:suppakilla@gmail.com)
 
+## Contributors ##
+
+[Lorenzo Castelli](http://github.com/lcastelli)
 
 ## License ##
 

+ 5 - 0
TODO

@@ -4,3 +4,8 @@
 * 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 a PubSubBlock class.
+
+* Missing tests for new commands: 
+    PubSub  : PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 522 - 146
lib/Predis.php


+ 496 - 0
lib/Predis_Compatibility.php

@@ -0,0 +1,496 @@
+<?php
+Predis_RedisServerProfile::registerProfile('Predis_RedisServer_v1_0', '1.0');
+
+class Predis_RedisServer_v1_0 extends Predis_RedisServerProfile {
+    public function getVersion() { return '1.0'; }
+    public function getSupportedCommands() {
+        return array(
+            /* miscellaneous commands */
+            'ping'      => 'Predis_Compatibility_v1_0_Commands_Ping',
+            'echo'      => 'Predis_Compatibility_v1_0_Commands_DoEcho',
+            'auth'      => 'Predis_Compatibility_v1_0_Commands_Auth',
+
+            /* connection handling */
+            'quit'      => 'Predis_Compatibility_v1_0_Commands_Quit',
+
+            /* commands operating on string values */
+            'set'                     => 'Predis_Compatibility_v1_0_Commands_Set',
+            'setnx'                   => 'Predis_Compatibility_v1_0_Commands_SetPreserve',
+                'setPreserve'         => 'Predis_Compatibility_v1_0_Commands_SetPreserve',
+            'get'                     => 'Predis_Compatibility_v1_0_Commands_Get',
+            'mget'                    => 'Predis_Compatibility_v1_0_Commands_GetMultiple',
+                'getMultiple'         => 'Predis_Compatibility_v1_0_Commands_GetMultiple',
+            'getset'                  => 'Predis_Compatibility_v1_0_Commands_GetSet',
+                'getSet'              => 'Predis_Compatibility_v1_0_Commands_GetSet',
+            'incr'                    => 'Predis_Compatibility_v1_0_Commands_Increment',
+                'increment'           => 'Predis_Compatibility_v1_0_Commands_Increment',
+            'incrby'                  => 'Predis_Compatibility_v1_0_Commands_IncrementBy',
+                'incrementBy'         => 'Predis_Compatibility_v1_0_Commands_IncrementBy',
+            'decr'                    => 'Predis_Compatibility_v1_0_Commands_Decrement',
+                'decrement'           => 'Predis_Compatibility_v1_0_Commands_Decrement',
+            'decrby'                  => 'Predis_Compatibility_v1_0_Commands_DecrementBy',
+                'decrementBy'         => 'Predis_Compatibility_v1_0_Commands_DecrementBy',
+            'exists'                  => 'Predis_Compatibility_v1_0_Commands_Exists',
+            'del'                     => 'Predis_Compatibility_v1_0_Commands_Delete',
+                'delete'              => 'Predis_Compatibility_v1_0_Commands_Delete',
+            'type'                    => 'Predis_Compatibility_v1_0_Commands_Type',
+
+            /* commands operating on the key space */
+            'keys'               => 'Predis_Compatibility_v1_0_Commands_Keys',
+            'randomkey'          => 'Predis_Compatibility_v1_0_Commands_RandomKey',
+                'randomKey'      => 'Predis_Compatibility_v1_0_Commands_RandomKey',
+            'rename'             => 'Predis_Compatibility_v1_0_Commands_Rename',
+            'renamenx'           => 'Predis_Compatibility_v1_0_Commands_RenamePreserve',
+                'renamePreserve' => 'Predis_Compatibility_v1_0_Commands_RenamePreserve',
+            'expire'             => 'Predis_Compatibility_v1_0_Commands_Expire',
+            'expireat'           => 'Predis_Compatibility_v1_0_Commands_ExpireAt',
+                'expireAt'       => 'Predis_Compatibility_v1_0_Commands_ExpireAt',
+            'dbsize'             => 'Predis_Compatibility_v1_0_Commands_DatabaseSize',
+                'databaseSize'   => 'Predis_Compatibility_v1_0_Commands_DatabaseSize',
+            'ttl'                => 'Predis_Compatibility_v1_0_Commands_TimeToLive',
+                'timeToLive'     => 'Predis_Compatibility_v1_0_Commands_TimeToLive',
+
+            /* commands operating on lists */
+            'rpush'            => 'Predis_Compatibility_v1_0_Commands_ListPushTail',
+                'pushTail'     => 'Predis_Compatibility_v1_0_Commands_ListPushTail',
+            'lpush'            => 'Predis_Compatibility_v1_0_Commands_ListPushHead',
+                'pushHead'     => 'Predis_Compatibility_v1_0_Commands_ListPushHead',
+            'llen'             => 'Predis_Compatibility_v1_0_Commands_ListLength',
+                'listLength'   => 'Predis_Compatibility_v1_0_Commands_ListLength',
+            'lrange'           => 'Predis_Compatibility_v1_0_Commands_ListRange',
+                'listRange'    => 'Predis_Compatibility_v1_0_Commands_ListRange',
+            'ltrim'            => 'Predis_Compatibility_v1_0_Commands_ListTrim',
+                'listTrim'     => 'Predis_Compatibility_v1_0_Commands_ListTrim',
+            'lindex'           => 'Predis_Compatibility_v1_0_Commands_ListIndex',
+                'listIndex'    => 'Predis_Compatibility_v1_0_Commands_ListIndex',
+            'lset'             => 'Predis_Compatibility_v1_0_Commands_ListSet',
+                'listSet'      => 'Predis_Compatibility_v1_0_Commands_ListSet',
+            'lrem'             => 'Predis_Compatibility_v1_0_Commands_ListRemove',
+                'listRemove'   => 'Predis_Compatibility_v1_0_Commands_ListRemove',
+            'lpop'             => 'Predis_Compatibility_v1_0_Commands_ListPopFirst',
+                'popFirst'     => 'Predis_Compatibility_v1_0_Commands_ListPopFirst',
+            'rpop'             => 'Predis_Compatibility_v1_0_Commands_ListPopLast',
+                'popLast'      => 'Predis_Compatibility_v1_0_Commands_ListPopLast',
+
+            /* commands operating on sets */
+            'sadd'                      => 'Predis_Compatibility_v1_0_Commands_SetAdd', 
+                'setAdd'                => 'Predis_Compatibility_v1_0_Commands_SetAdd',
+            'srem'                      => 'Predis_Compatibility_v1_0_Commands_SetRemove', 
+                'setRemove'             => 'Predis_Compatibility_v1_0_Commands_SetRemove',
+            'spop'                      => 'Predis_Compatibility_v1_0_Commands_SetPop',
+                'setPop'                => 'Predis_Compatibility_v1_0_Commands_SetPop',
+            'smove'                     => 'Predis_Compatibility_v1_0_Commands_SetMove', 
+                'setMove'               => 'Predis_Compatibility_v1_0_Commands_SetMove',
+            'scard'                     => 'Predis_Compatibility_v1_0_Commands_SetCardinality', 
+                'setCardinality'        => 'Predis_Compatibility_v1_0_Commands_SetCardinality',
+            'sismember'                 => 'Predis_Compatibility_v1_0_Commands_SetIsMember', 
+                'setIsMember'           => 'Predis_Compatibility_v1_0_Commands_SetIsMember',
+            'sinter'                    => 'Predis_Compatibility_v1_0_Commands_SetIntersection', 
+                'setIntersection'       => 'Predis_Compatibility_v1_0_Commands_SetIntersection',
+            'sinterstore'               => 'Predis_Compatibility_v1_0_Commands_SetIntersectionStore', 
+                'setIntersectionStore'  => 'Predis_Compatibility_v1_0_Commands_SetIntersectionStore',
+            'sunion'                    => 'Predis_Compatibility_v1_0_Commands_SetUnion', 
+                'setUnion'              => 'Predis_Compatibility_v1_0_Commands_SetUnion',
+            'sunionstore'               => 'Predis_Compatibility_v1_0_Commands_SetUnionStore', 
+                'setUnionStore'         => 'Predis_Compatibility_v1_0_Commands_SetUnionStore',
+            'sdiff'                     => 'Predis_Compatibility_v1_0_Commands_SetDifference', 
+                'setDifference'         => 'Predis_Compatibility_v1_0_Commands_SetDifference',
+            'sdiffstore'                => 'Predis_Compatibility_v1_0_Commands_SetDifferenceStore', 
+                'setDifferenceStore'    => 'Predis_Compatibility_v1_0_Commands_SetDifferenceStore',
+            'smembers'                  => 'Predis_Compatibility_v1_0_Commands_SetMembers', 
+                'setMembers'            => 'Predis_Compatibility_v1_0_Commands_SetMembers',
+            'srandmember'               => 'Predis_Compatibility_v1_0_Commands_SetRandomMember', 
+                'setRandomMember'       => 'Predis_Compatibility_v1_0_Commands_SetRandomMember',
+
+            /* multiple databases handling commands */
+            'select'                => 'Predis_Compatibility_v1_0_Commands_SelectDatabase', 
+                'selectDatabase'    => 'Predis_Compatibility_v1_0_Commands_SelectDatabase',
+            'move'                  => 'Predis_Compatibility_v1_0_Commands_MoveKey', 
+                'moveKey'           => 'Predis_Compatibility_v1_0_Commands_MoveKey',
+            'flushdb'               => 'Predis_Compatibility_v1_0_Commands_FlushDatabase', 
+                'flushDatabase'     => 'Predis_Compatibility_v1_0_Commands_FlushDatabase',
+            'flushall'              => 'Predis_Compatibility_v1_0_Commands_FlushAll', 
+                'flushDatabases'    => 'Predis_Compatibility_v1_0_Commands_FlushAll',
+
+            /* sorting */
+            'sort'                  => 'Predis_Compatibility_v1_0_Commands_Sort',
+
+            /* remote server control commands */
+            'info'                  => 'Predis_Compatibility_v1_0_Commands_Info',
+            'slaveof'               => 'Predis_Compatibility_v1_0_Commands_SlaveOf', 
+                'slaveOf'           => 'Predis_Compatibility_v1_0_Commands_SlaveOf',
+
+            /* persistence control commands */
+            'save'                  => 'Predis_Compatibility_v1_0_Commands_Save',
+            'bgsave'                => 'Predis_Compatibility_v1_0_Commands_BackgroundSave', 
+                'backgroundSave'    => 'Predis_Compatibility_v1_0_Commands_BackgroundSave',
+            'lastsave'              => 'Predis_Compatibility_v1_0_Commands_LastSave', 
+                'lastSave'          => 'Predis_Compatibility_v1_0_Commands_LastSave',
+            'shutdown'              => 'Predis_Compatibility_v1_0_Commands_Shutdown',
+        );
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* miscellaneous commands */
+class Predis_Compatibility_v1_0_Commands_Ping extends  Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'PING'; }
+    public function parseResponse($data) {
+        return $data === 'PONG' ? true : false;
+    }
+}
+
+class Predis_Compatibility_v1_0_Commands_DoEcho extends Predis_BulkCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'ECHO'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Auth extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'AUTH'; }
+}
+
+/* connection handling */
+class Predis_Compatibility_v1_0_Commands_Quit extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'QUIT'; }
+    public function closesConnection() { return true; }
+}
+
+/* commands operating on string values */
+class Predis_Compatibility_v1_0_Commands_Set extends Predis_BulkCommand {
+    public function getCommandId() { return 'SET'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetPreserve extends Predis_BulkCommand {
+    public function getCommandId() { return 'SETNX'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Get extends Predis_InlineCommand {
+    public function getCommandId() { return 'GET'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_GetMultiple extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'MGET'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_GetSet extends Predis_BulkCommand {
+    public function getCommandId() { return 'GETSET'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Increment extends Predis_InlineCommand {
+    public function getCommandId() { return 'INCR'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_IncrementBy extends Predis_InlineCommand {
+    public function getCommandId() { return 'INCRBY'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Decrement extends Predis_InlineCommand {
+    public function getCommandId() { return 'DECR'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_DecrementBy extends Predis_InlineCommand {
+    public function getCommandId() { return 'DECRBY'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Exists extends Predis_InlineCommand {
+    public function getCommandId() { return 'EXISTS'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Delete extends Predis_InlineCommand {
+    public function getCommandId() { return 'DEL'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Type extends Predis_InlineCommand {
+    public function getCommandId() { return 'TYPE'; }
+}
+
+/* commands operating on the key space */
+class Predis_Compatibility_v1_0_Commands_Keys extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'KEYS'; }
+    public function parseResponse($data) { 
+        return strlen($data) > 0 ? explode(' ', $data) : array();
+    }
+}
+
+class Predis_Compatibility_v1_0_Commands_RandomKey extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'RANDOMKEY'; }
+    public function parseResponse($data) { return $data !== '' ? $data : null; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Rename extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'RENAME'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_RenamePreserve extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'RENAMENX'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Expire extends Predis_InlineCommand {
+    public function getCommandId() { return 'EXPIRE'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ExpireAt extends Predis_InlineCommand {
+    public function getCommandId() { return 'EXPIREAT'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_DatabaseSize extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'DBSIZE'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_TimeToLive extends Predis_InlineCommand {
+    public function getCommandId() { return 'TTL'; }
+}
+
+/* commands operating on lists */
+class Predis_Compatibility_v1_0_Commands_ListPushTail extends Predis_BulkCommand {
+    public function getCommandId() { return 'RPUSH'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListPushHead extends Predis_BulkCommand {
+    public function getCommandId() { return 'LPUSH'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListLength extends Predis_InlineCommand {
+    public function getCommandId() { return 'LLEN'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListRange extends Predis_InlineCommand {
+    public function getCommandId() { return 'LRANGE'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListTrim extends Predis_InlineCommand {
+    public function getCommandId() { return 'LTRIM'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListIndex extends Predis_InlineCommand {
+    public function getCommandId() { return 'LINDEX'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListSet extends Predis_BulkCommand {
+    public function getCommandId() { return 'LSET'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListRemove extends Predis_BulkCommand {
+    public function getCommandId() { return 'LREM'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListPopFirst extends Predis_InlineCommand {
+    public function getCommandId() { return 'LPOP'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_ListPopLast extends Predis_InlineCommand {
+    public function getCommandId() { return 'RPOP'; }
+}
+
+/* commands operating on sets */
+class Predis_Compatibility_v1_0_Commands_SetAdd extends Predis_BulkCommand {
+    public function getCommandId() { return 'SADD'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetRemove extends Predis_BulkCommand {
+    public function getCommandId() { return 'SREM'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetPop  extends Predis_InlineCommand {
+    public function getCommandId() { return 'SPOP'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetMove extends Predis_BulkCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'SMOVE'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetCardinality extends Predis_InlineCommand {
+    public function getCommandId() { return 'SCARD'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetIsMember extends Predis_BulkCommand {
+    public function getCommandId() { return 'SISMEMBER'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetIntersection extends Predis_InlineCommand {
+    public function getCommandId() { return 'SINTER'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetIntersectionStore extends Predis_InlineCommand {
+    public function getCommandId() { return 'SINTERSTORE'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetUnion extends Predis_InlineCommand {
+    public function getCommandId() { return 'SUNION'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetUnionStore extends Predis_InlineCommand {
+    public function getCommandId() { return 'SUNIONSTORE'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetDifference extends Predis_InlineCommand {
+    public function getCommandId() { return 'SDIFF'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetDifferenceStore extends Predis_InlineCommand {
+    public function getCommandId() { return 'SDIFFSTORE'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetMembers extends Predis_InlineCommand {
+    public function getCommandId() { return 'SMEMBERS'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_SetRandomMember extends Predis_InlineCommand {
+    public function getCommandId() { return 'SRANDMEMBER'; }
+}
+
+/* multiple databases handling commands */
+class Predis_Compatibility_v1_0_Commands_SelectDatabase extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'SELECT'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_MoveKey extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'MOVE'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
+class Predis_Compatibility_v1_0_Commands_FlushDatabase extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'FLUSHDB'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_FlushAll extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'FLUSHALL'; }
+}
+
+/* sorting */
+class Predis_Compatibility_v1_0_Commands_Sort extends Predis_InlineCommand {
+    public function getCommandId() { return 'SORT'; }
+    public function filterArguments(Array $arguments) {
+        if (count($arguments) === 1) {
+            return $arguments;
+        }
+
+        // TODO: add more parameters checks
+        $query = array($arguments[0]);
+        $sortParams = $arguments[1];
+
+        if (isset($sortParams['by'])) {
+            $query[] = 'BY';
+            $query[] = $sortParams['by'];
+        }
+        if (isset($sortParams['get'])) {
+            $getargs = $sortParams['get'];
+            if (is_array($getargs)) {
+                foreach ($getargs as $getarg) {
+                    $query[] = 'GET';
+                    $query[] = $getarg;
+                }
+            }
+            else {
+                $query[] = 'GET';
+                $query[] = $getargs;
+            }
+        }
+        if (isset($sortParams['limit']) && is_array($sortParams['limit'])) {
+            $query[] = 'LIMIT';
+            $query[] = $sortParams['limit'][0];
+            $query[] = $sortParams['limit'][1];
+        }
+        if (isset($sortParams['sort'])) {
+            $query[] = strtoupper($sortParams['sort']);
+        }
+        if (isset($sortParams['alpha']) && $sortParams['alpha'] == true) {
+            $query[] = 'ALPHA';
+        }
+        if (isset($sortParams['store']) && $sortParams['store'] == true) {
+            $query[] = 'STORE';
+            $query[] = $sortParams['store'];
+        }
+
+        return $query;
+    }
+}
+
+/* persistence control commands */
+class Predis_Compatibility_v1_0_Commands_Save extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'SAVE'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_BackgroundSave extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'BGSAVE'; }
+    public function parseResponse($data) {
+        if ($data == 'Background saving started') {
+            return true;
+        }
+        return $data;
+    }
+}
+
+class Predis_Compatibility_v1_0_Commands_LastSave extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'LASTSAVE'; }
+}
+
+class Predis_Compatibility_v1_0_Commands_Shutdown extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'SHUTDOWN'; }
+    public function closesConnection() { return true; }
+}
+
+/* remote server control commands */
+class Predis_Compatibility_v1_0_Commands_Info extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'INFO'; }
+    public function parseResponse($data) {
+        $info      = array();
+        $infoLines = explode("\r\n", $data, -1);
+        foreach ($infoLines as $row) {
+            list($k, $v) = explode(':', $row);
+            if (!preg_match('/^db\d+$/', $k)) {
+                $info[$k] = $v;
+            }
+            else {
+                $db = array();
+                foreach (explode(',', $v) as $dbvar) {
+                    list($dbvk, $dbvv) = explode('=', $dbvar);
+                    $db[trim($dbvk)] = $dbvv;
+                }
+                $info[$k] = $db;
+            }
+        }
+        return $info;
+    }
+}
+
+class Predis_Compatibility_v1_0_Commands_SlaveOf extends Predis_InlineCommand {
+    public function canBeHashed()  { return false; }
+    public function getCommandId() { return 'SLAVEOF'; }
+    public function filterArguments(Array $arguments) {
+        if (count($arguments) === 0 || $arguments[0] === 'NO ONE') {
+            return array('NO', 'ONE');
+        }
+        return $arguments;
+    }
+}
+?>

+ 503 - 0
test/PredisClientFeatures.php

@@ -0,0 +1,503 @@
+<?php
+define('I_AM_AWARE_OF_THE_DESTRUCTIVE_POWER_OF_THIS_TEST_SUITE', false);
+
+require_once 'PHPUnit/Framework.php';
+require_once 'PredisShared.php';
+require_once '../lib/Predis_Compatibility.php';
+
+class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
+    public $redis;
+
+    protected function setUp() { 
+        $this->redis = RC::getConnection();
+        $this->redis->flushDatabase();
+    }
+
+    protected function tearDown() { 
+    }
+
+    protected function onNotSuccessfulTest(Exception $exception) {
+        // drops and reconnect to a redis server on uncaught exceptions
+        RC::resetConnection();
+        parent::onNotSuccessfulTest($exception);
+    }
+
+
+    /* ConnectionParameters */
+
+    function testConnectionParametersDefaultValues() {
+        $params = new Predis_ConnectionParameters();
+
+        $this->assertEquals(Predis_ConnectionParameters::DEFAULT_HOST, $params->host);
+        $this->assertEquals(Predis_ConnectionParameters::DEFAULT_PORT, $params->port);
+        $this->assertEquals(Predis_ConnectionParameters::DEFAULT_TIMEOUT, $params->connection_timeout);
+        $this->assertNull($params->read_write_timeout);
+        $this->assertNull($params->database);
+        $this->assertNull($params->password);
+        $this->assertNull($params->alias);
+    }
+
+    function testConnectionParametersSetupValuesArray() {
+        $paramsArray = RC::getConnectionParametersArgumentsArray();
+        $params = new Predis_ConnectionParameters($paramsArray);
+
+        $this->assertEquals($paramsArray['host'], $params->host);
+        $this->assertEquals($paramsArray['port'], $params->port);
+        $this->assertEquals($paramsArray['connection_timeout'], $params->connection_timeout);
+        $this->assertEquals($paramsArray['read_write_timeout'], $params->read_write_timeout);
+        $this->assertEquals($paramsArray['database'], $params->database);
+        $this->assertEquals($paramsArray['password'], $params->password);
+        $this->assertEquals($paramsArray['alias'], $params->alias);
+    }
+
+    function testConnectionParametersSetupValuesString() {
+        $paramsArray  = RC::getConnectionParametersArgumentsArray();
+        $paramsString = RC::getConnectionParametersArgumentsString($paramsArray);
+        $params = new Predis_ConnectionParameters($paramsArray);
+
+        $this->assertEquals($paramsArray['host'], $params->host);
+        $this->assertEquals($paramsArray['port'], $params->port);
+        $this->assertEquals($paramsArray['connection_timeout'], $params->connection_timeout);
+        $this->assertEquals($paramsArray['read_write_timeout'], $params->read_write_timeout);
+        $this->assertEquals($paramsArray['database'], $params->database);
+        $this->assertEquals($paramsArray['password'], $params->password);
+        $this->assertEquals($paramsArray['alias'], $params->alias);
+    }
+
+
+    /* Command and derivates */
+
+    function testCommand_TestArguments() {
+        $cmdArgs = array('key1', 'key2', 'key3');
+
+        $cmd = new Predis_Commands_GetMultiple();
+        $cmd->setArgumentsArray($cmdArgs);
+        $this->assertEquals($cmdArgs[0], $cmd->getArgument(0));
+        $this->assertEquals($cmdArgs[1], $cmd->getArgument(1));
+        $this->assertEquals($cmdArgs[2], $cmd->getArgument(2));
+
+        $cmd = new Predis_Commands_GetMultiple();
+        $cmd->setArguments('key1', 'key2', 'key3');
+        $this->assertEquals($cmdArgs[0], $cmd->getArgument(0));
+        $this->assertEquals($cmdArgs[1], $cmd->getArgument(1));
+        $this->assertEquals($cmdArgs[2], $cmd->getArgument(2));
+
+        $cmd = new Predis_Commands_Ping();
+        $this->assertNull($cmd->getArgument(0));
+    }
+
+    function testCommand_InlineWithNoArguments() {
+        $cmd = new Predis_Compatibility_v1_0_Commands_Ping();
+
+        $this->assertType('Predis_InlineCommand', $cmd);
+        $this->assertEquals('PING', $cmd->getCommandId());
+        $this->assertFalse($cmd->closesConnection());
+        $this->assertFalse($cmd->canBeHashed());
+        $this->assertNull($cmd->getHash(new Predis_Utilities_HashRing()));
+        $this->assertEquals("PING\r\n", $cmd->invoke());
+    }
+
+    function testCommand_InlineWithArguments() {
+        $cmd = new Predis_Compatibility_v1_0_Commands_Get();
+        $cmd->setArgumentsArray(array('key'));
+
+        $this->assertType('Predis_InlineCommand', $cmd);
+        $this->assertEquals('GET', $cmd->getCommandId());
+        $this->assertFalse($cmd->closesConnection());
+        $this->assertTrue($cmd->canBeHashed());
+        $this->assertNotNull($cmd->getHash(new Predis_Utilities_HashRing()));
+        $this->assertEquals("GET key\r\n", $cmd->invoke());
+    }
+
+    function testCommand_BulkWithArguments() {
+        $cmd = new Predis_Compatibility_v1_0_Commands_Set();
+        $cmd->setArgumentsArray(array('key', 'value'));
+
+        $this->assertType('Predis_BulkCommand', $cmd);
+        $this->assertEquals('SET', $cmd->getCommandId());
+        $this->assertFalse($cmd->closesConnection());
+        $this->assertTrue($cmd->canBeHashed());
+        $this->assertNotNull($cmd->getHash(new Predis_Utilities_HashRing()));
+        $this->assertEquals("SET key 5\r\nvalue\r\n", $cmd->invoke());
+    }
+
+    function testCommand_MultiBulkWithArguments() {
+        $cmd = new Predis_Commands_SetMultiple();
+        $cmd->setArgumentsArray(array('key1', 'value1', 'key2', 'value2'));
+
+        $this->assertType('Predis_MultiBulkCommand', $cmd);
+        $this->assertEquals('MSET', $cmd->getCommandId());
+        $this->assertFalse($cmd->closesConnection());
+        $this->assertFalse($cmd->canBeHashed());
+        $this->assertNull($cmd->getHash(new Predis_Utilities_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());
+    }
+
+    function testCommand_ParseResponse() {
+        // default parser
+        $cmd = new Predis_Commands_Get();
+        $this->assertEquals('test', $cmd->parseResponse('test'));
+
+        // overridden parser (boolean)
+        $cmd = new Predis_Commands_Exists();
+        $this->assertTrue($cmd->parseResponse('1'));
+        $this->assertFalse($cmd->parseResponse('0'));
+
+        // overridden parser (boolean)
+        $cmd = new Predis_Commands_Ping();
+        $this->assertTrue($cmd->parseResponse('PONG'));
+
+        // overridden parser (complex)
+        // TODO: emulate a respons to INFO
+    }
+
+
+    /* RedisServerProfile and derivates */
+
+    function testRedisServerProfile_GetSpecificVersions() {
+        $this->assertType('Predis_RedisServer_v1_0', Predis_RedisServerProfile::get('1.0'));
+        $this->assertType('Predis_RedisServer_v1_2', Predis_RedisServerProfile::get('1.2'));
+        $this->assertType('Predis_RedisServer_v2_0', Predis_RedisServerProfile::get('2.0'));
+        $this->assertType('Predis_RedisServer_vNext', Predis_RedisServerProfile::get('dev'));
+        $this->assertType('Predis_RedisServerProfile', Predis_RedisServerProfile::get('default'));
+        $this->assertEquals(Predis_RedisServerProfile::get('default'), Predis_RedisServerProfile::getDefault());
+    }
+
+    function testRedisServerProfile_SupportedCommands() {
+        $profile_10 = Predis_RedisServerProfile::get('1.0');
+        $profile_12 = Predis_RedisServerProfile::get('1.2');
+
+        $this->assertTrue($profile_10->supportsCommand('info'));
+        $this->assertTrue($profile_12->supportsCommand('info'));
+
+        $this->assertFalse($profile_10->supportsCommand('mset'));
+        $this->assertTrue($profile_12->supportsCommand('mset'));
+
+        $this->assertFalse($profile_10->supportsCommand('multi'));
+        $this->assertFalse($profile_12->supportsCommand('multi'));
+    }
+
+    function testRedisServerProfile_CommandsCreation() {
+        $profile = Predis_RedisServerProfile::get('1.0');
+
+        $cmdNoArgs = $profile->createCommand('info');
+        $this->assertType('Predis_Compatibility_v1_0_Commands_Info', $cmdNoArgs);
+        $this->assertNull($cmdNoArgs->getArgument());
+
+        $args = array('key1', 'key2');
+        $cmdWithArgs = $profile->createCommand('mget', $args);
+        $this->assertType('Predis_Compatibility_v1_0_Commands_GetMultiple', $cmdWithArgs);
+        $this->assertEquals($args[0], $cmdWithArgs->getArgument()); // TODO: why?
+        $this->assertEquals($args[0], $cmdWithArgs->getArgument(0));
+        $this->assertEquals($args[1], $cmdWithArgs->getArgument(1));
+
+        $bogusCommand    = 'not_existing_command';
+        $expectedMessage = "'$bogusCommand' is not a registered Redis command";
+        RC::testForClientException($this, $expectedMessage, p_anon("\$test", "
+            \$profile = Predis_RedisServerProfile::getDefault();
+            \$profile->createCommand('$bogusCommand');
+        "));
+    }
+
+    function testRedisServerProfile_CommandsRegistration() {
+        $profile  = Predis_RedisServerProfile::get('1.0');
+        $cmdId    = 'mset';
+        $cmdClass = 'Predis_Commands_SetMultiple';
+
+        $this->assertFalse($profile->supportsCommand($cmdId));
+        $profile->registerCommand(new $cmdClass(), $cmdId);
+        $this->assertTrue($profile->supportsCommand($cmdId));
+        $this->assertType($cmdClass, $profile->createCommand($cmdId));
+    }
+
+
+    /* ResponseQueued */
+
+    function testResponseQueued() {
+        $response = new Predis_ResponseQueued();
+        $this->assertTrue($response->queued);
+        $this->assertEquals(Predis_Protocol::QUEUED, (string)$response);
+    }
+
+
+    /* ResponseError */
+
+    function testResponseError() {
+        $errorMessage = 'ERROR MESSAGE';
+        $response = new Predis_ResponseError($errorMessage);
+
+        $this->assertTrue($response->error);
+        $this->assertEquals($errorMessage, $response->message);
+        $this->assertEquals($errorMessage, (string)$response);
+    }
+
+
+    /* Connection */
+
+    function testConnection_StringCastReturnsIPAndPort() {
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+        $this->assertEquals(RC::SERVER_HOST . ':' . RC::SERVER_PORT, (string) $connection);
+    }
+
+    function testConnection_ConnectDisconnect() {
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+
+        $this->assertFalse($connection->isConnected());
+        $connection->connect();
+        $this->assertTrue($connection->isConnected());
+        $connection->disconnect();
+        $this->assertFalse($connection->isConnected());
+    }
+
+    function testConnection_WriteAndReadCommand() {
+        $cmd = Predis_RedisServerProfile::getDefault()->createCommand('ping');
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+        $connection->connect();
+
+        $connection->writeCommand($cmd);
+        $this->assertTrue($connection->readResponse($cmd));
+    }
+
+    function testConnection_WriteCommandAndCloseConnection() {
+        $cmd = Predis_RedisServerProfile::getDefault()->createCommand('quit');
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+        $connection->connect();
+
+        $this->assertTrue($connection->isConnected());
+        $connection->writeCommand($cmd);
+        $expectedMessage = 'Error while reading line from the server';
+        $thrownException = null;
+        try {
+            $connection->readResponse($cmd);
+        }
+        catch (Predis_CommunicationException $exception) {
+            $thrownException = $exception;
+        }
+        $this->assertType('Predis_CommunicationException', $thrownException);
+        $this->assertEquals($expectedMessage, $thrownException->getMessage());
+        //$this->assertFalse($connection->isConnected());
+    }
+
+    function testConnection_GetSocketOpensConnection() {
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+
+        $this->assertFalse($connection->isConnected());
+        $this->assertType('resource', $connection->getSocket());
+        $this->assertTrue($connection->isConnected());
+    }
+
+    function testConnection_LazyConnect() {
+        $cmd = Predis_RedisServerProfile::getDefault()->createCommand('ping');
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+
+        $this->assertFalse($connection->isConnected());
+        $connection->writeCommand($cmd);
+        $this->assertTrue($connection->isConnected());
+        $this->assertTrue($connection->readResponse($cmd));
+    }
+
+    function testConnection_RawCommand() {
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+        $this->assertEquals('PONG', $connection->rawCommand("PING\r\n"));
+    }
+
+    function testConnection_Alias() {
+        $connection1 = new Predis_Connection(RC::getConnectionParameters());
+        $this->assertNull($connection1->getParameters()->alias);
+
+        $args = array_merge(RC::getConnectionArguments(), array('alias' => 'servername'));
+        $connection2 = new Predis_Connection(new Predis_ConnectionParameters($args));
+        $this->assertEquals('servername', $connection2->getParameters()->alias);
+    }
+
+    function testConnection_ConnectionTimeout() {
+        $timeout = 3;
+        $args    = array('host' => '1.0.0.1', 'connection_timeout' => $timeout);
+        $connection = new Predis_Connection(new Predis_ConnectionParameters($args));
+
+        $start = time();
+        $thrownException = null;
+        try {
+            $connection->connect();
+        }
+        catch (Predis_CommunicationException $exception) {
+            $thrownException = $exception;
+        }
+        $this->assertType('Predis_CommunicationException', $thrownException);
+        $this->assertEquals((float)(time() - $start), $timeout, '', 1);
+    }
+
+    function testConnection_ReadTimeout() {
+        $timeout = 1;
+        $args    = array_merge(RC::getConnectionArguments(), array('read_write_timeout' => $timeout));
+        $cmdFake = Predis_RedisServerProfile::getDefault()->createCommand('ping');
+        $connection = new Predis_Connection(new Predis_ConnectionParameters($args));
+
+        $expectedMessage = 'Error while reading line from the server';
+        $start = time();
+        $thrownException = null;
+        try {
+            $connection->readResponse($cmdFake);
+        }
+        catch (Predis_CommunicationException $exception) {
+            $thrownException = $exception;
+        }
+        $this->assertType('Predis_CommunicationException', $thrownException);
+        $this->assertEquals($expectedMessage, $thrownException->getMessage());
+        $this->assertEquals((float)(time() - $start), $timeout, '', 1);
+    }
+
+
+    /* ResponseReader */
+
+    function testResponseReader_OptionIterableMultiBulkReplies() {
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+        $responseReader = $connection->getResponseReader();
+
+        $responseReader->setHandler(
+            Predis_Protocol::PREFIX_MULTI_BULK, 
+            new Predis_ResponseMultiBulkHandler()
+        );
+        $this->assertType('array', $connection->rawCommand("KEYS *\r\n"));
+
+        $responseReader->setHandler(
+            Predis_Protocol::PREFIX_MULTI_BULK, 
+            new Predis_ResponseMultiBulkStreamHandler()
+        );
+        $this->assertType('Iterator', $connection->rawCommand("KEYS *\r\n"));
+    }
+
+    function testResponseReader_OptionExceptionOnError() {
+        $connection = new Predis_Connection(RC::getConnectionParameters());
+        $responseReader = $connection->getResponseReader();
+        $connection->rawCommand("SET key 5\r\nvalue\r\n");
+        $rawCmdUnexpected = "LPUSH key 5\r\nvalue\r\n";
+
+        $responseReader->setHandler(
+            Predis_Protocol::PREFIX_ERROR,  
+            new Predis_ResponseErrorSilentHandler()
+        );
+        $errorReply = $connection->rawCommand($rawCmdUnexpected);
+        $this->assertType('Predis_ResponseError', $errorReply);
+        $this->assertEquals(RC::EXCEPTION_WRONG_TYPE, $errorReply->message);
+
+        $responseReader->setHandler(
+            Predis_Protocol::PREFIX_ERROR, 
+            new Predis_ResponseErrorHandler()
+        );
+        $thrownException = null;
+        try {
+            $connection->rawCommand($rawCmdUnexpected);
+        }
+        catch (Predis_ServerException $exception) {
+            $thrownException = $exception;
+        }
+        $this->assertType('Predis_ServerException', $thrownException);
+        $this->assertEquals(RC::EXCEPTION_WRONG_TYPE, $thrownException->getMessage());
+    }
+
+
+    /* Client + CommandPipeline */
+
+    function testCommandPipeline_Simple() {
+        $client = RC::getConnection();
+        $client->flushdb();
+
+        $pipe = $client->pipeline();
+
+        $this->assertType('Predis_CommandPipeline', $pipe);
+        $this->assertType('Predis_CommandPipeline', $pipe->set('foo', 'bar'));
+        $this->assertType('Predis_CommandPipeline', $pipe->set('hoge', 'piyo'));
+        $this->assertType('Predis_CommandPipeline', $pipe->mset(array(
+            'foofoo' => 'barbar', 'hogehoge' => 'piyopiyo'
+        )));
+        $this->assertType('Predis_CommandPipeline', $pipe->mget(array(
+            'foo', 'hoge', 'foofoo', 'hogehoge'
+        )));
+
+        $replies = $pipe->execute();
+        $this->assertType('array', $replies);
+        $this->assertEquals(4, count($replies));
+        $this->assertEquals(4, count($replies[3]));
+        $this->assertEquals('barbar', $replies[3][2]);
+    }
+
+    function testCommandPipeline_FluentInterface() {
+        $client = RC::getConnection();
+        $client->flushdb();
+
+        $replies = $client->pipeline()->ping()->set('foo', 'bar')->get('foo')->execute();
+        $this->assertType('array', $replies);
+        $this->assertEquals('bar', $replies[2]);
+    }
+
+    function testCommandPipeline_CallableAnonymousBlock() {
+        $client = RC::getConnection();
+        $client->flushdb();
+
+        $replies = $client->pipeline(p_anon("\$pipe", "
+            \$pipe->ping();
+            \$pipe->set('foo', 'bar');
+            \$pipe->get('foo');
+        "));
+
+        $this->assertType('array', $replies);
+        $this->assertEquals('bar', $replies[2]);
+    }
+
+    function testCommandPipeline_ClientExceptionInCallableBlock() {
+        $client = RC::getConnection();
+        $client->flushdb();
+
+        $expectedMessage = 'TEST';
+        $thrownException = null;
+        try {
+            $client->pipeline(p_anon("\$pipe", "
+                \$pipe->ping();
+                \$pipe->set('foo', 'bar');
+                throw new Predis_ClientException('$expectedMessage');
+            "));
+        }
+        catch (Predis_ClientException $exception) {
+            $thrownException = $exception;
+        }
+        $this->assertType('Predis_ClientException', $thrownException);
+        $this->assertEquals($expectedMessage, $thrownException->getMessage());
+
+        $this->assertFalse($client->exists('foo'));
+    }
+
+    function testCommandPipeline_ServerExceptionInCallableBlock() {
+        $client = RC::getConnection();
+        $client->flushdb();
+        $client->getResponseReader()->setHandler('-', new Predis_ResponseErrorSilentHandler());
+
+        $replies = $client->pipeline(p_anon("\$pipe", "
+            \$pipe->set('foo', 'bar');
+            \$pipe->lpush('foo', 'piyo'); // LIST operation on STRING type returns an ERROR
+            \$pipe->set('hoge', 'piyo');
+        "));
+
+        $this->assertType('array', $replies);
+        $this->assertType('Predis_ResponseError', $replies[1]);
+        $this->assertTrue($client->exists('foo'));
+        $this->assertTrue($client->exists('hoge'));
+    }
+
+    function testCommandPipeline_Flush() {
+        $client = RC::getConnection();
+        $client->flushdb();
+
+        $pipe = $client->pipeline();
+        $pipe->set('foo', 'bar')->set('hoge', 'piyo');
+        $pipe->flushPipeline();
+        $pipe->ping()->mget(array('foo', 'hoge'));
+        $replies = $pipe->execute();
+
+        $this->assertType('array', $replies);
+        $this->assertEquals(4, count($replies));
+        $this->assertEquals('bar', $replies[3][0]);
+        $this->assertEquals('piyo', $replies[3][1]);
+    }
+}
+?>

+ 66 - 9
test/PredisShared.php

@@ -25,15 +25,26 @@ class RC {
     const EXCEPTION_NO_SUCH_KEY    = 'no such key';
     const EXCEPTION_OUT_OF_RANGE   = 'index out of range';
     const EXCEPTION_INVALID_DB_IDX = 'invalid DB index';
+    const EXCEPTION_VALUE_NOT_INT  = 'value is not an integer';
     const EXCEPTION_EXEC_NO_MULTI  = 'EXEC without MULTI';
+    const EXCEPTION_SETEX_TTL      = 'invalid expire time in SETEX';
+    const EXCEPTION_HASH_VALNOTINT = 'hash value is not an integer';
 
     private static $_connection;
 
+    public static function getConnectionArguments() { 
+        return array('host' => RC::SERVER_HOST, 'port' => RC::SERVER_PORT);
+    }
+
+    public static function getConnectionParameters() { 
+        return new Predis_ConnectionParameters(array('host' => RC::SERVER_HOST, 'port' => RC::SERVER_PORT));
+    }
+
     private static function createConnection() {
         $serverProfile = Predis_RedisServerProfile::get('dev');
-        $connection = new Predis_Client(array('host' => RC::SERVER_HOST, 'port' => RC::SERVER_PORT), $serverProfile);
+        $connection = new Predis_Client(RC::getConnectionArguments(), $serverProfile);
         $connection->connect();
-        $connection->selectDatabase(RC::DEFAULT_DATABASE);
+        $connection->select(RC::DEFAULT_DATABASE);
         return $connection;
     }
 
@@ -109,25 +120,55 @@ class RC {
             $thrownException = $exception;
         }
         $testcaseInstance->assertType('Predis_ServerException', $thrownException);
-        $testcaseInstance->assertEquals($expectedMessage, $thrownException->getMessage());
+        if (isset($expectedMessage)) {
+            $testcaseInstance->assertEquals($expectedMessage, $thrownException->getMessage());
+        }
+    }
+
+    public static function testForClientException($testcaseInstance, $expectedMessage, $wrapFunction) {
+        $thrownException = null;
+        try {
+            $wrapFunction($testcaseInstance);
+        }
+        catch (Predis_ClientException $exception) {
+            $thrownException = $exception;
+        }
+        $testcaseInstance->assertType('Predis_ClientException', $thrownException);
+        if (isset($expectedMessage)) {
+            $testcaseInstance->assertEquals($expectedMessage, $thrownException->getMessage());
+        }
+    }
+
+    public static function testForCommunicationException($testcaseInstance, $expectedMessage, $wrapFunction) {
+        $thrownException = null;
+        try {
+            $wrapFunction($testcaseInstance);
+        }
+        catch (Predis_CommunicationException $exception) {
+            $thrownException = $exception;
+        }
+        $testcaseInstance->assertType('Predis_CommunicationException', $thrownException);
+        if (isset($expectedMessage)) {
+            $testcaseInstance->assertEquals($expectedMessage, $thrownException->getMessage());
+        }
     }
 
     public static function pushTailAndReturn(Predis_Client $client, $keyName, Array $values, $wipeOut = 0) {
         if ($wipeOut == true) {
-            $client->delete($keyName);
+            $client->del($keyName);
         }
         foreach ($values as $value) {
-            $client->pushTail($keyName, $value);
+            $client->rpush($keyName, $value);
         }
         return $values;
     }
 
     public static function setAddAndReturn(Predis_Client $client, $keyName, Array $values, $wipeOut = 0) {
         if ($wipeOut == true) {
-            $client->delete($keyName);
+            $client->del($keyName);
         }
         foreach ($values as $value) {
-            $client->setAdd($keyName, $value);
+            $client->sadd($keyName, $value);
         }
         return $values;
     }
@@ -135,12 +176,28 @@ class RC {
     public static function zsetAddAndReturn(Predis_Client $client, $keyName, Array $values, $wipeOut = 0) {
         // $values: array(SCORE => VALUE, ...);
         if ($wipeOut == true) {
-            $client->delete($keyName);
+            $client->del($keyName);
         }
         foreach ($values as $value => $score) {
-            $client->zsetAdd($keyName, $score, $value);
+            $client->zadd($keyName, $score, $value);
         }
         return $values;
     }
+
+    public static function getConnectionParametersArgumentsArray() {
+        return array(
+            'host' => '10.0.0.1', 'port' => 6380, 'connection_timeout' => 10, 'read_write_timeout' => 30, 
+            'database' => 5, 'password' => 'dbpassword', 'alias' => 'connection_alias'
+        );
+    }
+
+    public static function getConnectionParametersArgumentsString($arguments = null) {
+        // TODO: must be improved
+        $args = $arguments !== null ? $arguments : RC::getConnectionParametersArgumentsArray();
+        $paramsString = "redis://{$args['host']}:{$args['port']}/";
+        $paramsString .= "?connection_timeout={$args['connection_timeout']}&read_write_timeout={$args['read_write_timeout']}";
+        $paramsString .= "&database={$args['database']}&password={$args['password']}&alias={$args['alias']}";
+        return $paramsString;
+    }
 }
 ?>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 290 - 220
test/RedisCommandsTest.php


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است