Compare commits

..

164 Commits

Author SHA1 Message Date
Daniele Alessandri 495e3c6f8a Bump VERSION and update CHANGELOG.
[ci skip]
2013-01-19 11:18:58 +01:00
Daniele Alessandri 3465bc7c1f Add PHP 5.5 to the CI build matrix and switch to branch blacklisting. 2013-01-19 11:16:20 +01:00
Daniele Alessandri 8bf896f81f Update CHANGELOG. 2013-01-19 09:56:51 +01:00
Daniele Alessandri 0085bc08ae Fix CHANGELOG. 2013-01-19 09:46:58 +01:00
Daniele Alessandri f7232b9ddc Highlight PHP code in FAQ. 2013-01-19 09:44:14 +01:00
Daniele Alessandri 95cc528904 Remove <?php tags from README.
It seems like GitHub can now highlight PHP syntax without those ones.
2013-01-19 09:44:09 +01:00
Daniele Alessandri 4a16cb9417 Update .gitignore.
[ci skip]
2013-01-13 13:54:09 +01:00
Daniele Alessandri 94758d5f58 Update CHANGELOG.
[ci skip]
2013-01-13 13:53:22 +01:00
Daniele Alessandri acc0f04bdb Add missing param to phpdoc entry.
[ci skip]
2013-01-13 13:48:50 +01:00
Daniele Alessandri 09de7be7cb Add optional callable to drive extraction of node hash in distributor.
This is mainly in response to the longstanding issue #36 in which my
proposed solution was fine in terms of functionalities, but eventually
never made into the repository since it was far from being clean enough
for my taste.

Now developers can optionally pass a callable object when creating the
hashring instance to decide how the distributor should extract the hash
from a node (really a connection instance) to populate the ring:

  use Predis\Cluster\Distribution\HashRing;
  use Predis\Connection\PredisCluster;

  $servers = array(
    'tcp://10.0.0.1?alias=node01',
    'tcp://10.0.0.2?alias=node02',
  );

  $options = array(
    'nodehash' => function ($connection) {
      return $connection->getParameters()->alias;
    },
    'cluster' => function ($options) {
      $replicas = HashRing::DEFAULT_REPLICAS;
      $hashring = new HashRing($replicas, $options->nodehash);
      $cluster  = new PredisCluster($hashring);

      return $cluster;
    },
  );

  $client = new Predis\Client($servers, $options);

Both HashRing and KetamaPureRing in the Predis\Cluster\Distribution
namespace support this new approach.
2013-01-13 13:10:01 +01:00
Daniele Alessandri 70b279bbdc Bump year in LICENSE.
On a related note, happy new year!

[ci skip]
2013-01-01 13:58:18 +01:00
Daniele Alessandri f8cbbc5b8f Update URL for phpiredis.
Ownership of the repository has been transferred. Thanks again to
@seppo0010, the original author of phpiredis, for his work on the
extension!

[ci skip]
2012-12-28 20:51:34 +01:00
Daniele Alessandri 83720075f3 Make it possible to cast key prefix processor instance to string.
Mostly useful with string interpolation when getting the current prefix
associated to a client instance.
2012-12-27 10:50:25 +01:00
Daniele Alessandri 9901233fa1 Instantiate the current class when subclassing Client.
Previously the getClientFor() method in a subclass of Predis\Client
returned an instance of Predis\Client instead of a new instance of
the subclass. The new behaviour is more correct.
2012-12-24 14:50:07 +01:00
Daniele Alessandri dcb4093046 Update CHANGELOG. 2012-12-15 17:46:00 +01:00
Daniele Alessandri 1e8af648cb [tests] Add missing tests for Predis\Option\ClientReplication. 2012-12-15 17:22:10 +01:00
Daniele Alessandri ff5e3515c3 Implement callable initializer for "connections" client option. 2012-12-15 17:12:37 +01:00
Daniele Alessandri 59813cd74e Pass option handler instance to callable initializers.
This is just a convention implemented to client options supporting
callable initializers such as "profile", "cluster" and "replication".

This is useful to get a fully-initialized default value and perform
additional operations before returning it. An example with "profile":

  $options = array(
    'commands' => array(
      'test1' => 'Predis\Command\ConnectionEcho',
      'test2' => 'Predis\Command\ConnectionEcho',
    ),
    'profile'  => function ($options, $option) {
      $profile = $option->getDefault($options);

      if (is_array($options->commands)) {
        foreach ($options->commands as $id => $cmd) {
          $profile->defineCommand($id, $cmd);
        }
      }

      return $profile;
    },
  );
2012-12-15 16:47:32 +01:00
Daniele Alessandri bfd96b15dc Fix response parsing with scripted commands after -NOSCRIPT.
This commit fixes issue #94.
2012-12-12 14:28:06 +01:00
Daniele Alessandri 51b466267d Fix missing use directives.
This miss caused a PHP error when Redis did not return a +QUEUED
reply inside a MULTI / EXEC command.
2012-11-21 22:57:23 +01:00
Daniele Alessandri 78f3596566 Merge branch 'tests/update-redis-2.8' into v0.8 2012-11-21 22:13:42 +01:00
Daniele Alessandri ac01c1a17f [tests] TTL and PTTL return -2 on non existing keys in Redis >= 2.8. 2012-11-21 22:09:05 +01:00
Daniele Alessandri 46920c9c77 [tests] Fix expected exception message on wrong key type.
Redis >= 2.8 returns -WRONGTYPE errors instead of -ERR when executing
operations on wrong key type (such as trying to LPUSH on a string key).

Luckily for us, phpunit's @expectedExceptionMessage annotation actually
does not perform an exact match but works on a substring so we just omit
the initial part of the exception message to make the test work.
2012-11-21 21:49:49 +01:00
Daniele Alessandri b26d6103a6 Avoid channels reordering issues when testing UNSUBSCRIBE.
When using UNSUBSCRIBE without a list of channels (which actually means
"unsubscribe from all the subscribed channels") Redis 2.6 does not
guarantee that channels are returned following the same order of
subscription.
2012-11-13 16:44:55 +01:00
Daniele Alessandri a4760d65cd Bump Redis version used in tests on Travis CI.
It seems like Travis CI switched silently to Redis 2.6, which means
that we can enable the full test suite of Predis.
2012-11-13 16:13:07 +01:00
Daniele Alessandri 49aa6f0aca Update performance numbers in the related FAQ section.
We use newer version of PHP, phpiredis, phpredis, Redis and Ubuntu.

[ci skip]
2012-11-08 13:06:24 +01:00
Daniele Alessandri 292f9939b2 Tweak CONTRIBUTING instructions.
[ci skip]
2012-11-03 16:02:43 +01:00
Daniele Alessandri daa36a980d Make Predis\Connection\PhpiredisConnection::write() protected.
We should actually make use of more protected methods in this
class instead of the private ones, this is most likely going
to be addressed in future commits.

See also issue #86 on GitHub for the reason behind this change.
2012-11-03 15:21:52 +01:00
Daniele Alessandri 0b09d2c359 Back to development for the next patch release. 2012-11-03 15:16:01 +01:00
Daniele Alessandri 0a622aee31 Bump VERSION and update CHANGELOG.
Guess what time is it? Yeah, once again... it is release time!
2012-10-23 01:46:05 +02:00
Daniele Alessandri 040dfebd9c Minimum required version of PHP is 5.3.2. 2012-10-23 01:35:22 +02:00
Daniele Alessandri 0e4604a9bb Tweak README. 2012-10-23 01:34:48 +02:00
Daniele Alessandri deb64c295a Add notes about coding standards in CONTRIBUTING.
[ci skip]
2012-10-12 22:43:45 +02:00
Daniele Alessandri aac06a4cd8 Move contributing section from README to CONTRIBUTING.
[ci skip]
2012-10-12 22:32:21 +02:00
Daniele Alessandri dfb5cf2c1b Remove TODO.
We are going to open a new issue on GitHub for a decent
documentation of the library.

[ci skip]
2012-10-12 21:57:50 +02:00
Daniele Alessandri a1024bba53 Merge FAQ files into a single markdown document.
[ci skip]
2012-10-12 21:51:54 +02:00
Daniele Alessandri f357cafb14 Update CHANGELOG.
[ci skip]
2012-10-12 21:39:05 +02:00
Daniele Alessandri 2b4cf80d90 Merge remote-tracking branch 'origin/pr/84' 2012-09-18 18:05:11 +02:00
Ondrej -onovy- Novy 6cd1f03834 Fixed typos. 2012-09-18 17:43:29 +02:00
Daniele Alessandri 732d7f96d1 Extract a strategy class from the replication connection. 2012-09-12 13:09:52 +02:00
Daniele Alessandri b4e1ab106c Update README.
[ci skip]
2012-09-10 14:17:00 +02:00
Daniele Alessandri 5a2c61c02b Update CHANGELOG.
[ci skip]
2012-09-04 18:32:16 +02:00
Daniele Alessandri 1b9e10bdd8 Move command response parsing to the client class.
Connection classes should just handle, convert and return simple Redis
types while parsing and transforming structured replies should be done
by consumers (see Predis\Client or Predis\Transaction\MultiExecContext).

This actually makes more sense considering that parsing a complex response
with the associated command parser may require different actions. As an
example, the result of EXEC is a multibulk that holds the actual responses,
so we really need to parse each one of its elements and we should also
make sure that iterable multibulks are consumed. We already did that
previously, but it was weird knowing that command parsers were applied
by the connection class.

This also moves some duplicated logic away from each connection class
implementation which is a nice bonus.
2012-09-04 14:53:05 +02:00
Daniele Alessandri 37e2b546b0 Fix MULTI / EXEC pipeline executor when using iterable multibulks. 2012-09-04 13:19:26 +02:00
Daniele Alessandri f163e1221b Describe scripted commands (EVAL / EVALSHA) in the README.
[ci skip]
2012-09-04 13:19:18 +02:00
Daniele Alessandri 1bb2ab95ef Use the proper service name.
[ci skip]
2012-09-03 19:52:25 +02:00
Daniele Alessandri ea18afc820 Update the configuration file for Travis CI.
Travis is upgrading their base image and will disable most of
the services at boot (Redis is one of these).
2012-09-03 14:39:11 +02:00
Daniele Alessandri baa91da35b Merge remote-tracking branch 'origin/pr/79' 2012-09-03 14:37:46 +02:00
Pascal Borreli 6d85a81da4 Fixed typo 2012-09-03 11:50:26 +00:00
Daniele Alessandri f913e81b9f Add a reference to Predis\Async in the README.
I think we can indeed start promoting Predis\Async now that it has
reached a fairly stable state (in terms of API and functionalites)
despite still being considered experimental.

[ci skip]
2012-09-03 12:27:45 +02:00
Daniele Alessandri 003b3d180d Add missing test for Predis\Client::getClientFor(). 2012-08-24 12:13:37 +02:00
Daniele Alessandri a6e1ac11d0 Implement separate Predis\Client::getConnectionById().
This method is not part of Predis\ClientInterface because it is being
considered mostly an helper / utility method.
2012-08-24 12:07:56 +02:00
Daniele Alessandri be46ab813c Remove useless default NULL value in method arguments. 2012-08-24 11:55:19 +02:00
Daniele Alessandri 6918de6e4b Restore 0x notation for CRC16 hash generator. 2012-08-24 11:51:00 +02:00
Daniele Alessandri cfa97dd9f5 Fix cs. 2012-08-24 11:49:43 +02:00
Daniele Alessandri 1e8f790ee4 Remove leftover method. 2012-08-24 11:48:42 +02:00
Daniele Alessandri c655affe5f Add Predis\Command\CommandInterface::setRawArguments(). 2012-08-24 11:47:44 +02:00
Daniele Alessandri 457fd6076d CS fixes.
[ci skip]
2012-08-22 11:54:49 +02:00
Daniele Alessandri 282287765b Fix phpdoc comment.
[ci skip]
2012-08-22 11:53:16 +02:00
Daniele Alessandri 00d6160549 Enable code coverage report on Travis CI. 2012-08-22 11:46:31 +02:00
Daniele Alessandri 576163724c Add an example for multibulk reply iterators.
[ci skip]
2012-08-21 18:10:49 +02:00
Daniele Alessandri 9df9dbdedc Promote multibulk response iterators as Predis response objects.
Multibulk response iterators will not be passed anymore to the response
parser method of the command that generated the response. Pipeline and
transaction abstractions still consume interators returned as response
items.
2012-08-21 17:15:14 +02:00
Daniele Alessandri 8d7f5099c4 Add tests for Predis\Connection\RedisCluster (redis-cluster). 2012-08-21 15:24:06 +02:00
Daniele Alessandri e8daf45269 Improve Predis\Pipeline\MultiExecExecutor. 2012-08-19 15:00:45 +02:00
Daniele Alessandri 8567b05459 Specify the server profile in constructor of the connection factory.
When no server profile is specified, the connection factory will not push
any initialization command to the newly created connection object.

This change is mainly useful when using redis-cluster and also makes it
possible to easily inject commands such as "SCRIPT LOAD" at initialization
time by grouping everything into one common place simply by extending the
connection factory class.
2012-08-19 13:57:58 +02:00
Daniele Alessandri 6e76ea6c7e Add tests for classes in the Predis\Protocol\Text namespace.
This was the only namespace containing classes with no tests.
2012-08-02 21:42:11 +02:00
Daniele Alessandri e774a301c4 Add tests for Predis\Connection\ComposableStreamConnection. 2012-08-02 17:25:13 +02:00
Daniele Alessandri 2b85824d74 Apply minor fixes to README.
[ci skip]
2012-08-02 16:15:31 +02:00
Daniele Alessandri 81422b9992 Partially rewrite README.
[ci skip]
2012-08-02 16:10:58 +02:00
Daniele Alessandri c230b6e831 Update TODO.
[ci skip]
2012-08-02 16:10:47 +02:00
Daniele Alessandri 69d4c013f7 Minor change (mostly micro-optimization). 2012-08-02 12:42:53 +02:00
Daniele Alessandri 1ac9cc9c71 Support EVAL and EVALSHA with redis-cluster.
We extract the keys from commands using the second argument of EVAL /
EVALSHA which specifies the number of arguments that must be treated
as keys (used to populate the KEYS table in the Lua script) and then
we check if there is only one key since redis-cluster right now does
not support multi-keys requests.

Our scripted command abstraction is also supported.
2012-08-02 12:08:56 +02:00
Daniele Alessandri b2f59a0c63 Support EVAL and EVALSHA with client-side sharding (Predis cluster).
We extract the keys from commands using the second argument of EVAL /
EVALSHA which specifies the number of arguments that must be treated
as keys (used to populate the KEYS table in the Lua script) and then
we check if all the keys generate the same hash using the usual method.

Our scripted command abstraction is also supported.

It comes without saying that accessing or setting keys from within the
Lua script is something that might not work as expected, so you should
be careful when using EVAL and EVALSHA in the context of client-side
sharding.
2012-08-02 11:38:20 +02:00
Daniele Alessandri 93971bcb45 Merge branch 'command-hash-strategy'
Conflicts:
	lib/Predis/Connection/RedisCluster.php
2012-07-30 20:29:13 +02:00
Daniele Alessandri e54bc31212 Create Predis\Cluster namespace and move related classes / interfaces. 2012-07-30 20:10:14 +02:00
Daniele Alessandri b9eddae96f Fix and optimize node guessing algorithm. 2012-07-30 16:40:53 +02:00
Daniele Alessandri 7683f97ff2 Move the hash generator inside the command hash strategy class.
This should make things a tiny bit faster (we are speaking about micro
optimizations anyway) but more  importantly it results in a better
encapsulation.
2012-07-30 15:42:45 +02:00
Daniele Alessandri f4f9d02551 Improve the slot-guessing algorithm.
This change makes it almost useless to pre-assign slots using connection
parameters, but the functionality will be kept for special cases.
2012-07-30 12:55:41 +02:00
Daniele Alessandri bacdb7ff62 Remove useless "use" directives. 2012-07-29 22:30:31 +02:00
Daniele Alessandri e0c6ad58be Optimize lazy initialization of the slots map.
This is twice faster than before but still kind of slow compared to the
initialization of a cluster with no slots pre-assigned to connections.
2012-07-29 22:07:00 +02:00
Daniele Alessandri febd99421f Pre-associate connections to slot ranges using connection parameters.
This makes it possible to easily assign a connection to a slot range
using the key "slots" in connection parameters like in the following
example:

    $parameters = array(
        'tcp://127.0.0.1:6379?slots=0-1364',
        'tcp://127.0.0.1:6380?slots=1365-2729',
        'tcp://127.0.0.1:6381?slots=2730-4095',
    );

    $options = array('cluster' => 'redis');
    $client = new Predis\Client($parameters, $options);

It is possible to get the full list of slot ranges for each node of
the cluster using redis-cli connected to one of the nodes:

    ./redis-cli -h 127.0.0.1 -p 6379 CLUSTER NODES

The last column in the resulting output contains the slots assigned
to each instance participating to the redis cluster.
2012-07-29 19:38:58 +02:00
Daniele Alessandri 8559c2cb30 Make it possible to pre-associate slots to a connection.
This is useful to avoid guessing the correct server instance at runtime
thus reducing the overhead of a guess-and-try approach since relying on
-ASK or -MOVED responses returned by a node is a costly operation.

For now, you can pre-associate slots using the "cluster" client option:

    $parameters = array(
        'tcp://127.0.0.1:6379',
        'tcp://127.0.0.1:6380',
        'tcp://127.0.0.1:6381',
    );

    $options = array(
        'cluster' => function ($options) {
            $cluster = new Predis\Connection\RedisCluster();

            $cluster->setSlots(0,    1364, '127.0.0.1:6379');
            $cluster->setSlots(1365, 2729, '127.0.0.1:6380');
            $cluster->setSlots(2730, 4095, '127.0.0.1:6381');

            return $cluster;
        },
    );

    $client = new Predis\Client($parameters, $options);

In the future we will make the slots configuration easier by fetching
the configuration from a node using the command "CLUSTER NODES" or by
pre-computing the slices of slots associated to a node when adding it
to the cluster connection object.
2012-07-29 14:14:11 +02:00
Daniele Alessandri 2ba54a0e1c Use specific command hash strategy for redis-cluster.
This is needed because redis-cluster does not support the same commands
or operations that can be performed with our client-side managed predis
cluster.

For example redis-cluster does not support key tagging (that is, parts
of a key enclosed by {...} to hash only that specific part of a key)
and multiple-key operations suchs as MGET, MSET, SDIFF, SUNION or SINTER.
Some multiple-key operations can be performed anyway if the command has
only one key (e.g. "MGET foo" and "MSET foo bar" will not fail).
2012-07-29 13:48:16 +02:00
Daniele Alessandri 024a31d571 Fix wrong binary notation.
Cannot use 0b yet since it is PHP 5.4 only.
2012-07-29 13:33:06 +02:00
Daniele Alessandri d0572b0efc Rename command hash strategy cass used by Predis cluster. 2012-07-29 12:07:48 +02:00
Daniele Alessandri 0f80cb55bb Add suggested extensions to composer.json.
[ci skip]
2012-07-27 16:58:51 +02:00
Daniele Alessandri 55a3fd65a2 More code styling changes. 2012-07-27 12:23:25 +02:00
Daniele Alessandri 6c732c737b Order "use" directives by namespace depth and name.
This is not really useful per se, but at least we use a fixed rule.
2012-07-26 23:04:20 +02:00
Daniele Alessandri 559853bdd5 Move some classes and interfaces in other namespaces. 2012-07-26 22:37:31 +02:00
Daniele Alessandri 28e1e7ad25 Reword some exception messages. 2012-07-26 16:31:09 +02:00
Daniele Alessandri 98112fdb39 Use instance methods to cast values for connection parameters. 2012-07-26 15:49:42 +02:00
Daniele Alessandri 0e8d978348 Minor code styling changes in client class.
[ci skip]
2012-07-26 15:18:09 +02:00
Daniele Alessandri 469f3cc68b Do not accept string or Predis\Profile\ServerProfileInstance in constructor.
To specify a server profile you can just set the "prefix" option in the
client options array passed to the constructor of Predis\Client.
2012-07-26 14:36:01 +02:00
Daniele Alessandri 36f8210864 Rename variable and field. 2012-07-26 12:18:28 +02:00
Daniele Alessandri 81be513eaa Change signature of Predis\Pipeline\PipelineContext constructor.
Now the second argument is a Predis\Pipeline\PipelineExecutorInterface.
2012-07-26 11:33:24 +02:00
Daniele Alessandri 4b73afc316 Add missing tests for Predis\Command\Hash\CommandHashStrategy. 2012-07-25 22:41:14 +02:00
Daniele Alessandri fd6a14d80b Make it possible to set/unset command handlers in CommandHashStrategy. 2012-07-25 22:22:55 +02:00
Daniele Alessandri f2d92f1e29 Expose the command hash strategy from the predis cluster class.
This will be useful to add, remove or change the handler used to
extract, validate and hash the keys of each command instance.
2012-07-25 21:25:50 +02:00
Daniele Alessandri c3cafaa9a6 Remove useless helpers. 2012-07-25 21:18:58 +02:00
Daniele Alessandri 89d6c0e9b6 Extract common PubSub handling logic into a separate abstract class. 2012-07-25 21:07:04 +02:00
Daniele Alessandri 9f51263136 Fix internal flags for MULTI / EXEC and PUB/SUB contexts.
Do not ask me why I was using 0x (the prefix for hexadecimal notation)
when what we really need here is 0b (the prefix for binary notation).
Really, everything was working by sheer coincidence.

Since 0b has been added in PHP 5.4, we rely on plain decimal numbers
to define our constants for now.
2012-07-21 13:48:32 +02:00
Daniele Alessandri a75e52cf5a Update CHANGELOG.
[ci skip]
2012-06-01 12:14:50 +02:00
Irakli Nadareishvili b865cee1f8 Make autoloader act nicer to autoloaders that may be running in parallel. 2012-05-27 21:09:38 +02:00
Daniele Alessandri 1f0be85a3d Add initial support for Redis cluster (Redis v3.0).
This implementation is capable of handling "ASK" and "MOVED" replies
returned by Redis when one of the nodes asks the client for temporary
or permanent redirects of a slot to a different node.

Performances look almost on par with our client-side sharding solution
and everything looks already relatively stable.

Tests are still missing.
2012-05-13 11:13:33 +02:00
Daniele Alessandri 2578e154d4 New commands: BITOP, BITCOUNT (Redis v2.6-dev). 2012-05-17 17:17:35 +02:00
Daniele Alessandri 81da6d2553 TIME can be considered a read-only command in MASTER / SLAVE. 2012-05-17 16:48:08 +02:00
Daniele Alessandri 188eaf035b Fix comment in package.ini.
[ci skip]
2012-05-17 09:53:45 +02:00
Daniele Alessandri 6638776d85 [test] Add tests for pipeline executors. 2012-05-17 09:43:48 +02:00
Daniele Alessandri 511fe0bbab Rewrite the logic used to handle Redis commands with clustering.
The reason for this change is to support the upcoming redis-cluster since
it has different behaviors compared to the client-side cluster implementation
provided by Predis. For example redis-cluster will not support key tags or
certain operations currently available with our client-side implementation.
2012-05-16 16:50:37 +02:00
Daniele Alessandri 56a3e327fc Add a pipeline executor that internally uses MULTI / EXEC.
The actual pipeline is wrapped between MULTI and EXEC to ensure that all the
commands are correctly sent and executed on the server. The whole pipeline is
discarded should it fail at a certain point during execution.

We do not use our Predis\Transaction\MultiExecContext abstraction internally
but rely on raw commands since this new executor does not really need full
support for Redis transactions.
2012-05-15 15:59:23 +02:00
Daniele Alessandri 63cf5048a7 Slightly optimize internals of MULTI / EXEC transactions.
We start using SplQueue to enqueue the commands executed inside a MULTI /
EXEC context so that we can dequeue them for parsing their replies later.

This change does not affect performances but makes the code more clean.
2012-05-15 15:03:42 +02:00
Daniele Alessandri 177d7e1c6a Fix CHANGELOG. 2012-05-15 12:44:49 +02:00
Daniele Alessandri 8a84f8b6cf Optimize command pipelines.
Using SplQueue instead of a plain PHP array to queue command instance in
the pipeline is faster and makes the underlying implementation definitely
more clean.

This change is not going to be backported to Predis v0.7 due to changes
in Predis\Pipeline\PipelineContextInterface.
2012-05-15 12:42:26 +02:00
Daniele Alessandri 09f15a9cb9 Fix README. 2012-05-15 10:38:53 +02:00
Daniele Alessandri 2b4885abc3 Update README.
[ci skip]
2012-04-30 19:56:08 +02:00
Daniele Alessandri 649e71c0ab Clean up leftovers. 2012-04-30 17:07:19 +02:00
Daniele Alessandri 14ac381aad Fix phpdoc. 2012-04-30 17:06:42 +02:00
Daniele Alessandri 78360ea27b Set raw arguments when switching from scripted command to EVAL. 2012-04-28 18:04:54 +02:00
Daniele Alessandri 214b7d2dc9 Remove almost useless method. 2012-04-28 09:23:41 +02:00
Daniele Alessandri 2d0b30dac5 Client handles -NOSCRIPT falling back to EVAL for scripted commands.
Now Predis\Command\ScriptedCommand uses EVALSHA instead of EVAL internally
so that performances should be better since the client do not resend the
Lua script body on each call.

Plain EVALSHA commands are not affected and will return or throw the error.
2012-04-28 08:56:11 +02:00
Daniele Alessandri 75afe831b0 Fix Predis\Command\ServerEvalSHA::getScriptHash().
"Predis\Command\ServerEvalSHA::getScriptHash()" was calculating the hash
while it just needs to return the first argument of the command.
2012-04-28 01:30:21 +02:00
Daniele Alessandri c36e6fd900 Pass also the command that generated the server error. 2012-04-27 23:59:51 +02:00
Daniele Alessandri 743ccc39ae Remove the "safe" pipeline option.
Just let users specify the appropriate pipeline executor.
2012-04-27 23:36:12 +02:00
Daniele Alessandri 41c29bed4e Change how Redis errors such as -ERR replies generates exceptions.
The "throw_errors" connection parameter has been removed and replaced by the
new "exceptions" client option since exceptions on -ERR replies returned by
Redis are not generated by connection classes anymore but are thrown by the
client class and other abstractions such as pipeline contexts.

This change does not affect much people using the Predis\Client class (aside
from the different configuration) but gives much more flexibility to those
building their own pieces of code around the internal classes of Predis.
2012-04-27 23:29:41 +02:00
Daniele Alessandri 90f37f8698 Do not assume that the position of a MultiBulkResponse iterator is 0. 2012-04-27 16:13:15 +02:00
Daniele Alessandri dc889be2a2 Switch to master inside executor when pipelining in replication mode.
The actual code to switch to master has been implemented only in the
standard and the fire-and-forget executor classes since it does not
make sense to have it in special executors used exclusively when in
client-side clustering mode.
2012-04-25 13:02:33 +02:00
Daniele Alessandri ca7bd8149f Scripting abstraction can use negatives to calculate number of keys.
With a negative number Predis will count from the end of the arguments list
to calculate the actual number of keys that will be interpreted as elements
for `KEYS` by the underlying `EVAL` command.
2012-04-25 11:35:06 +02:00
Daniele Alessandri 9426d78b90 Fix example for custom distribution strategy.
See ISSUE #63 for details.
2012-04-13 11:37:03 +02:00
Daniele Alessandri 6b6c91cd15 Update CHANGELOG. 2012-04-01 10:15:31 +02:00
Daniele Alessandri b511911ddb Update README.
[ci skip]
2012-03-31 12:00:10 +02:00
Daniele Alessandri eb450ae0b4 Make 2.6 the default server profile for the test suite too.
We leave 2.4 for Travis CI since their CI environment uses Redis 2.4.

[ci skip]
2012-03-31 11:49:46 +02:00
Daniele Alessandri ca033d15a9 Add a couple of paragraphs in the FAQ. 2012-03-31 11:45:32 +02:00
Daniele Alessandri 9443da1948 Update TODO. 2012-03-31 11:42:25 +02:00
Daniele Alessandri 298dc87613 Update CHANGELOG. 2012-03-31 11:41:43 +02:00
Daniele Alessandri 92697aa075 Make 2.6 the default server profile. 2012-03-31 11:39:42 +02:00
Daniele Alessandri 7c393cbc09 Apparently the next major version or Redis will be 2.8. 2012-03-31 11:36:35 +02:00
Daniele Alessandri 5b6e4dc3dd Make it possible to prepend the autoloader. 2012-03-19 11:31:55 +01:00
Daniele Alessandri b37d072b68 Make Predis\Autoloader faster. 2012-03-18 18:01:01 +01:00
Daniele Alessandri 608c48146f Fix helper method for invalid options in connection classes.
This fix is needed since connection parameters do not respond to __toString()
anymore after recent changes.
2012-03-18 15:23:25 +01:00
Daniele Alessandri bcba9e1009 Change how we check for required extensions in connection classes. 2012-03-18 15:22:24 +01:00
Daniele Alessandri 64a84d9020 Add notes about the parameters supported by each connection class. 2012-03-18 15:07:18 +01:00
Daniele Alessandri a3b4c2244a Remove some overkill stuff from Predis\ConnectionParameters.
It was basically some crazy and almost useless stuff I'd say.
2012-03-18 14:53:21 +01:00
Daniele Alessandri 4805a048ed Add missing tests for Predis\Profile\ServerProfile::defineCommand(). 2012-03-18 13:00:53 +01:00
Daniele Alessandri c394458b80 Remove an almost useless method from Predis\Profile\ServerProfile. 2012-03-18 13:00:28 +01:00
Daniele Alessandri 51d0b58cd3 Add new interfaces used to abstract the main client class and contexts.
Now developers can pass a client object or an executable context (see
Predis\Transaction\MultiExecContext or Predis\Pipeline\PipelineContext)
interchangeably as parameters to their methods using the new interface
Predis\BasicCliantInterface.

These new interfaces will also allow us to easily create new client
classes aside from the standard Predis\Client one.
2012-03-18 11:05:33 +01:00
Daniele Alessandri 890ace8565 Rename certain connection parameters usig shorter names.
- `connection_async` is now `async_connect`
  - `connection_timeout` is now `timeout`
  - `connection_persistent` is now `persistent`
2012-03-16 19:33:17 +01:00
Daniele Alessandri 8ba432e8f7 Move list of renamed namespaces/interfaces/classes to a separate file. 2012-03-16 19:20:15 +01:00
Daniele Alessandri d61bb3af07 Update CHANGELOG. 2012-03-11 17:50:48 +01:00
Daniele Alessandri da381fffa9 Promote 2.6 as an independent server profile for Redis 2.6.
The development server profile now targets Redis 3.0.
2012-03-11 17:49:18 +01:00
Daniele Alessandri 7b01bc0644 Fix MONITOR and Predis\MonitorContext with Redis 2.6.
Starting with 2.6, Redis uses a slightly different format for the payload
returned by MONITOR for each command. Predis can now adapt to this difference
and returns a new `client` field in the payload object. This new field is
always defined but is set to NULL when connected to Redis < 2.6.

Please note that Redis 2.6 now does not echo the `MONITOR` command as the
first payload when opening a new MONITOR context.
2012-03-11 17:39:57 +01:00
Daniele Alessandri 7893fd1faa New command: TIME (Redis v2.6-dev). 2012-03-11 17:34:06 +01:00
Daniele Alessandri 785670dcf7 Enable integration tests for Redis commands on Travis CI.
This was not possible before since our test suite requires Redis >= 2.4 but
their environment was running Redis 2.2 until a month ago or so.
2012-02-25 10:58:00 +01:00
Daniele Alessandri ec723fd4a9 Remove the ability to execute a command on all the nodes of a cluster.
The reason for this change is that not every cluster implementation can support
this behaviour, think of Redis cluster for example. We moved the implementation
of this method in Predis\Connection\PredisCluster since it can still be useful.
2012-02-07 15:40:19 +01:00
Daniele Alessandri b67af29eb6 Make cluster and replication classes extend from a common interface. 2012-02-07 11:42:34 +01:00
Daniele Alessandri dc14c29676 Rename certain namespaces, interfaces and classes.
Now we follow a Symfony2-like naming convention for namespaces, interfaces
and classes sticking with one clear rule.

- Renamed namespaces:

  - Predis\Network
  - Predis\Profiles
  - Predis\Iterators
  - Predis\Options
  - Predis\Commands
  - Predis\Commands\Processors

- Renamed interfaces:

  - Predis\IReplyObject
  - Predis\IRedisServerError
  - Predis\IConnectionFactory
  - Predis\IConnectionParameters
  - Predis\Options\IOption
  - Predis\Options\IClientOptions
  - Predis\Profile\IServerProfile
  - Predis\Pipeline\IPipelineExecutor
  - Predis\Distribution\INodeKeyGenerator
  - Predis\Distribution\IDistributionStrategy
  - Predis\Protocol\IProtocolProcessor
  - Predis\Protocol\IResponseReader
  - Predis\Protocol\IResponseHandler
  - Predis\Protocol\ICommandSerializer
  - Predis\Protocol\IComposableProtocolProcessor
  - Predis\Network\IConnection
  - Predis\Network\IConnectionSingle
  - Predis\Network\IConnectionComposable
  - Predis\Network\IConnectionCluster
  - Predis\Network\IConnectionReplication
  - Predis\Commands\ICommand
  - Predis\Commands\IPrefixable
  - Predis\Command\Processor\ICommandProcessor
  - Predis\Command\Processor\ICommandProcessorChain
  - Predis\Command\Processor\IProcessingSupport

- Renamed Classes:

  - Predis\Commands\Command
  - Predis\Network\ConnectionBase

- Classes moved to different namespaces:

  - Predis\MonitorContext

Meh
2012-01-31 18:19:18 +01:00
Daniele Alessandri ce625ca902 Start development of Predis v0.8. 2012-01-15 12:04:17 +01:00
Daniele Alessandri 46d293a716 Fix phpdocs.
[ci skip]
2012-01-14 23:35:39 +01:00
Daniele Alessandri fcd41a4787 Fix CHANGELOG. 2012-01-14 23:33:39 +01:00
Daniele Alessandri 8dc9450f39 Bump year in LICENSE.
[ci skip]
2012-01-14 15:29:12 +01:00
Daniele Alessandri 6ad522f492 Update CHANGELOG. 2012-01-14 15:11:27 +01:00
Daniele Alessandri 317fb6c398 Make Predis\Network\MasterSlaveReplication serializable.
Since PHP cannot serialize closures we switched to a private method to check
if a SORT command is a read-only operation, but closures are still supported
even if they will make the connection unserializable.
2012-01-14 14:53:52 +01:00
Daniele Alessandri 93163bdfb3 Make it possible to serialize and unserialize connection instances. 2012-01-14 14:53:47 +01:00
Daniele Alessandri 626520b836 Add specific server profile alias for Redis 2.6. 2012-01-14 13:01:38 +01:00
Daniele Alessandri aaea628c55 Back to development for the next patch release. 2012-01-14 12:51:01 +01:00
481 changed files with 11221 additions and 5113 deletions
+2
View File
@@ -2,4 +2,6 @@
*.phar
phpunit.xml
package.xml
composer.lock
experiments/
vendor/
+7 -3
View File
@@ -2,8 +2,12 @@ language: php
php:
- 5.3
- 5.4
- 5.5
branches:
only:
- master
- v0.7
except:
- v0.5
- v0.6
- php5.2_backport
- documentation
services: redis-server
script: phpunit -c phpunit.xml.travisci
+53
View File
@@ -0,0 +1,53 @@
# Namespaces, interfaces and classes renamed in Predis v0.8 #
____________________________________________
Some namespaces, interfaces and classes in Predis v0.8 have been renamed to follow a common rule inspired
by the naming conventions adopted by the Symfony2 project. This is a list of all the changes:
### Renamed namespaces ###
- `Predis\Network` => `Predis\Connection`
- `Predis\Profiles` => `Predis\Profile`
- `Predis\Iterators` => `Predis\Iterator`
- `Predis\Options` => `Predis\Option`
- `Predis\Commands` => `Predis\Command`
- `Predis\Commands\Processors` => `Predis\Command\Processor`
### Renamed interfaces ###
- `Predis\IReplyObject` => `Predis\ResponseObjectInterface`
- `Predis\IRedisServerError` => `Predis\ResponseErrorInterface`
- `Predis\Options\IOption` => `Predis\Option\OptionInterface`
- `Predis\Options\IClientOptions` => `Predis\Option\ClientOptionsInterface`
- `Predis\Profile\IServerProfile` => `Predis\Profile\ServerProfileInterface`
- `Predis\Pipeline\IPipelineExecutor` => `Predis\Pipeline\PipelineExecutorInterface`
- `Predis\Distribution\INodeKeyGenerator` => `Predis\Distribution\HashGeneratorInterface`
- `Predis\Distribution\IDistributionStrategy` => `Predis\Distribution\DistributionStrategyInterface`
- `Predis\Protocol\IProtocolProcessor` => `Predis\Protocol\ProtocolInterface`
- `Predis\Protocol\IResponseReader` => `Predis\Protocol\ResponseReaderInterface`
- `Predis\Protocol\IResponseHandler` => `Predis\Protocol\ResponseHandlerInterface`
- `Predis\Protocol\ICommandSerializer` => `Predis\Protocol\CommandSerializerInterface`
- `Predis\Protocol\IComposableProtocolProcessor` => `Predis\Protocol\ComposableProtocolInterface`
- `Predis\Network\IConnection` => `Predis\Connection\ConnectionInterface`
- `Predis\Network\IConnectionSingle` => `Predis\Connection\SingleConnectionInterface`
- `Predis\Network\IConnectionComposable` => `Predis\Connection\ComposableConnectionInterface`
- `Predis\Network\IConnectionCluster` => `Predis\Connection\ClusterConnectionInterface`
- `Predis\Network\IConnectionReplication` => `Predis\Connection\ReplicationConnectionInterface`
- `Predis\Commands\ICommand` => `Predis\Command\CommandInterface`
- `Predis\Commands\IPrefixable` => `Predis\Command\PrefixableCommandInterface`
- `Predis\Command\Processor\ICommandProcessor` => `Predis\Command\Processor\CommandProcessorInterface`
- `Predis\Command\Processor\ICommandProcessorChain` => `Predis\Command\Processor\CommandProcessorChainInterface`
- `Predis\Command\Processor\IProcessingSupport` => `Predis\Command\Processor\CommandProcessingInterface`
### Renamed classes ###
- `Predis\Commands\Command` => `Predis\Command\AbstractCommand`
- `Predis\Network\ConnectionBase` => `Predis\Connection\AbstractConnection`
### Classes or interfaces moved to different namespaces ###
- `Predis\MonitorContext` => `Predis\Monitor\MonitorContext`
- `Predis\ConnectionParameters` => `Predis\Connection\ConnectionParameters`
- `Predis\ConnectionParametersInterface` => `Predis\Connection\ConnectionParametersInterface`
- `Predis\ConnectionFactory` => `Predis\Connection\ConnectionFactory`
- `Predis\ConnectionFactoryInterface` => `Predis\Connection\ConnectionFactoryInterface`
+141 -4
View File
@@ -1,3 +1,140 @@
v0.8.1 (2013-01-19)
===============================================================================
- The `connections` client option can now accept a callable object returning
an instance of `Predis\Connection\ConnectionFactoryInterface`.
- Client options accepting callable objects as factories now pass their actual
instance to the callable as the second argument.
- `Predis\Command\Processor\KeyPrefixProcessor` can now be directly casted to
string to obtain the current prefix, useful with string interpolation.
- Added an optional callable argument to `Predis\Cluster\Distribution\HashRing`
and `Predis\Cluster\Distribution\KetamaPureRing` constructor that can be used
to customize how the distributor should extract the connection hash when
initializing the nodes distribution (ISSUE #36).
- Correctly handle `TTL` and `PTTL` returning -2 on non existing keys starting
with Redis 2.8.
- __FIX__: a missing use directive in `Predis\Transaction\MultiExecContext`
caused PHP errors when Redis did not return `+QUEUED` replies to commands
when inside a MULTI / EXEC context.
- __FIX__: the `parseResponse()` method implemented for a scripted command was
ignored when retrying to execute a Lua script by falling back to `EVAL` after
a `-NOSCRIPT` error (ISSUE #94).
- __FIX__: when subclassing `Predis\Client` the `getClientFor()` method returns
a new instance of the subclass instead of a new instance of `Predis\Client`.
v0.8.0 (2012-10-23)
===============================================================================
- The default server profile for Redis is now `2.6`.
- Certain connection parameters have been renamed:
- `connection_async` is now `async_connect`
- `connection_timeout` is now `timeout`
- `connection_persistent` is now `persistent`
- The `throw_errors` connection parameter has been removed and replaced by the
new `exceptions` client option since exceptions on `-ERR` replies returned by
Redis are not generated by connection classes anymore but instead are thrown
by the client class and other abstractions such as pipeline contexts.
- Added smart support for redis-cluster (Redis v3.0) in addition to the usual
cluster implementation that uses client-side sharding.
- Various namespaces and classes have been renamed to follow rules inspired by
the Symfony2 naming conventions.
- The second argument of the constructor of `Predis\Client` does not accept
strings or instances of `Predis\Profile\ServerProfileInterface` anymore.
To specify a server profile you must explicitly set `profile` in the array
of client options.
- `Predis\Command\ScriptedCommand` internally relies on `EVALSHA` instead of
`EVAL` thus avoiding to send Lua scripts bodies on each request. The client
automatically resends the command falling back to `EVAL` when Redis returns a
`-NOSCRIPT` error. Automatic fallback to `EVAL` does not work with pipelines,
inside a `MULTI / EXEC` context or with plain `EVALSHA` commands.
- Complex responses are no more parsed by connection classes as they must be
processed by consumer classes using the handler associated to the issued
command. This means that executing commands directly on connections only
returns simple Redis types, but nothing changes when using `Predis\Client`
or the provided abstractions for pipelines and transactions.
- Iterators for multi-bulk replies now skip the response parsing method of the
command that generated the response and are passed directly to user code.
Pipeline and transaction objects still consume automatically iterators.
- Cluster and replication connections now extend a new common interface,
`Predis\Connection\AggregatedConnectionInterface`.
- `Predis\Connection\MasterSlaveReplication` now uses an external strategy
class to handle the logic for checking readable / writable commands and Lua
scripts.
- Command pipelines have been optimized for both speed and code cleanness, but
at the cost of bringing a breaking change in the signature of the interface
for pipeline executors.
- Added a new pipeline executor that sends commands wrapped in a MULTI / EXEC
context to make the execution atomic: if a pipeline fails at a certain point
then the whole pipeline is discarded.
- The key-hashing mechanism for commands is now handled externally and is no
more a competence of each command class. This change is neeeded to support
both client-side sharding and Redis cluster.
- `Predis\Options\Option` is now abstract, see `Predis\Option\AbstractOption`.
v0.7.3 (2012-06-01)
===============================================================================
- New commands available in the Redis v2.6 profile (dev): `BITOP`, `BITCOUNT`.
- When the number of keys `Predis\Commands\ScriptedCommand` is negative, Predis
will count from the end of the arguments list to calculate the actual number
of keys that will be interpreted as elements for `KEYS` by the underlying
`EVAL` command.
- __FIX__: `examples\CustomDistributionStrategy.php` had a mistyped constructor
call and produced a bad distribution due to an error as pointed in ISSUE #63.
This bug is limited to the above mentioned example and does not affect the
classes implemented in the `Predis\Distribution` namespace.
- __FIX__: `Predis\Commands\ServerEvalSHA::getScriptHash()` was calculating the
hash while it just needs to return the first argument of the command.
- __FIX__: `Predis\Autoloader` has been modified to allow cascading autoloaders
for the `Predis` namespace.
v0.7.2 (2012-04-01)
===============================================================================
- Added `2.6` in the server profiles aliases list for the upcoming Redis 2.6.
`2.4` is still the default server profile. `dev` now targets Redis 2.8.
- Connection instances can be serialized and unserialized using `serialize()`
and `unserialize()`. This is handy in certain scenarios such as client-side
clustering or replication to lower the overhead of initializing a connection
object with many sub-connections since unserializing them can be up to 5x
times faster.
- Reworked the default autoloader to make it faster. It is also possible to
prepend it in PHP's autoload stack.
- __FIX__: fixed parsing of the payload returned by `MONITOR` with Redis 2.6.
v0.7.1 (2011-12-27)
===============================================================================
@@ -97,7 +234,7 @@ v0.6.6 (2011-04-01)
- Switched to Redis 2.2 as the default server profile (there are no changes
that would break compatibility with previous releases). Long command names
are no more supported by default but if you need them you can still require
Predis_Compatibility.php to avoid breaking compatibility.
`Predis_Compatibility.php` to avoid breaking compatibility.
- Added a `VERSION` constant to `Predis\Client`.
@@ -107,7 +244,7 @@ v0.6.6 (2011-04-01)
- Predis now uses by default a new protocol reader, more lightweight and
faster than the default handler-based one. Users can revert to the old
protocol reader with the 'reader' client option set to `composable`.
protocol reader with the `reader` client option set to `composable`.
This client option can also accept custom reader classes implementing the
new `Predis\IResponseReader` interface.
@@ -246,7 +383,7 @@ v0.6.0 (2010-05-24)
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.
'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`
@@ -259,7 +396,7 @@ v0.6.0 (2010-05-24)
- Introduced client-level options with the new `Predis\ClientOptions` class.
Options can be passed to the constructor of `Predis\Client` in its second
argument as an array or an instance of Predis\ClientOptions. For brevity's
argument as an array or an instance of `Predis\ClientOptions`. For brevity's
sake and compatibility with older versions, the constructor still accepts
an instance of `Predis\RedisServerProfile` in its second argument. The
currently supported client options are:
+40
View File
@@ -0,0 +1,40 @@
## Filing bug reports ##
Bugs or feature requests can be posted online on the [GitHub issues](http://github.com/nrk/predis/issues)
section of the project.
When reporting bugs, in addition to the obvious description of your issue you __must__ always provide
some essential information about your environment such as:
1. version of Predis (check the `VERSION` file or the `Predis\Client::VERSION` constant).
2. version of Redis (check the `redis_version` field returned by [`INFO`](http://redis.io/commands/info)).
3. version of PHP.
4. name and version of the operating system.
5. when possible, a small snippet of code that reproduces the issue.
__Think about it__: we do not have a crystal ball and cannot predict things and peer into the unknown,
so please provide as much details as possible to help us isolating issues and fix them.
__Never__ use GitHub issues to post generic questions about Predis! When you have questions about
how Predis works or how it can be used, please just hop me an email and I will get back to you as
soon as possible.
## Contributing code ##
If you want to work on Predis, it is highly recommended that you first run the test suite in order to
check that everything is OK, and report strange behaviours or bugs. When modifying Predis please make
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 | E_STRICT`.
The recommended way to contribute to Predis is to fork the project on GitHub, create new topic branches
on your newly created repository to fix or add features (possibly with tests covering your modifications)
and then open a new pull request with a description of the applied changes. Obviously you can use any
other Git hosting provider of your preference.
When writing code please follow the [basic coding (PSR-1)](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
and [coding style (PSR-2)](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
standards and stick with the conventions used in Predis to name classes and interfaces.
Please also follow some basic [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines)
before opening pull requests.
-79
View File
@@ -1,79 +0,0 @@
# Some frequently asked questions about the performances of Predis #
____________________________________________
### Predis is a pure-PHP implementation: it can not be fast enough! ###
It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you
a couple of easy numbers using a single Predis client with PHP 5.3.5 (custom build) and Redis 2.2
(localhost) under Ubuntu 11.04 (running on a Intel Q6600):
19600 SET/sec using 12 bytes for both key and value
18900 GET/sec while retrieving the very same values
0.200 seconds to fetch 30000 keys using _KEYS *_.
How does it compare with a nice C-based extension such as [__phpredis__](http://github.com/nicolasff/phpredis)?
30500 SET/sec using 12 bytes for both key and value
31000 GET/sec while retrieving the very same values
0.030 seconds to fetch 30000 keys using "KEYS *"".
Wow, __phpredis__ looks so much faster! Well we are comparing a C extension with a pure-PHP library so
lower numbers are quite expected, but there is a fundamental flaw in them: is this really how you are
going to use Redis in your application? Are you really going to send thousands of commands in a for-loop
for each page request using a single client instance? If so, well I guess you are probably doing something
wrong. Also, if you need to SET or GET multiple keys you should definitely use commands such as MSET and
MGET. You can also use pipelining to get more performances when this technique can be used.
There is one more thing. We have tested the overhead of Predis by connecting on a localhost instance of
Redis, but how these numbers change when we hit the network by connecting to instances of Redis that
reside on other servers?
Using Predis:
3600 SET/sec using 12 bytes for both key and value
3600 GET/sec while retrieving the very same values
0.210 seconds to fetch 30000 keys using "KEYS *".
Using phpredis:
4000 SET/sec using 12 bytes for both key and value
4000 GET/sec while retrieving the very same values
0.051 seconds to fetch 30000 keys using "KEYS *".
There you go, you get almost the same average numbers and the reason is quite simple: network latency
is a real performance killer and you cannot do (almost) anything about that. As a disclaimer, please
remember that we are measuring the overhead of client libraries implementations and the effects of the
network round-trip time, we are not really measuring how fast Redis is. Redis shines the best with
thousands of concurrent clients doing requests! Also, actual performances should be measured according
to how your application will use Redis.
### I am convinced, but performances for multi-bulk replies (e.g. _KEYS *_) are still worse ###
Fair enough, but there is actually an option for you if you need even more speed and it consists on
installing __[phpiredis](http://github.com/seppo0010/phpiredis)__ (note the additional _i_ in the
name) and let Predis using it. __phpiredis__ is a C-based extension that wraps __hiredis__ (the
official Redis C client library) with a thin layer that exposes its features to PHP. You will now
get the benefits of a faster protocol parser just by adding a single line of code in your application:
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'Predis\Network\PhpiredisConnection')
));
As simple as it is, nothing will really change in the way you use the library in your application. So,
how fast is it now? There are not much improvements for inline or short bulk replies (e.g. _SET_ or
_GET_), but the speed for parsing multi-bulk replies is now on par with phpredis:
Using Predis with a phpiredis-based connection to fetch 30000 keys using _KEYS *_:
0.031 seconds from a local Redis instance
0.058 seconds from a remote Redis instance
### If I need to install a C extension to get better performances, why not using phpredis? ###
Good question. Generically speaking, if you need absolute uber-speed using localhost instances of Redis
and you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you
do not need any kind of extensibility or guaranteed backwards compatibility with different versions of
Redis (Predis currently supports from 1.2 up to 2.2, and even the current development version), then
using __phpredis__ can make sense for you. Otherwise, Predis is for you. Using __phpiredis__ gives you
a nice speed bump, but it is not mandatory.
+126 -29
View File
@@ -1,5 +1,5 @@
# Some frequently asked questions about Predis #
____________________________________________
_________________________________________________
### What is the point of Predis? ###
@@ -14,34 +14,6 @@ features, this can be a great asset that allows you to add new and still missing
or change the behaviour of the library without the need to break your dependencies in production code
(well, at least to some degree).
### How about performances? ###
Please refer to the dedicated __FAQ.PERFORMANCES__ file.
### Why PHP 5.3? ###
Seriously, are you still using PHP 5.2 to build new applications? I assume that if you are throwing Redis
in the mix, then you are probably coding something new after all. PHP 5.3 is faster, less memory hungry
and has a few nice features such as namespaces and closures (kind of). More importantly, PHP 5.2 is not
even officially supported anymore (aside from security patches). PHP 5.3 is not the future of PHP, but
its current present. Furthermore, most of the existing frameworks out there are also making the switch
with their respective new major versions. If you still insist on using PHP 5.2, you can get any recent
backported release of Predis 0.6.x, or just use a different library.
### Why so many files for just one library? ###
Before v0.7, Predis used the one-big-file approach to distribute the library. As much as you prefer having
just one file for everything, this kind of solution is actually not that good. Predis now complies with the
[PSR-0](http://groups.google.com/group/php-standards/web/psr-0-final-proposal) standard to play nice with
the major recent frameworks and libraries, so it needs an autoloader function to be defined. If you still
want to have just one file grouping all the classes for whatever reason, then the __bin/create-single-file.php__
script in the repository can generate it for you. There is also the __bin/create-phar.php__ script that
generates a single [Phar archive](http://www.php.net/manual/en/intro.phar.php) of the whole library.
### Does Predis support UNIX domain sockets and persistent connections? ###
Yes. Obviously, persistent connections actually work when using PHP configured as a persistent process that
@@ -56,3 +28,128 @@ generalized when using Redis because of the many possible access patterns for th
mean that it is impossible to have such a feature, you can leverage Predis' extensibility to define your
own serialization-aware commands. See [here](http://github.com/nrk/predis/issues/29#issuecomment-1202624)
for more details on how to implement such a feature with a practical example.
### How can I force Predis to connect to Redis before sending any command? ###
Explicitly connecting to Redis is usually not needed since the client library relies on lazily initialized
connections to the server, but this behavior can be inconvenient in certain scenarios when you absolutely
need to do an upfront check to detect if the server is up and running and eventually catch exceptions on
failures. In this case developers can use `Predis\Client::connect()` to explicitly connect to the server:
```php
$client = new Predis\Client();
try {
$client->connect();
} catch (Predis\Connection\ConnectionException $exception) {
// We could not connect to Redis! Your handling code goes here.
}
$client->info();
```
### How Predis implements abstraction of Redis commands? ###
The approach used in Predis to implement the abstraction of Redis commands is quite simple. By default
every command in the library follows exactly the same argument list as defined in the great online
[Redis documentation](http://redis.io/commands) which makes things pretty easy if you already know how
Redis works or if you need to look up how to use certain commands. Alternatively, variadic commands can
accept an array for keys or values (depending on the command) instead of a list of arguments. See for
example how [RPUSH](http://redis.io/commands/rpush) or [HMSET](http://redis.io/commands/hmset) work:
```php
$client->rpush('my:list', 'value1', 'value2', 'value3'); // values as arguments
$client->rpush('my:list', array('value1', 'value2', 'value3')); // values as single argument array
$client->hmset('my:hash', 'field1', 'value1', 'field2', 'value2'); // values as arguments
$client->hmset('my:hash', array('field1'=>'value1', 'field2'=>'value2'); // values as single named array
```
The only exception to this _rule_ is the [SORT](http://redis.io/commands/sort) command for which modifiers are
[passed using a named array](https://github.com/nrk/predis/blob/master/tests/Predis/Command/KeySortTest.php#L56-77).
# Frequently asked questions about performances #
_________________________________________________
### Predis is a pure-PHP implementation: it can not be fast enough! ###
It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you
a couple of easy numbers using a single Predis client with PHP 5.4.7 (custom build) and Redis 2.2
(localhost) under Ubuntu 12.04.1 (running on a Intel Q6600):
21500 SET/sec using 12 bytes for both key and value
21000 GET/sec while retrieving the very same values
0.130 seconds to fetch 30000 keys using _KEYS *_.
How does it compare with a nice C-based extension such as [__phpredis__](http://github.com/nicolasff/phpredis)?
30100 SET/sec using 12 bytes for both key and value
29400 GET/sec while retrieving the very same values
0.035 seconds to fetch 30000 keys using "KEYS *"".
Wow, __phpredis__ looks so much faster! Well we are comparing a C extension with a pure-PHP library so
lower numbers are quite expected, but there is a fundamental flaw in them: is this really how you are
going to use Redis in your application? Are you really going to send thousands of commands in a for-loop
for each page request using a single client instance? If so, well I guess you are probably doing something
wrong. Also, if you need to SET or GET multiple keys you should definitely use commands such as MSET and
MGET. You can also use pipelining to get more performances when this technique can be used.
There is one more thing. We have tested the overhead of Predis by connecting on a localhost instance of
Redis, but how these numbers change when we hit the network by connecting to instances of Redis that
reside on other servers?
Using Predis:
3200 SET/sec using 12 bytes for both key and value
3200 GET/sec while retrieving the very same values
0.132 seconds to fetch 30000 keys using "KEYS *".
Using phpredis:
3500 SET/sec using 12 bytes for both key and value
3500 GET/sec while retrieving the very same values
0.045 seconds to fetch 30000 keys using "KEYS *".
There you go, you get almost the same average numbers and the reason is quite simple: network latency
is a real performance killer and you cannot do (almost) anything about that. As a disclaimer, please
remember that we are measuring the overhead of client libraries implementations and the effects of the
network round-trip time, we are not really measuring how fast Redis is. Redis shines the best with
thousands of concurrent clients doing requests! Also, actual performances should be measured according
to how your application will use Redis.
### I am convinced, but performances for multi-bulk replies (e.g. _KEYS *_) are still worse ###
Fair enough, but there is actually an option for you if you need even more speed and it consists on
installing __[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name)
and let Predis using it. __phpiredis__ is a C-based extension that wraps __hiredis__ (the official
Redis C client library) with a thin layer that exposes its features to PHP. You will now get the
benefits of a faster protocol parser just by adding a single line of code in your application:
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'Predis\Connection\PhpiredisConnection')
));
```
As simple as it is, nothing will really change in the way you use the library in your application. So,
how fast is it now? There are not much improvements for inline or short bulk replies (e.g. _SET_ or
_GET_), but the speed for parsing multi-bulk replies is now on par with phpredis:
Using Predis with a phpiredis-based connection to fetch 30000 keys using _KEYS *_:
0.035 seconds from a local Redis instance
0.047 seconds from a remote Redis instance
### If I need to install a C extension to get better performances, why not using phpredis? ###
Good question. Generically speaking, if you need absolute uber-speed using localhost instances of Redis
and you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you
do not need any kind of extensibility or guaranteed backwards compatibility with different versions of
Redis (Predis currently supports from 1.2 up to 2.6, and even the current development version), then
using __phpredis__ can make sense for you. Otherwise, Predis is perfect for the job. __phpiredis__
can give you a nice speed bump, but using it is not mandatory.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2011 Daniele Alessandri
Copyright (c) 2009-2013 Daniele Alessandri
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
+93 -61
View File
@@ -2,24 +2,29 @@
Predis is a flexible and feature-complete PHP (>= 5.3) client library for the Redis key-value store.
For a list of frequently asked questions about Predis, see the __FAQ__ file in the root of the repository.
For a version compatible with PHP 5.2 you must use the backported version from the latest release in the
0.6.x series. More details are available on the [official wiki](http://wiki.github.com/nrk/predis) of the
project,
The library does not require any additional extension loaded in PHP but it can be optionally paired
with the [phpiredis](https://github.com/nrk/phpiredis) C-based extension to lower the overhead of
serializing and parsing the Redis protocol. Predis is also available in an asynchronous fashion
through the experimental client provided by the [Predis\Async](http://github.com/nrk/predis-async)
library.
For a list of frequently asked questions about Predis, see __FAQ.md__ in the root of the repository.
More details are available on the [official wiki](http://wiki.github.com/nrk/predis) of the project.
## Main features ##
- Complete support for Redis from __1.2__ to __2.4__ and the current development versions using different
server profiles.
- Client-side sharding with support for consistent hashing or custom distribution strategies.
- Wide range of Redis versions supported (from __1.2__ to __2.6__ and unstable) using server profiles.
- Smart support for [redis-cluster](http://redis.io/topics/cluster-spec) (Redis >= 3.0).
- Client-side sharding via consistent hashing or custom distribution strategies.
- Support for master / slave replication configurations (write on master, read from slaves).
- Command pipelining on single and aggregated connections.
- Transparent key prefixing strategy capable of handling any command known that has keys in its arguments.
- Command pipelining on single and aggregated connections.
- Abstraction for Redis transactions (Redis >= 2.0) with support for CAS operations (Redis >= 2.2).
- Connections to Redis instances are automatically and lazily estabilished upon the first call to a command.
- Abstraction for Lua scripting (Redis >= 2.6) capable of automatically switching between `EVAL` and `EVALSHA`.
- Connections to Redis instances are lazily established upon the first call to a command by the client.
- Ability to connect to Redis using TCP/IP or UNIX domain sockets with support for persistent connections.
- Ability to use alternative connection classes to use different types of network or protocol backends.
- Ability to specify alternative connection classes to use different types of network or protocol backends.
- Flexible system to define and register your own set of commands or server profiles to client instances.
@@ -35,13 +40,13 @@ by browsing the list of [tagged releases](http://github.com/nrk/predis/tags).
### Loading the library ###
To automatically load all of its files, Predis relies on the autoloading features of PHP and complies
with the [PSR-0 standard](http://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) for
interoperability with most of the major frameworks and libraries. Everything is transparently handled
for you when installing the library using Composer, but you can also leverage its own autoloader class
if you are going to use it in a project or script without any PSR-0 compliant autoloading facility:
Predis relies on the autoloading features of PHP to load its files when needed and complies with the
[PSR-0 standard](http://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) which makes it
compatible with most of the major frameworks and libraries. Autoloading in your application is handled
automatically when managing the dependencies with Composer, but you can also leverage its own autoloader
class if you are going to use it in a project or script without any PSR-0 compliant autoloading facility:
``` php
```php
<?php
// prepend a base path if Predis is not present in your "include_path".
require 'Predis/Autoloader.php';
@@ -49,32 +54,29 @@ require 'Predis/Autoloader.php';
Predis\Autoloader::register();
```
You can create a single [Phar](http://www.php.net/manual/en/intro.phar.php) archive from the repository
just by launching the `bin/create-phar.php` executable script. The generated Phar archive ships with a
stub defining an autoloader function for Predis, so you just need to require the Phar to be able to use
the library.
It is possible to create a single [Phar](http://www.php.net/manual/en/intro.phar.php) archive from the
repository just by launching `bin/create-phar.php`. The generated Phar archive ships with a stub defining
an autoloader function for Predis, so you just need to require the Phar to be able to use the library.
Alternatively you can generate a single PHP file that holds every class, just like older versions of
Predis, using the `bin/create-single-file.php` executable script. In this way you can load Predis in your
scripts simply by using functions such as `require` and `include`, but this practice is not encouraged.
Alternatively it is possible to generate a single PHP file that holds every class, just like older versions
of Predis, using `bin/create-single-file.php`. In this way you can load Predis in your scripts simply by
using functions such as `require` and `include`, but this practice is not encouraged.
### Connecting to a local instance of Redis ###
### Connecting to Redis ###
When connecting to local instance of Redis (`127.0.0.1` on port `6379`), you do not have to specify any
additional parameter to create a new client instance:
By default Predis uses `127.0.0.1` and `6379` as the default host and port when creating a new client
instance without specifying any connection parameter:
``` php
<?php
```php
$redis = new Predis\Client();
$redis->set('foo', 'bar');
$value = $redis->get('foo');
```
However you can use an URI string or a named array to specify the needed connection parameters:
It is possible to specify the various connection parameters using URI strings or named arrays:
``` php
<?php
```php
$redis = new Predis\Client('tcp://10.0.0.1:6379');
// is equivalent to:
@@ -87,21 +89,20 @@ $redis = new Predis\Client(array(
```
### Pipelining multiple commands to multiple instances of Redis with client-side sharding ###
### Pipelining commands to multiple instances of Redis with client-side sharding ###
Pipelining helps with performances when there is the need to send many commands to a server in one go.
Furthermore, pipelining works transparently even on aggregated connections. To achieve this, Predis
supports client-side sharding using consistent-hashing on keys while clustered connections are supported
natively by the client class.
``` php
<?php
```php
$redis = new Predis\Client(array(
array('host' => '10.0.0.1', 'port' => 6379),
array('host' => '10.0.0.2', 'port' => 6379)
));
$replies = $redis->pipeline(function($pipe) {
$replies = $redis->pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
$pipe->get("key:$i");
@@ -110,15 +111,24 @@ $replies = $redis->pipeline(function($pipe) {
```
### Overriding standard connection classes with custom ones ###
### Multiple and customizable connection backends ###
Predis allows developers to create new connection classes to add support for new protocols or override
the existing ones and provide a different implementation compared to the default classes. This can be
obtained by subclassing the `Predis\Network\IConnectionSingle` interface.
Predis can optionally use different connection backends to connect to Redis. One of them leverages
the [phpiredis](http://github.com/nrk/phpiredis) C-based extension resulting in a major speed bump
especially when dealing with long multibulk replies (the `socket` extension is also required):
``` php
<?php
class MyConnectionClass implements Predis\Network\IConnectionSingle
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'Predis\Connection\PhpiredisConnection')
));
```
Developers can also create their own connection backends to add support for new protocols, extend
existing ones or provide different implementations. Connection backend classes must implement
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
```php
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
{
// implementation goes here
}
@@ -129,8 +139,8 @@ $client = new Predis\Client('tcp://127.0.0.1', array(
));
```
The classes contained in the `Predis\Network` namespace give you a better insight with actual code on
how to create new connection classes.
For a more in-depth insight on how to create new connection backends you can look at the actual
implementation of the classes contained in `Predis\Connection` namespace.
### Defining and registering new commands on the client at runtime ###
@@ -141,9 +151,8 @@ code or waiting for it to find its way into a stable Predis release, then you ca
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:
``` php
<?php
class BrandNewRedisCommand extends Predis\Commands\Command
```php
class BrandNewRedisCommand extends Predis\Command\AbstractCommand
{
public function getId()
{
@@ -157,6 +166,42 @@ $redis->newcmd();
```
### Abstraction for handling Lua scripts as plain Redis commands ###
A scripted command in Predis is an abstraction for [Lua scripting](http://redis.io/commands/eval)
with Redis >= 2.6 that allows to use a Lua script as if it was a plain Redis command registered
in the server profile being used by the client instance. Internally, scripted commands use
[EVALSHA](http://redis.io/commands/evalsha) to refer to a Lua script by its SHA1 hash in order
to save bandwidth, but they are capable of falling back to [EVAL](http://redis.io/commands/eval)
when needed:
```php
class ListPushRandomValue extends Predis\Command\ScriptedCommand
{
public function getKeysCount()
{
return 1;
}
public function getScript()
{
return
<<<LUA
math.randomseed(ARGV[1])
local rnd = tostring(math.random())
redis.call('lpush', KEYS[1], rnd)
return rnd
LUA;
}
}
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
$value = $client->lpushrand('random_values', $seed = mt_rand());
```
## Test suite ##
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running in
@@ -175,19 +220,6 @@ Predis uses Travis CI for continuous integration. You can find the results of th
history [on its project page](http://travis-ci.org/nrk/predis).
## Contributing ##
If you want to work on Predis, it is highly recommended that you first run the test suite in order to
check that everything is OK, and report strange behaviours or bugs. When modifying Predis please make
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 | E_STRICT`.
The recommended way to contribute to Predis is to fork the project on GitHub, create new topic branches
on your newly created repository to fix or add features (possibly with tests covering your modifications)
and then open a new pull request with a description of the applied changes. Obviously you can use any
other Git hosting provider of your preference.
## Dependencies ##
- PHP >= 5.3.2
@@ -216,7 +248,7 @@ other Git hosting provider of your preference.
- [Lorenzo Castelli](http://github.com/lcastelli)
- [Jordi Boggiano](http://github.com/Seldaek) ([twitter](http://twitter.com/seldaek))
- [Sebastian Waisbrot](http://github.com/seppo0010) ([twitter](http://twitter.com/seppo0010))
for his work on extending [phpiredis](http://github.com/seppo0010/phpiredis) for Predis
for his past work on extending [phpiredis](http://github.com/nrk/phpiredis) for Predis.
## License ##
-4
View File
@@ -1,4 +0,0 @@
* Documentation! The README is obviously not enought to show how to use Predis
as it does not cover all of its features.
* [v0.8] Implement smart and transparent support for redis-cluster.
+1 -1
View File
@@ -1 +1 @@
0.7.1
0.8.1
+1 -1
View File
@@ -50,7 +50,7 @@ function getPharStub($options)
return <<<EOSTUB
<?php
Phar::mapPhar('predis.phar');
spl_autoload_register(function(\$class) {
spl_autoload_register(function (\$class) {
if (strpos(\$class, 'Predis\\\\') === 0) {
\$file = 'phar://predis.phar/'.strtr(\$class, '\\\', '/').'.php';
if (file_exists(\$file)) {
+10 -9
View File
@@ -184,7 +184,7 @@ class PredisFile
{
$namespaces = array_fill_keys(array_unique(
array_map(
function($fqn) { return PhpNamespace::extractName($fqn); },
function ($fqn) { return PhpNamespace::extractName($fqn); },
array_keys($dependencyScores)
)
), 0);
@@ -200,7 +200,7 @@ class PredisFile
private function getOrderedClasses(PhpNamespace $phpNamespace, $classes)
{
$nsClassesFQNs = array_map(function($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
$nsClassesFQNs = array_map(function ($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
$nsOrderedClasses = array();
foreach ($nsClassesFQNs as $nsClassFQN) {
@@ -352,7 +352,9 @@ class PhpUseDirectives implements Countable, IteratorAggregate
public function getPhpCode()
{
$reducer = function($str, $use) { return $str .= "use $use;\n"; };
$reducer = function ($str, $use) {
return $str .= "use $use;\n";
};
return array_reduce($this->getList(), $reducer, '');
}
@@ -430,7 +432,7 @@ LICENSE;
{
$useDirectives = $this->getNamespace()->getUseDirectives();
$useExtractor = function($m) use($useDirectives) {
$useExtractor = function ($m) use ($useDirectives) {
$useDirectives->add(($namespacedPath = $m[1]));
};
@@ -453,7 +455,7 @@ LICENSE;
$implements = array();
$extends = array();
$extractor = function($iterator, $callback) {
$extractor = function ($iterator, $callback) {
$className = '';
$iterator->seek($iterator->key() + 1);
@@ -464,8 +466,7 @@ LICENSE;
if (preg_match('/\s?,\s?/', $token)) {
$callback(trim($className));
$className = '';
}
else if ($token == '{') {
} else if ($token == '{') {
$callback(trim($className));
return;
}
@@ -510,13 +511,13 @@ LICENSE;
break;
case T_IMPLEMENTS:
$extractor($iterator, function($fqn) use (&$implements) {
$extractor($iterator, function ($fqn) use (&$implements) {
$implements[] = $fqn;
});
break;
case T_EXTENDS:
$extractor($iterator, function($fqn) use (&$extends) {
$extractor($iterator, function ($fqn) use (&$extends) {
$extends[] = $fqn;
});
break;
+11 -11
View File
@@ -13,9 +13,9 @@
// -------------------------------------------------------------------------- //
// This script can be used to automatically generate a file with the scheleton
// of a test case to test a Redis command by specifying the name of the class
// in the Predis\Commands namespace (only classes in this namespace are valid).
// in the Predis\Command namespace (only classes in this namespace are valid).
// For example, to generate a test case for SET (which is represented by the
// Predis\Commands\StringSet class):
// Predis\Command\StringSet class):
//
// $ ./bin/generate-command-test.php --class=StringSet
//
@@ -33,8 +33,8 @@
// is explicitly specified.
// -------------------------------------------------------------------------- //
use Predis\Commands\ICommand;
use Predis\Commands\IPrefixable;
use Predis\Command\CommandInterface;
use Predis\Command\PrefixableCommandInterface;
class CommandTestCaseGenerator
{
@@ -92,8 +92,8 @@ class CommandTestCaseGenerator
throw new RuntimeException("Missing 'class' option.");
}
$options['fqn'] = "Predis\\Commands\\{$options['class']}";
$options['path'] = "Predis/Commands/{$options['class']}.php";
$options['fqn'] = "Predis\\Command\\{$options['class']}";
$options['path'] = "Predis/Command/{$options['class']}.php";
$source = __DIR__.'/../lib/'.$options['path'];
if (!file_exists($source)) {
@@ -129,8 +129,8 @@ class CommandTestCaseGenerator
if (!$reflection->isInstantiable()) {
throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
}
if (!$reflection->implementsInterface('Predis\Commands\ICommand')) {
throw new RuntimeException("Class $class must implement the Predis\Commands\ICommand interface.");
if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
}
$instance = $reflection->newInstance();
@@ -148,7 +148,7 @@ class CommandTestCaseGenerator
file_put_contents($options['output'], $this->generate());
}
protected function getTestCaseBuffer(ICommand $instance)
protected function getTestCaseBuffer(CommandInterface $instance)
{
$id = $instance->getId();
$fqn = get_class($instance);
@@ -167,7 +167,7 @@ class CommandTestCaseGenerator
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
use \PHPUnit_Framework_TestCase as StandardTestCase;
@@ -226,7 +226,7 @@ class $class extends CommandTestCase
PHP;
if ($instance instanceof IPrefixable) {
if ($instance instanceof PrefixableCommandInterface) {
$buffer .=<<<PHP
/**
+5 -1
View File
@@ -13,7 +13,11 @@
}
],
"require": {
"php": ">=5.3.0"
"php": ">=5.3.2"
},
"suggest": {
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
"ext-curl": "Allows access to Webdis when paired with phpiredis"
},
"autoload": {
"psr-0": {"Predis": "lib/"}
+19 -12
View File
@@ -13,17 +13,18 @@ require '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\IDistributionStrategy interface.
// that implements Predis\Distribution\DistributionStrategyInterface.
use Predis\Distribution\IDistributionStrategy;
use Predis\Network\PredisCluster;
use Predis\Connection\PredisCluster;
use Predis\Cluster\Distribution\DistributionStrategyInterface;
use Predis\Cluster\Hash\HashGeneratorInterface;
class NaiveDistributionStrategy implements IDistributionStrategy
class NaiveDistributionStrategy implements DistributionStrategyInterface, HashGeneratorInterface
{
private $nodes;
private $nodesCount;
public function __constructor()
public function __construct()
{
$this->nodes = array();
$this->nodesCount = 0;
@@ -37,7 +38,7 @@ class NaiveDistributionStrategy implements IDistributionStrategy
public function remove($node)
{
$this->nodes = array_filter($this->nodes, function($n) use($node) {
$this->nodes = array_filter($this->nodes, function ($n) use ($node) {
return $n !== $node;
});
@@ -46,24 +47,30 @@ class NaiveDistributionStrategy implements IDistributionStrategy
public function get($key)
{
$count = $this->nodesCount;
if ($count === 0) {
if (0 === $count = $this->nodesCount) {
throw new RuntimeException('No connections');
}
return $this->nodes[$count > 1 ? abs(crc32($key) % $count) : 0];
return $this->nodes[$count > 1 ? abs($key % $count) : 0];
}
public function generateKey($value)
public function hash($value)
{
return crc32($value);
}
public function getHashGenerator()
{
return $this;
}
}
$options = array(
'cluster' => function() {
'cluster' => function () {
$distributor = new NaiveDistributionStrategy();
return new PredisCluster($distributor);
$cluster = new PredisCluster($distributor);
return $cluster;
},
);
+1 -1
View File
@@ -60,7 +60,7 @@ class EventsListener implements Countable
$dispatcher->attachCallback('events', ($events = new EventsListener()));
// Attach a function to control the dispatcher loop termination with a message.
$dispatcher->attachCallback('control', function($payload) use ($dispatcher) {
$dispatcher->attachCallback('control', function ($payload) use ($dispatcher) {
if ($payload === 'terminate_dispatcher') {
$dispatcher->stop();
}
+12 -9
View File
@@ -13,14 +13,15 @@ require 'SharedConfigurations.php';
// Predis allows to set Lua scripts as read-only operations in the context of
// replication. This works for both EVAL and EVALSHA and also for the client-side
// abstraction built upon them (Predis\Commands\ScriptedCommand). This example
// abstraction built upon them (Predis\Command\ScriptedCommand). This example
// shows a slightly more complex configuration that injects a new scripted command
// in the server profile used by the new client instance and marks it marks it as
// a read-only operation for replication so that it will be executed on slaves.
use Predis\Profiles\ServerProfile;
use Predis\Commands\ScriptedCommand;
use Predis\Network\MasterSlaveReplication;
use Predis\Command\ScriptedCommand;
use Predis\Connection\MasterSlaveReplication;
use Predis\Profile\ServerProfile;
use Predis\Replication\ReplicationStrategy;
// ------------------------------------------------------------------------- //
@@ -50,15 +51,17 @@ $parameters = array(
);
$options = array(
'profile' => function($options) {
$profile = ServerProfile::get('dev');
'profile' => function ($options) {
$profile = ServerProfile::get('2.6');
$profile->defineCommand('hmgetall', 'HashMultipleGetAll');
return $profile;
},
'replication' => function($options) {
$replication = new MasterSlaveReplication();
$replication->setScriptReadOnly(HashMultipleGetAll::BODY);
'replication' => function ($options) {
$strategy = new ReplicationStrategy();
$strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
$replication = new MasterSlaveReplication($strategy);
return $replication;
},
+56
View File
@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// Operations such as LRANGE, ZRANGE and others can potentially generate replies
// containing a huge number of items. In some corner cases, such replies might
// end up exhausting the maximum allowed memory allocated for a PHP process.
// Multibulk iterators can be handy because they allow you to stream multibulk
// replies using plain old PHP iterators, making it possible to iterate them with
// a classic `foreach` loop and avoiding to consume an excessive amount of memory.
//
// PS: please note that multibulk iterators are supported only by the standard
// connection backend class (Predis\Connection\StreamConnection) and not the
// phpiredis-based one (Predis\Connection\PhpiredisConnection).
// Create a client and force the connection to use iterable multibulk responses.
$client = new Predis\Client($single_server + array('iterable_multibulk' => true));
// Prepare an hash with some fields and their respective values.
$client->hmset('metavars', array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut'));
// By default multibulk iterators iterate over the reply as a list of items...
foreach ($client->hgetall('metavars') as $index => $item) {
echo "[$index] $item\n";
}
/* OUTPUT:
[0] foo
[1] bar
[2] hoge
[3] piyo
[4] lol
[5] wut
*/
// ... but certain multibulk replies are better represented as lists of tuples.
foreach ($client->hgetall('metavars')->asTuple() as $index => $kv) {
list($key, $value) = $kv;
echo "[$index] $key => $value\n";
}
/* OUTPUT:
[0] foo => bar
[1] hoge => piyo
[2] lol => wut
*/
+1 -1
View File
@@ -33,7 +33,7 @@ function zpop($client, $key)
// which the client bails out with an exception.
);
$client->multiExec($options, function($tx) use ($key, &$element) {
$client->multiExec($options, function ($tx) use ($key, &$element) {
@list($element) = $tx->zrange($key, 0, 0);
if (isset($element)) {
+1 -1
View File
@@ -16,7 +16,7 @@ require 'SharedConfigurations.php';
$client = new Predis\Client($single_server);
$replies = $client->pipeline(function($pipe) {
$replies = $client->pipeline(function ($pipe) {
$pipe->ping();
$pipe->flushdb();
$pipe->incrby('counter', 10);
+2 -4
View File
@@ -38,12 +38,10 @@ foreach ($pubsub as $message) {
if ($message->payload == 'quit_loop') {
echo "Aborting pubsub loop...\n";
$pubsub->unsubscribe();
}
else {
} else {
echo "Received an unrecognized command: {$message->payload}.\n";
}
}
else {
} else {
echo "Received the following message from {$message->channel}:\n",
" {$message->payload}\n\n";
}
+30 -13
View File
@@ -14,36 +14,53 @@ require 'SharedConfigurations.php';
// This example will not work with versions of Redis < 2.6.
//
// Additionally to the EVAL command defined in the current development profile, the new
// Predis\Commands\ScriptedCommand base class can be used to build an higher abstraction
// Predis\Command\ScriptedCommand base class can be used to build an higher abstraction
// for our "scripted" commands so that they will appear just like any other command on
// the client-side. This is a quick example used to implement INCREX.
use Predis\Commands\ScriptedCommand;
use Predis\Command\ScriptedCommand;
class IncrementExistingKey extends ScriptedCommand
class IncrementExistingKeysBy extends ScriptedCommand
{
public function getKeysCount()
{
return 1;
// Tell Predis to use all the arguments but the last one as arguments
// for KEYS. The last one will be used to populate ARGV.
return -1;
}
public function getScript()
{
return
<<<LUA
local cmd = redis.call
if cmd('exists', KEYS[1]) == 1 then
return cmd('incr', KEYS[1])
end
local cmd, insert = redis.call, table.insert
local increment, results = ARGV[1], { }
for idx, key in ipairs(KEYS) do
if cmd('exists', key) == 1 then
insert(results, idx, cmd('incrby', key, increment))
else
insert(results, idx, false)
end
end
return results
LUA;
}
}
$client = new Predis\Client($single_server, 'dev');
$client = new Predis\Client($single_server);
$client->getProfile()->defineCommand('increx', 'IncrementExistingKey');
$client->getProfile()->defineCommand('increxby', 'IncrementExistingKeysBy');
$client->set('foo', 10);
$client->mset('foo', 10, 'foobar', 100);
var_dump($client->increx('foo')); // int(11)
var_dump($client->increx('bar')); // NULL
var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
/*
array (
0 => 60,
1 => NULL,
2 => 150,
)
*/
+5 -6
View File
@@ -11,9 +11,8 @@
require 'SharedConfigurations.php';
use Predis\ConnectionParameters;
use Predis\Commands\ICommand;
use Predis\Network\StreamConnection;
use Predis\Command\CommandInterface;
use Predis\Connection\StreamConnection;
class SimpleDebuggableConnection extends StreamConnection
{
@@ -27,7 +26,7 @@ class SimpleDebuggableConnection extends StreamConnection
parent::connect();
}
private function storeDebug(ICommand $command, $direction)
private function storeDebug(CommandInterface $command, $direction)
{
$firtsArg = $command->getArgument(0);
$timestamp = round(microtime(true) - $this->tstart, 4);
@@ -40,14 +39,14 @@ class SimpleDebuggableConnection extends StreamConnection
$this->debugBuffer[] = $debug;
}
public function writeCommand(ICommand $command)
public function writeCommand(CommandInterface $command)
{
parent::writeCommand($command);
$this->storeDebug($command, '->');
}
public function readResponse(ICommand $command)
public function readResponse(CommandInterface $command)
{
$reply = parent::readResponse($command);
$this->storeDebug($command, '<-');
+17 -12
View File
@@ -15,27 +15,32 @@ namespace Predis;
* Implements a lightweight PSR-0 compliant autoloader.
*
* @author Eric Naeseth <eric@thumbtack.com>
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Autoloader
{
private $baseDir;
private $directory;
private $prefix;
private $prefixLength;
/**
* @param string $baseDirectory Base directory where the source files are located.
*/
public function __construct($baseDirectory = null)
public function __construct($baseDirectory = __DIR__)
{
$this->baseDir = $baseDirectory ?: dirname(__FILE__);
$this->directory = $baseDirectory;
$this->prefix = __NAMESPACE__ . '\\';
$this->prefixLength = strlen($this->prefix);
}
/**
* Registers the autoloader class with the PHP SPL autoloader.
*
* @param boolean $prepend Prepend the autoloader on the stack instead of appending it.
*/
public static function register()
public static function register($prepend = false)
{
spl_autoload_register(array(new self, 'autoload'));
spl_autoload_register(array(new self, 'autoload'), true, $prepend);
}
/**
@@ -45,13 +50,13 @@ class Autoloader
*/
public function autoload($className)
{
if (0 !== strpos($className, $this->prefix)) {
return;
if (0 === strpos($className, $this->prefix)) {
$parts = explode('\\', substr($className, $this->prefixLength));
$filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
if (is_file($filepath)) {
require($filepath);
}
}
$relativeClassName = substr($className, strlen($this->prefix));
$classNameParts = explode('\\', $relativeClassName);
require_once $this->baseDir.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $classNameParts).'.php';
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Command\CommandInterface;
/**
* Defines the interface of a basic client object or abstraction that
* can send commands to Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface BasicClientInterface
{
/**
* Executes the specified Redis command.
*
* @param CommandInterface $command A Redis command.
* @return mixed
*/
public function executeCommand(CommandInterface $command);
}
+106 -78
View File
@@ -11,15 +11,19 @@
namespace Predis;
use Predis\Commands\ICommand;
use Predis\Options\IClientOptions;
use Predis\Network\IConnection;
use Predis\Network\IConnectionSingle;
use Predis\Profiles\IServerProfile;
use Predis\Options\ClientOptions;
use Predis\Profiles\ServerProfile;
use Predis\PubSub\PubSubContext;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptedCommand;
use Predis\Connection\AggregatedConnectionInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ConnectionFactory;
use Predis\Connection\ConnectionFactoryInterface;
use Predis\Monitor\MonitorContext;
use Predis\Option\ClientOptions;
use Predis\Option\ClientOptionsInterface;
use Predis\Pipeline\PipelineContext;
use Predis\Profile\ServerProfile;
use Predis\Profile\ServerProfileInterface;
use Predis\PubSub\PubSubContext;
use Predis\Transaction\MultiExecContext;
/**
@@ -27,9 +31,9 @@ use Predis\Transaction\MultiExecContext;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Client
class Client implements ClientInterface
{
const VERSION = '0.7.1';
const VERSION = '0.8.1';
private $options;
private $profile;
@@ -44,17 +48,16 @@ class Client
*/
public function __construct($parameters = null, $options = null)
{
$options = $this->filterOptions($options);
$this->options = $options;
$this->profile = $options->profile;
$this->connections = $options->connections;
$this->options = $this->filterOptions($options);
$this->profile = $this->options->profile;
$this->connections = $this->options->connections;
$this->connection = $this->initializeConnection($parameters);
}
/**
* Creates an instance of Predis\Options\ClientOptions from various types of
* arguments (string, array, Predis\Profiles\ServerProfile) or returns the
* passed object if it is an instance of Predis\Options\ClientOptions.
* Creates an instance of Predis\Option\ClientOptions from various types of
* arguments (string, array, Predis\Profile\ServerProfile) or returns the
* passed object if it is an instance of Predis\Option\ClientOptions.
*
* @param mixed $options Client options.
* @return ClientOptions
@@ -64,15 +67,14 @@ class Client
if ($options === null) {
return new ClientOptions();
}
if (is_array($options)) {
return new ClientOptions($options);
}
if ($options instanceof IClientOptions) {
if ($options instanceof ClientOptionsInterface) {
return $options;
}
if ($options instanceof IServerProfile || is_string($options)) {
return new ClientOptions(array('profile' => $options));
}
throw new \InvalidArgumentException("Invalid type for client options");
}
@@ -80,33 +82,29 @@ class Client
/**
* Initializes one or multiple connection (cluster) objects from various
* types of arguments (string, array) or returns the passed object if it
* implements the Predis\Network\IConnection interface.
* implements Predis\Connection\ConnectionInterface.
*
* @param mixed $parameters Connection parameters or instance.
* @return IConnection
* @return ConnectionInterface
*/
protected function initializeConnection($parameters)
{
if ($parameters instanceof IConnection) {
if ($parameters instanceof ConnectionInterface) {
return $parameters;
}
if (is_array($parameters) && isset($parameters[0])) {
$replication = isset($this->options->replication) && $this->options->replication;
$connection = $this->options->{$replication ? 'replication' : 'cluster'};
$initializer = $replication ? 'createReplication' : 'createCluster';
return $this->connections->$initializer($connection, $parameters, $this->profile);
return $this->connections->createAggregated($connection, $parameters);
}
return $this->connections->create($parameters, $this->profile);
return $this->connections->create($parameters);
}
/**
* Returns the server profile used by the client.
*
* @return IServerProfile
* {@inheritdoc}
*/
public function getProfile()
{
@@ -114,9 +112,7 @@ class Client
}
/**
* Returns the client options specified upon initialization.
*
* @return ClientOptions
* {@inheritdoc}
*/
public function getOptions()
{
@@ -126,7 +122,7 @@ class Client
/**
* Returns the connection factory object used by the client.
*
* @return IConnectionFactory
* @return ConnectionFactoryInterface
*/
public function getConnectionFactory()
{
@@ -140,13 +136,13 @@ class Client
*
* @return Client
*/
public function getClientFor($connectionAlias)
public function getClientFor($connectionID)
{
if (($connection = $this->getConnection($connectionAlias)) === null) {
throw new \InvalidArgumentException("Invalid connection alias: '$connectionAlias'");
if (($connection = $this->getConnectionById($connectionID)) === null) {
throw new \InvalidArgumentException("Invalid connection ID: '$connectionID'");
}
return new Client($connection, $this->options);
return new static($connection, $this->options);
}
/**
@@ -187,27 +183,30 @@ class Client
}
/**
* Returns the underlying connection instance or, when connected to a cluster,
* one of the connection instances identified by its alias.
*
* @param string $id The alias of a connection when connected to a cluster.
* @return IConnection
* {@inheritdoc}
*/
public function getConnection($id = null)
public function getConnection()
{
if (isset($id)) {
if (!Helpers::isAggregated($this->connection)) {
$message = 'Retrieving connections by alias is supported only with aggregated connections (cluster or replication)';
throw new NotSupportedException($message);
}
return $this->connection->getConnectionById($id);
}
return $this->connection;
}
/**
* Dinamically invokes a Redis command with the specified arguments.
* Retrieves a single connection out of an aggregated connections instance.
*
* @param string $connectionId Index or alias of the connection.
* @return SingleConnectionInterface
*/
public function getConnectionById($connectionId)
{
if (!$this->connection instanceof AggregatedConnectionInterface) {
throw new NotSupportedException('Retrieving connections by ID is supported only when using aggregated connections');
}
return $this->connection->getConnectionById($connectionId);
}
/**
* Dynamically invokes a Redis command with the specified arguments.
*
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
@@ -216,15 +215,21 @@ class Client
public function __call($method, $arguments)
{
$command = $this->profile->createCommand($method, $arguments);
return $this->connection->executeCommand($command);
$response = $this->connection->executeCommand($command);
if ($response instanceof ResponseObjectInterface) {
if ($response instanceof ResponseErrorInterface) {
$response = $this->onResponseError($command, $response);
}
return $response;
}
return $command->parseResponse($response);
}
/**
* Creates a new instance of the specified Redis command.
*
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
* @return ICommand
* {@inheritdoc}
*/
public function createCommand($method, $arguments = array())
{
@@ -232,35 +237,50 @@ class Client
}
/**
* Executes the specified Redis command.
*
* @param ICommand $command A Redis command.
* @return mixed
* {@inheritdoc}
*/
public function executeCommand(ICommand $command)
public function executeCommand(CommandInterface $command)
{
return $this->connection->executeCommand($command);
$response = $this->connection->executeCommand($command);
if ($response instanceof ResponseObjectInterface) {
if ($response instanceof ResponseErrorInterface) {
$response = $this->onResponseError($command, $response);
}
return $response;
}
return $command->parseResponse($response);
}
/**
* Executes the specified Redis command on all the nodes of a cluster.
* Handles -ERR responses returned by Redis.
*
* @param ICommand $command A Redis command.
* @return array
* @param CommandInterface $command The command that generated the error.
* @param ResponseErrorInterface $response The error response instance.
* @return mixed
*/
public function executeCommandOnShards(ICommand $command)
protected function onResponseError(CommandInterface $command, ResponseErrorInterface $response)
{
if (Helpers::isCluster($this->connection)) {
$replies = array();
if ($command instanceof ScriptedCommand && $response->getErrorType() === 'NOSCRIPT') {
$eval = $this->createCommand('eval');
$eval->setRawArguments($command->getEvalArguments());
foreach ($this->connection as $connection) {
$replies[] = $connection->executeCommand($command);
$response = $this->executeCommand($eval);
if (false === $response instanceof ResponseObjectInterface) {
$response = $command->parseResponse($response);
}
return $replies;
return $response;
}
return array($this->connection->executeCommand($command));
if ($this->options->exceptions === true) {
throw new ServerException($response->getMessage());
}
return $response;
}
/**
@@ -312,8 +332,16 @@ class Client
*/
protected function initPipeline(Array $options = null, $callable = null)
{
$pipeline = new PipelineContext($this, $options);
return $this->pipelineExecute($pipeline, $callable);
$executor = isset($options['executor']) ? $options['executor'] : null;
if (is_callable($executor)) {
$executor = call_user_func($executor, $this, $options);
}
$pipeline = new PipelineContext($this, $executor);
$replies = $this->pipelineExecute($pipeline, $callable);
return $replies;
}
/**
+66
View File
@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Connection\ConnectionInterface;
use Predis\Option\ClientOptionsInterface;
use Predis\Profile\ServerProfileInterface;
/**
* Interface defining the most important parts needed to create an
* high-level Redis client object that can interact with other
* building blocks of Predis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ClientInterface extends BasicClientInterface
{
/**
* Returns the server profile used by the client.
*
* @return ServerProfileInterface
*/
public function getProfile();
/**
* Returns the client options specified upon initialization.
*
* @return ClientOptionsInterface
*/
public function getOptions();
/**
* Opens the connection to the server.
*/
public function connect();
/**
* Disconnects from the server.
*/
public function disconnect();
/**
* Returns the underlying connection instance.
*
* @return ConnectionInterface
*/
public function getConnection();
/**
* Creates a new instance of the specified Redis command.
*
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
* @return CommandInterface
*/
public function createCommand($method, $arguments = array());
}
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Command\CommandInterface;
/**
* Interface for classes defining the strategy used to calculate an hash
* out of keys extracted from supported commands.
*
* This is mostly useful to support clustering via client-side sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface CommandHashStrategyInterface
{
/**
* Returns the hash for the given command using the specified algorithm, or null
* if the command cannot be hashed.
*
* @param CommandInterface $command Command to be hashed.
* @return int
*/
public function getHash(CommandInterface $command);
/**
* Returns the hash for the given key using the specified algorithm.
*
* @param string $key Key to be hashed.
* @return string
*/
public function getKeyHash($key);
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Distribution;
namespace Predis\Cluster\Distribution;
/**
* A distributor implements the logic to automatically distribute
@@ -17,7 +17,7 @@ namespace Predis\Distribution;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IDistributionStrategy extends INodeKeyGenerator
interface DistributionStrategyInterface
{
/**
* Adds a node to the distributor with an optional weight.
@@ -40,4 +40,11 @@ interface IDistributionStrategy extends INodeKeyGenerator
* @return mixed
*/
public function get($key);
/**
* Returns the underlying hash generator instance.
*
* @return Predis\Cluster\Hash\HashGeneratorInterface
*/
public function getHashGenerator();
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Distribution;
namespace Predis\Cluster\Distribution;
/**
* Exception class that identifies empty rings.
@@ -9,7 +9,9 @@
* file that was distributed with this source code.
*/
namespace Predis\Distribution;
namespace Predis\Cluster\Distribution;
use Predis\Cluster\Hash\HashGeneratorInterface;
/**
* This class implements an hashring-based distributor that uses the same
@@ -19,24 +21,26 @@ namespace Predis\Distribution;
* @author Daniele Alessandri <suppakilla@gmail.com>
* @author Lorenzo Castelli <lcastelli@gmail.com>
*/
class HashRing implements IDistributionStrategy
class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
{
const DEFAULT_REPLICAS = 128;
const DEFAULT_WEIGHT = 100;
private $nodes;
private $ring;
private $ringKeys;
private $ringKeysCount;
private $replicas;
private $nodeHashCallback;
private $nodes = array();
/**
* @param int $replicas Number of replicas in the ring.
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
*/
public function __construct($replicas = self::DEFAULT_REPLICAS)
public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
{
$this->replicas = $replicas;
$this->nodes = array();
$this->nodeHashCallback = $nodeHashCallback;
}
/**
@@ -101,6 +105,7 @@ class HashRing implements IDistributionStrategy
private function computeTotalWeight()
{
$totalWeight = 0;
foreach ($this->nodes as $node) {
$totalWeight += $node['weight'];
}
@@ -123,14 +128,14 @@ class HashRing implements IDistributionStrategy
$this->ring = array();
$totalWeight = $this->computeTotalWeight();
$nodesCount = count($this->nodes);
$nodesCount = count($this->nodes);
foreach ($this->nodes as $node) {
$weightRatio = $node['weight'] / $totalWeight;
$this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
}
ksort($this->ring, SORT_NUMERIC);
ksort($this->ring, SORT_NUMERIC);
$this->ringKeys = array_keys($this->ring);
$this->ringKeysCount = count($this->ringKeys);
}
@@ -161,7 +166,11 @@ class HashRing implements IDistributionStrategy
*/
protected function getNodeHash($nodeObject)
{
return (string) $nodeObject;
if ($this->nodeHashCallback === null) {
return (string) $nodeObject;
}
return call_user_func($this->nodeHashCallback, $nodeObject);
}
/**
@@ -170,7 +179,7 @@ class HashRing implements IDistributionStrategy
* @param string $value Input value.
* @return int
*/
public function generateKey($value)
public function hash($value)
{
return crc32($value);
}
@@ -199,13 +208,12 @@ class HashRing implements IDistributionStrategy
while ($lower <= $upper) {
$index = ($lower + $upper) >> 1;
$item = $ringKeys[$index];
if ($item > $key) {
$upper = $index - 1;
}
else if ($item < $key) {
} else if ($item < $key) {
$lower = $index + 1;
}
else {
} else {
return $item;
}
}
@@ -227,4 +235,12 @@ class HashRing implements IDistributionStrategy
// equal to the key. If no such item exists, return the last item.
return $upper >= 0 ? $upper : $ringKeysCount - 1;
}
/**
* {@inheritdoc}
*/
public function getHashGenerator()
{
return $this;
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Distribution;
namespace Predis\Cluster\Distribution;
/**
* This class implements an hashring-based distributor that uses the same
@@ -24,11 +24,11 @@ class KetamaPureRing extends HashRing
const DEFAULT_REPLICAS = 160;
/**
*
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
*/
public function __construct()
public function __construct($nodeHashCallback = null)
{
parent::__construct($this::DEFAULT_REPLICAS);
parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
}
/**
@@ -42,6 +42,7 @@ class KetamaPureRing extends HashRing
for ($i = 0; $i < $replicas; $i++) {
$unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
foreach ($unpackedDigest as $key) {
$ring[$key] = $nodeObject;
}
@@ -51,7 +52,7 @@ class KetamaPureRing extends HashRing
/**
* {@inheritdoc}
*/
public function generateKey($value)
public function hash($value)
{
$hash = unpack('V', md5($value, true));
return $hash[1];
@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster\Hash;
/**
* This class implements the CRC-CCITT-16 algorithm used by redis-cluster.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class CRC16HashGenerator implements HashGeneratorInterface
{
private static $CCITT_16 = array(
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
);
/**
* {@inheritdoc}
*/
public function hash($value)
{
// CRC-CCITT-16 algorithm
$crc = 0;
$CCITT_16 = self::$CCITT_16;
$strlen = strlen($value);
for ($i = 0; $i < $strlen; $i++) {
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 65535; // 0xFFFF
}
return $crc;
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Distribution;
namespace Predis\Cluster\Hash;
/**
* A generator of node keys implements the logic used to calculate the hash of
@@ -17,7 +17,7 @@ namespace Predis\Distribution;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface INodeKeyGenerator
interface HashGeneratorInterface
{
/**
* Generates an hash that is used by the distributor algorithm
@@ -25,5 +25,5 @@ interface INodeKeyGenerator
* @param string $value Value used to generate the hash.
* @return int
*/
public function generateKey($value);
public function hash($value);
}
@@ -0,0 +1,380 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptedCommand;
/**
* Default class used by Predis for client-side sharding to calculate
* hashes out of keys of supported commands.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PredisClusterHashStrategy implements CommandHashStrategyInterface
{
private $commands;
private $hashGenerator;
/**
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
*/
public function __construct(HashGeneratorInterface $hashGenerator)
{
$this->commands = $this->getDefaultCommands();
$this->hashGenerator = $hashGenerator;
}
/**
* Returns the default map of supported commands with their handlers.
*
* @return array
*/
protected function getDefaultCommands()
{
$keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
$keysAreAllArguments = array($this, 'getKeyFromAllArguments');
return array(
/* commands operating on the key space */
'EXISTS' => $keyIsFirstArgument,
'DEL' => $keysAreAllArguments,
'TYPE' => $keyIsFirstArgument,
'EXPIRE' => $keyIsFirstArgument,
'EXPIREAT' => $keyIsFirstArgument,
'PERSIST' => $keyIsFirstArgument,
'PEXPIRE' => $keyIsFirstArgument,
'PEXPIREAT' => $keyIsFirstArgument,
'TTL' => $keyIsFirstArgument,
'PTTL' => $keyIsFirstArgument,
'SORT' => $keyIsFirstArgument, // TODO
/* commands operating on string values */
'APPEND' => $keyIsFirstArgument,
'DECR' => $keyIsFirstArgument,
'DECRBY' => $keyIsFirstArgument,
'GET' => $keyIsFirstArgument,
'GETBIT' => $keyIsFirstArgument,
'MGET' => $keysAreAllArguments,
'SET' => $keyIsFirstArgument,
'GETRANGE' => $keyIsFirstArgument,
'GETSET' => $keyIsFirstArgument,
'INCR' => $keyIsFirstArgument,
'INCRBY' => $keyIsFirstArgument,
'SETBIT' => $keyIsFirstArgument,
'SETEX' => $keyIsFirstArgument,
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
'SETNX' => $keyIsFirstArgument,
'SETRANGE' => $keyIsFirstArgument,
'STRLEN' => $keyIsFirstArgument,
'SUBSTR' => $keyIsFirstArgument,
'BITOP' => array($this, 'getKeyFromBitOp'),
'BITCOUNT' => $keyIsFirstArgument,
/* commands operating on lists */
'LINSERT' => $keyIsFirstArgument,
'LINDEX' => $keyIsFirstArgument,
'LLEN' => $keyIsFirstArgument,
'LPOP' => $keyIsFirstArgument,
'RPOP' => $keyIsFirstArgument,
'RPOPLPUSH' => $keysAreAllArguments,
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
'LPUSH' => $keyIsFirstArgument,
'LPUSHX' => $keyIsFirstArgument,
'RPUSH' => $keyIsFirstArgument,
'RPUSHX' => $keyIsFirstArgument,
'LRANGE' => $keyIsFirstArgument,
'LREM' => $keyIsFirstArgument,
'LSET' => $keyIsFirstArgument,
'LTRIM' => $keyIsFirstArgument,
/* commands operating on sets */
'SADD' => $keyIsFirstArgument,
'SCARD' => $keyIsFirstArgument,
'SDIFF' => $keysAreAllArguments,
'SDIFFSTORE' => $keysAreAllArguments,
'SINTER' => $keysAreAllArguments,
'SINTERSTORE' => $keysAreAllArguments,
'SUNION' => $keysAreAllArguments,
'SUNIONSTORE' => $keysAreAllArguments,
'SISMEMBER' => $keyIsFirstArgument,
'SMEMBERS' => $keyIsFirstArgument,
'SPOP' => $keyIsFirstArgument,
'SRANDMEMBER' => $keyIsFirstArgument,
'SREM' => $keyIsFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $keyIsFirstArgument,
'ZCARD' => $keyIsFirstArgument,
'ZCOUNT' => $keyIsFirstArgument,
'ZINCRBY' => $keyIsFirstArgument,
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'ZRANGE' => $keyIsFirstArgument,
'ZRANGEBYSCORE' => $keyIsFirstArgument,
'ZRANK' => $keyIsFirstArgument,
'ZREM' => $keyIsFirstArgument,
'ZREMRANGEBYRANK' => $keyIsFirstArgument,
'ZREMRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANGE' => $keyIsFirstArgument,
'ZREVRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANK' => $keyIsFirstArgument,
'ZSCORE' => $keyIsFirstArgument,
'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
/* commands operating on hashes */
'HDEL' => $keyIsFirstArgument,
'HEXISTS' => $keyIsFirstArgument,
'HGET' => $keyIsFirstArgument,
'HGETALL' => $keyIsFirstArgument,
'HMGET' => $keyIsFirstArgument,
'HINCRBY' => $keyIsFirstArgument,
'HINCRBYFLOAT' => $keyIsFirstArgument,
'HKEYS' => $keyIsFirstArgument,
'HLEN' => $keyIsFirstArgument,
'HSET' => $keyIsFirstArgument,
'HSETNX' => $keyIsFirstArgument,
'HVALS' => $keyIsFirstArgument,
/* scripting */
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
);
}
/**
* Returns the list of IDs for the supported commands.
*
* @return array
*/
public function getSupportedCommands()
{
return array_keys($this->commands);
}
/**
* Sets an handler for the specified command ID.
*
* The signature of the callback must have a single parameter
* of type Predis\Command\CommandInterface.
*
* When the callback argument is omitted or NULL, the previously
* associated handler for the specified command ID is removed.
*
* @param string $commandId The ID of the command to be handled.
* @param mixed $callback A valid callable object or NULL.
*/
public function setCommandHandler($commandId, $callback = null)
{
$commandId = strtoupper($commandId);
if (!isset($callback)) {
unset($this->commands[$commandId]);
return;
}
if (!is_callable($callback)) {
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
}
$this->commands[$commandId] = $callback;
}
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
{
return $command->getArgument(0);
}
/**
* Extracts the key from a command with multiple keys only when all keys
* in the arguments array produce the same hash.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromAllArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameHashForKeys($arguments)) {
return $arguments[0];
}
}
/**
* Extracts the key from a command with multiple keys only when all keys
* in the arguments array produce the same hash.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
$keys = array();
for ($i = 0; $i < count($arguments); $i += 2) {
$keys[] = $arguments[$i];
}
if ($this->checkSameHashForKeys($keys)) {
return $arguments[0];
}
}
/**
* Extracts the key from BLPOP and BRPOP commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameHashForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
return $arguments[0];
}
}
/**
* Extracts the key from BITOP command.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBitOp(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameHashForKeys(array_slice($arguments, 1, count($arguments)))) {
return $arguments[1];
}
}
/**
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
$keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
if ($this->checkSameHashForKeys($keys)) {
return $arguments[0];
}
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
$keys = $command instanceof ScriptedCommand
? $command->getKeys()
: array_slice($args = $command->getArguments(), 2, $args[1]);
if ($keys && $this->checkSameHashForKeys($keys)) {
return $keys[0];
}
}
/**
* {@inheritdoc}
*/
public function getHash(CommandInterface $command)
{
$hash = $command->getHash();
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
$key = call_user_func($this->commands[$cmdID], $command);
if (isset($key)) {
$hash = $this->getKeyHash($key);
$command->setHash($hash);
}
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function getKeyHash($key)
{
$key = $this->extractKeyTag($key);
$hash = $this->hashGenerator->hash($key);
return $hash;
}
/**
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys Array of keys.
* @return Boolean
*/
protected function checkSameHashForKeys(Array $keys)
{
if (($count = count($keys)) === 0) {
return false;
}
$currentKey = $this->extractKeyTag($keys[0]);
for ($i = 1; $i < $count; $i++) {
$nextKey = $this->extractKeyTag($keys[$i]);
if ($currentKey !== $nextKey) {
return false;
}
$currentKey = $nextKey;
}
return true;
}
/**
* Returns only the hashable part of a key (delimited by "{...}"), or the
* whole key if a key tag is not found in the string.
*
* @param string $key A key.
* @return string
*/
protected function extractKeyTag($key)
{
if (false !== $start = strpos($key, '{')) {
if (false !== $end = strpos($key, '}', $start)) {
$key = substr($key, ++$start, $end - $start);
}
}
return $key;
}
}
@@ -0,0 +1,284 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Cluster\Hash\CRC16HashGenerator;
use Predis\Command\CommandInterface;
/**
* Default class used by Predis to calculate hashes out of keys of
* commands supported by redis-cluster.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RedisClusterHashStrategy implements CommandHashStrategyInterface
{
private $commands;
private $hashGenerator;
/**
*
*/
public function __construct()
{
$this->commands = $this->getDefaultCommands();
$this->hashGenerator = new CRC16HashGenerator();
}
/**
* Returns the default map of supported commands with their handlers.
*
* @return array
*/
protected function getDefaultCommands()
{
$keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
return array(
/* commands operating on the key space */
'EXISTS' => $keyIsFirstArgument,
'DEL' => array($this, 'getKeyFromAllArguments'),
'TYPE' => $keyIsFirstArgument,
'EXPIRE' => $keyIsFirstArgument,
'EXPIREAT' => $keyIsFirstArgument,
'PERSIST' => $keyIsFirstArgument,
'PEXPIRE' => $keyIsFirstArgument,
'PEXPIREAT' => $keyIsFirstArgument,
'TTL' => $keyIsFirstArgument,
'PTTL' => $keyIsFirstArgument,
'SORT' => $keyIsFirstArgument, // TODO
/* commands operating on string values */
'APPEND' => $keyIsFirstArgument,
'DECR' => $keyIsFirstArgument,
'DECRBY' => $keyIsFirstArgument,
'GET' => $keyIsFirstArgument,
'GETBIT' => $keyIsFirstArgument,
'MGET' => array($this, 'getKeyFromAllArguments'),
'SET' => $keyIsFirstArgument,
'GETRANGE' => $keyIsFirstArgument,
'GETSET' => $keyIsFirstArgument,
'INCR' => $keyIsFirstArgument,
'INCRBY' => $keyIsFirstArgument,
'SETBIT' => $keyIsFirstArgument,
'SETEX' => $keyIsFirstArgument,
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
'SETNX' => $keyIsFirstArgument,
'SETRANGE' => $keyIsFirstArgument,
'STRLEN' => $keyIsFirstArgument,
'SUBSTR' => $keyIsFirstArgument,
'BITCOUNT' => $keyIsFirstArgument,
/* commands operating on lists */
'LINSERT' => $keyIsFirstArgument,
'LINDEX' => $keyIsFirstArgument,
'LLEN' => $keyIsFirstArgument,
'LPOP' => $keyIsFirstArgument,
'RPOP' => $keyIsFirstArgument,
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
'LPUSH' => $keyIsFirstArgument,
'LPUSHX' => $keyIsFirstArgument,
'RPUSH' => $keyIsFirstArgument,
'RPUSHX' => $keyIsFirstArgument,
'LRANGE' => $keyIsFirstArgument,
'LREM' => $keyIsFirstArgument,
'LSET' => $keyIsFirstArgument,
'LTRIM' => $keyIsFirstArgument,
/* commands operating on sets */
'SADD' => $keyIsFirstArgument,
'SCARD' => $keyIsFirstArgument,
'SISMEMBER' => $keyIsFirstArgument,
'SMEMBERS' => $keyIsFirstArgument,
'SPOP' => $keyIsFirstArgument,
'SRANDMEMBER' => $keyIsFirstArgument,
'SREM' => $keyIsFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $keyIsFirstArgument,
'ZCARD' => $keyIsFirstArgument,
'ZCOUNT' => $keyIsFirstArgument,
'ZINCRBY' => $keyIsFirstArgument,
'ZRANGE' => $keyIsFirstArgument,
'ZRANGEBYSCORE' => $keyIsFirstArgument,
'ZRANK' => $keyIsFirstArgument,
'ZREM' => $keyIsFirstArgument,
'ZREMRANGEBYRANK' => $keyIsFirstArgument,
'ZREMRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANGE' => $keyIsFirstArgument,
'ZREVRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANK' => $keyIsFirstArgument,
'ZSCORE' => $keyIsFirstArgument,
/* commands operating on hashes */
'HDEL' => $keyIsFirstArgument,
'HEXISTS' => $keyIsFirstArgument,
'HGET' => $keyIsFirstArgument,
'HGETALL' => $keyIsFirstArgument,
'HMGET' => $keyIsFirstArgument,
'HINCRBY' => $keyIsFirstArgument,
'HINCRBYFLOAT' => $keyIsFirstArgument,
'HKEYS' => $keyIsFirstArgument,
'HLEN' => $keyIsFirstArgument,
'HSET' => $keyIsFirstArgument,
'HSETNX' => $keyIsFirstArgument,
'HVALS' => $keyIsFirstArgument,
/* scripting */
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
);
}
/**
* Returns the list of IDs for the supported commands.
*
* @return array
*/
public function getSupportedCommands()
{
return array_keys($this->commands);
}
/**
* Sets an handler for the specified command ID.
*
* The signature of the callback must have a single parameter
* of type Predis\Command\CommandInterface.
*
* When the callback argument is omitted or NULL, the previously
* associated handler for the specified command ID is removed.
*
* @param string $commandId The ID of the command to be handled.
* @param mixed $callback A valid callable object or NULL.
*/
public function setCommandHandler($commandId, $callback = null)
{
$commandId = strtoupper($commandId);
if (!isset($callback)) {
unset($this->commands[$commandId]);
return;
}
if (!is_callable($callback)) {
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
}
$this->commands[$commandId] = $callback;
}
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
{
return $command->getArgument(0);
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromAllArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 1) {
return $arguments[0];
}
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from BLPOP and BRPOP commands ensuring that only one key
* is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
$keys = $command instanceof ScriptedCommand
? $command->getKeys()
: array_slice($args = $command->getArguments(), 2, $args[1]);
if (count($keys) === 1) {
return $keys[0];
}
}
/**
* {@inheritdoc}
*/
public function getHash(CommandInterface $command)
{
$hash = $command->getHash();
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
$key = call_user_func($this->commands[$cmdID], $command);
if (isset($key)) {
$hash = $this->hashGenerator->hash($key);
$command->setHash($hash);
}
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function getKeyHash($key)
{
return $this->hashGenerator->hash($key);
}
}
@@ -9,17 +9,14 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
use Predis\Helpers;
use Predis\Distribution\INodeKeyGenerator;
namespace Predis\Command;
/**
* Base class for Redis commands.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class Command implements ICommand
abstract class AbstractCommand implements CommandInterface
{
private $hash;
private $arguments = array();
@@ -76,57 +73,21 @@ abstract class Command implements ICommand
}
/**
* Checks if the command can return an hash for client-side sharding.
*
* @return Boolean
* {@inheritdoc}
*/
protected function canBeHashed()
public function setHash($hash)
{
return isset($this->arguments[0]);
}
/**
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys Array of keys.
* @return Boolean
*/
protected function checkSameHashForKeys(Array $keys)
{
if (($count = count($keys)) === 0) {
return false;
}
$currentKey = Helpers::extractKeyTag($keys[0]);
for ($i = 1; $i < $count; $i++) {
$nextKey = Helpers::extractKeyTag($keys[$i]);
if ($currentKey !== $nextKey) {
return false;
}
$currentKey = $nextKey;
}
return true;
$this->hash = $hash;
}
/**
* {@inheritdoc}
*/
public function getHash(INodeKeyGenerator $distributor)
public function getHash()
{
if (isset($this->hash)) {
return $this->hash;
}
if ($this->canBeHashed()) {
$key = Helpers::extractKeyTag($this->arguments[0]);
$this->hash = $distributor->generateKey($key);
return $this->hash;
}
return null;
}
/**
@@ -149,6 +110,7 @@ abstract class Command implements ICommand
if (strlen($argument) > 32) {
$argument = substr($argument, 0, 32) . '[...]';
}
$accumulator .= " $argument";
return $accumulator;
@@ -9,15 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
use Predis\Distribution\INodeKeyGenerator;
namespace Predis\Command;
/**
* Defines an abstraction representing a Redis command.
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ICommand
interface CommandInterface
{
/**
* Gets the ID of a Redis command.
@@ -27,21 +25,33 @@ interface ICommand
public function getId();
/**
* Returns an hash of the command using the provided algorithm against the
* key (used to calculate the distribution of keys with client-side sharding).
* Set the hash for the command.
*
* @param INodeKeyGenerator $distributor Distribution algorithm.
* @return int
* @param int $hash Calculated hash.
*/
public function getHash(INodeKeyGenerator $distributor);
public function setHash($hash);
/**
* Sets the arguments of the command.
* Returns the hash of the command.
*
* @return int
*/
public function getHash();
/**
* Sets the arguments for the command.
*
* @param array $arguments List of arguments.
*/
public function setArguments(Array $arguments);
/**
* Sets the raw arguments for the command without processing them.
*
* @param array $arguments List of arguments.
*/
public function setRawArguments(Array $arguments);
/**
* Gets the arguments of the command.
*
@@ -9,19 +9,19 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/evalsha
* @link http://redis.io/commands/auth
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerEvalSHA extends ServerEval
class ConnectionAuth extends AbstractCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'EVALSHA';
return 'AUTH';
}
}
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/echo
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionEcho extends Command
class ConnectionEcho extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -24,12 +24,4 @@ class ConnectionEcho extends Command
{
return 'ECHO';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/ping
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionPing extends Command
class ConnectionPing extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -25,14 +25,6 @@ class ConnectionPing extends Command
return 'PING';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
/**
* {@inheritdoc}
*/
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/quit
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionQuit extends Command
class ConnectionQuit extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -24,12 +24,4 @@ class ConnectionQuit extends Command
{
return 'QUIT';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/select
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionSelect extends Command
class ConnectionSelect extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -24,12 +24,4 @@ class ConnectionSelect extends Command
{
return 'SELECT';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
use Predis\Helpers;
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hexists
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hget
@@ -9,9 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
use Predis\Iterators\MultiBulkResponseTuple;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hgetall
@@ -32,11 +30,8 @@ class HashGetAll extends PrefixableCommand
*/
public function parseResponse($data)
{
if ($data instanceof \Iterator) {
return new MultiBulkResponseTuple($data);
}
$result = array();
for ($i = 0; $i < count($data); $i++) {
$result[$data[$i]] = $data[++$i];
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
use Predis\Helpers;
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hincrby
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hincrbyfloat
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hkeys
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hlen
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hset
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hmset
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hsetnx
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/hvals
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
use Predis\Helpers;
@@ -17,7 +17,7 @@ use Predis\Helpers;
* @link http://redis.io/commands/del
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDelete extends Command implements IPrefixable
class KeyDelete extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -42,17 +42,4 @@ class KeyDelete extends Command implements IPrefixable
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
$args = $this->getArguments();
if (count($args) === 1) {
return true;
}
return $this->checkSameHashForKeys($args);
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/exists
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/expire
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/expireat
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/keys
@@ -24,12 +24,4 @@ class KeyKeys extends PrefixableCommand
{
return 'KEYS';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/keys
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/move
@@ -25,14 +25,6 @@ class KeyMove extends PrefixableCommand
return 'MOVE';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
/**
* {@inheritdoc}
*/
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/persist
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/pexpire
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/pexpireat
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/pttl
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/randomkey
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRandom extends Command
class KeyRandom extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -25,14 +25,6 @@ class KeyRandom extends Command
return 'RANDOMKEY';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
/**
* {@inheritdoc}
*/
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/rename
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRename extends Command implements IPrefixable
class KeyRename extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -32,12 +32,4 @@ class KeyRename extends Command implements IPrefixable
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/renamenx
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/sort
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeySort extends Command implements IPrefixable
class KeySort extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -44,20 +44,21 @@ class KeySort extends Command implements IPrefixable
if (isset($sortParams['GET'])) {
$getargs = $sortParams['GET'];
if (is_array($getargs)) {
foreach ($getargs as $getarg) {
$query[] = 'GET';
$query[] = $getarg;
}
}
else {
} else {
$query[] = 'GET';
$query[] = $getargs;
}
}
if (isset($sortParams['LIMIT']) && is_array($sortParams['LIMIT'])
&& count($sortParams['LIMIT']) == 2) {
if (isset($sortParams['LIMIT']) &&
is_array($sortParams['LIMIT']) &&
count($sortParams['LIMIT']) == 2) {
$query[] = 'LIMIT';
$query[] = $sortParams['LIMIT'][0];
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/ttl
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/type
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/lindex
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/linsert
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/llen
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/lpop
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/blpop
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopFirstBlocking extends Command implements IPrefixable
class ListPopFirstBlocking extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -34,6 +34,7 @@ class ListPopFirstBlocking extends Command implements IPrefixable
list($arguments, $timeout) = $arguments;
array_push($arguments, $timeout);
}
return $arguments;
}
@@ -44,14 +45,4 @@ class ListPopFirstBlocking extends Command implements IPrefixable
{
PrefixHelpers::skipLast($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return $this->checkSameHashForKeys(
array_slice(($args = $this->getArguments()), 0, count($args) - 1)
);
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/rpop
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/brpop
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/rpoplpush
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopLastPushHead extends Command implements IPrefixable
class ListPopLastPushHead extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -32,12 +32,4 @@ class ListPopLastPushHead extends Command implements IPrefixable
{
PrefixHelpers::all($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return $this->checkSameHashForKeys($this->getArguments());
}
}
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/brpoplpush
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ListPopLastPushHeadBlocking extends Command implements IPrefixable
class ListPopLastPushHeadBlocking extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -32,14 +32,4 @@ class ListPopLastPushHeadBlocking extends Command implements IPrefixable
{
PrefixHelpers::skipLast($this, $prefix);
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return $this->checkSameHashForKeys(
array_slice($args = $this->getArguments(), 0, count($args) - 1)
);
}
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/lpush
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/lpushx
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
use Predis\Helpers;
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/rpushx
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/lrange
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/lrem
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/lset
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/ltrim
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* Class that defines a few helpers method for prefixing keys.
@@ -21,10 +21,10 @@ class PrefixHelpers
/**
* Applies the specified prefix only the first argument.
*
* @param ICommand $command Command instance.
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function first(ICommand $command, $prefix)
public static function first(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
@@ -35,10 +35,10 @@ class PrefixHelpers
/**
* Applies the specified prefix to all the arguments.
*
* @param ICommand $command Command instance.
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function all(ICommand $command, $prefix)
public static function all(CommandInterface $command, $prefix)
{
$arguments = $command->getArguments();
@@ -52,10 +52,10 @@ class PrefixHelpers
/**
* Applies the specified prefix only to even arguments in the list.
*
* @param ICommand $command Command instance.
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function interleaved(ICommand $command, $prefix)
public static function interleaved(CommandInterface $command, $prefix)
{
$arguments = $command->getArguments();
$length = count($arguments);
@@ -68,12 +68,30 @@ class PrefixHelpers
}
/**
* Applies the specified prefix to all the arguments but the last one.
* Applies the specified prefix to all the arguments but the first one.
*
* @param ICommand $command Command instance.
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function skipLast(ICommand $command, $prefix)
public static function skipFirst(CommandInterface $command, $prefix)
{
$arguments = $command->getArguments();
$length = count($arguments);
for ($i = 1; $i < $length; $i++) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
/**
* Applies the specified prefix to all the arguments but the last one.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function skipLast(CommandInterface $command, $prefix)
{
$arguments = $command->getArguments();
$length = count($arguments);
@@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* Base class for Redis commands with prefixable keys.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class PrefixableCommand extends Command implements IPrefixable
abstract class PrefixableCommand extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -9,16 +9,14 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
use Predis\Distribution\INodeKeyGenerator;
namespace Predis\Command;
/**
* Defines a command whose keys can be prefixed.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IPrefixable
interface PrefixableCommandInterface
{
/**
* Prefixes all the keys found in the arguments of the command.
@@ -9,26 +9,26 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands\Processors;
namespace Predis\Command\Processor;
/**
* Defines an object that can process commands using command processors.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface IProcessingSupport
interface CommandProcessingInterface
{
/**
* Associates a command processor.
*
* @param ICommandProcessor $processor The command processor.
* @param CommandProcessorInterface $processor The command processor.
*/
public function setProcessor(ICommandProcessor $processor);
public function setProcessor(CommandProcessorInterface $processor);
/**
* Returns the associated command processor.
*
* @return ICommandProcessor
* @return CommandProcessorInterface
*/
public function getProcessor();
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands\Processors;
namespace Predis\Command\Processor;
/**
* A command processor chain processes a command using multiple chained command
@@ -17,21 +17,21 @@ namespace Predis\Commands\Processors;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ICommandProcessorChain extends ICommandProcessor, \IteratorAggregate, \Countable
interface CommandProcessorChainInterface extends CommandProcessorInterface, \IteratorAggregate, \Countable
{
/**
* Adds a command processor.
*
* @param ICommandProcessor $processor A command processor.
* @param CommandProcessorInterface $processor A command processor.
*/
public function add(ICommandProcessor $processor);
public function add(CommandProcessorInterface $processor);
/**
* Removes a command processor from the chain.
*
* @param ICommandProcessor $processor A command processor.
* @param CommandProcessorInterface $processor A command processor.
*/
public function remove(ICommandProcessor $processor);
public function remove(CommandProcessorInterface $processor);
/**
* Returns an ordered list of the command processors in the chain.
@@ -9,21 +9,21 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands\Processors;
namespace Predis\Command\Processor;
use Predis\Commands\ICommand;
use Predis\Command\CommandInterface;
/**
* A command processor processes commands before they are sent to Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ICommandProcessor
interface CommandProcessorInterface
{
/**
* Processes a Redis command.
*
* @param ICommand $command Redis command.
* @param CommandInterface $command Redis command.
*/
public function process(ICommand $command);
public function process(CommandInterface $command);
}
@@ -9,10 +9,10 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands\Processors;
namespace Predis\Command\Processor;
use Predis\Commands\ICommand;
use Predis\Commands\IPrefixable;
use Predis\Command\CommandInterface;
use Predis\Command\PrefixableCommandInterface;
/**
* Command processor that is used to prefix the keys contained in the arguments
@@ -20,7 +20,7 @@ use Predis\Commands\IPrefixable;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPrefixProcessor implements ICommandProcessor
class KeyPrefixProcessor implements CommandProcessorInterface
{
private $prefix;
@@ -55,10 +55,18 @@ class KeyPrefixProcessor implements ICommandProcessor
/**
* {@inheritdoc}
*/
public function process(ICommand $command)
public function process(CommandInterface $command)
{
if ($command instanceof IPrefixable) {
if ($command instanceof PrefixableCommandInterface) {
$command->prefixKeys($this->prefix);
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->getPrefix();
}
}
@@ -9,21 +9,21 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands\Processors;
namespace Predis\Command\Processor;
use Predis\Commands\ICommand;
use Predis\Command\CommandInterface;
/**
* Default implementation of a command processors chain.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ProcessorChain implements ICommandProcessorChain, \ArrayAccess
class ProcessorChain implements CommandProcessorChainInterface, \ArrayAccess
{
private $processors = array();
/**
* @param array $processors List of instances of ICommandProcessor.
* @param array $processors List of instances of CommandProcessorInterface.
*/
public function __construct($processors = array())
{
@@ -35,7 +35,7 @@ class ProcessorChain implements ICommandProcessorChain, \ArrayAccess
/**
* {@inheritdoc}
*/
public function add(ICommandProcessor $processor)
public function add(CommandProcessorInterface $processor)
{
$this->processors[] = $processor;
}
@@ -43,10 +43,9 @@ class ProcessorChain implements ICommandProcessorChain, \ArrayAccess
/**
* {@inheritdoc}
*/
public function remove(ICommandProcessor $processor)
public function remove(CommandProcessorInterface $processor)
{
$index = array_search($processor, $this->processors, true);
if ($index !== false) {
if (false !== $index = array_search($processor, $this->processors, true)) {
unset($this[$index]);
}
}
@@ -54,10 +53,9 @@ class ProcessorChain implements ICommandProcessorChain, \ArrayAccess
/**
* {@inheritdoc}
*/
public function process(ICommand $command)
public function process(CommandInterface $command)
{
$count = count($this->processors);
for ($i = 0; $i < $count; $i++) {
for ($i = 0; $i < $count = count($this->processors); $i++) {
$this->processors[$i]->process($command);
}
}
@@ -111,10 +109,10 @@ class ProcessorChain implements ICommandProcessorChain, \ArrayAccess
*/
public function offsetSet($index, $processor)
{
if (!$processor instanceof ICommandProcessor) {
if (!$processor instanceof CommandProcessorInterface) {
throw new \InvalidArgumentException(
'A processor chain can hold only instances of classes implementing '.
'the Predis\Commands\Preprocessors\ICommandProcessor interface'
'the Predis\Command\Processor\CommandProcessorInterface interface'
);
}
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Commands;
namespace Predis\Command;
/**
* @link http://redis.io/commands/publish
@@ -24,12 +24,4 @@ class PubSubPublish extends PrefixableCommand
{
return 'PUBLISH';
}
/**
* {@inheritdoc}
*/
protected function canBeHashed()
{
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More