Compare commits

...

139 Commits

Author SHA1 Message Date
Daniele Alessandri 4123fcd85d Update CHANGELOG and bump VERSION. 2014-08-01 11:43:10 +02:00
Daniele Alessandri 647cd4c502 Update CHANGELOG.
[ci skip]
2014-07-28 20:58:26 +02:00
Daniele Alessandri cab266f359 Move command "COMMAND" to the server profile for Redis 2.8.
This command has been backported from the "unstable" branch of Redis
to the "2.8" one, and it is available since 2.8.13.
2014-07-28 20:56:55 +02:00
Daniele Alessandri cea6354b55 Update README.
[ci skip]
2014-07-28 10:44:00 +02:00
Daniele Alessandri 7155dfdfb4 Update README.
[ci skip]
2014-07-27 15:49:49 +02:00
Daniele Alessandri dea768044c Update README.
[ci skip]
2014-07-25 16:33:03 +02:00
Daniele Alessandri 70d1c116b6 Update README.
[ci skip]
2014-07-25 13:23:04 +02:00
Daniele Alessandri 001fb45a32 Update README.
[ci skip]
2014-07-25 13:15:14 +02:00
Daniele Alessandri 8dd7f40d21 Fix read/write timeouts with PhpiredisStreamConnection.
This is a regression of commit 4db0017, using stream_socket_recvfrom()
bypasses stream wrappers thus read/write timeouts handled by PHP. The
only way to make them work again is to fetch the raw socket from the
stream resource and use socket_import_stream() which is unfortunately
a PHP 5.4+ function.
2014-07-23 12:43:29 +02:00
Daniele Alessandri d5c650d51a Update example to avoid breaking when run against Redis 3.0.
[ci skip]
2014-07-21 18:23:00 +02:00
Daniele Alessandri 7bb85cae1c Fix phpdocs. 2014-07-21 14:41:13 +02:00
Daniele Alessandri 43a6ae181e Fetch updated slots map from node indicated by -MOVED response.
This optimization makes it possible to fetch the slots map directly
from the server indicated by the -MOVED response eliminating the need
to use a random node in the pool, which in turn could require Predis
to open a new and useless connection.
2014-07-21 14:31:37 +02:00
Daniele Alessandri f538442ad9 Minor phpdocs fixes.
[ci skip]
2014-07-21 14:17:06 +02:00
Daniele Alessandri c2d0737e98 Fetch cluster slots from node upon first -MOVED response.
This is a more sane default as it allows users to indicate only a few
servers of the whole cluster composition, while it previously required
a more complex configuration of the client using client options.

This feature can be disabled using the "enableClusterNodes()" method.
2014-07-21 14:14:14 +02:00
Daniele Alessandri d6bd47ca47 Light rewording and reorganization of README.
[ci skip]
2014-07-21 10:43:38 +02:00
Daniele Alessandri 0235ba1a30 New command: COMMAND (Redis 3.0.0).
The command returns the response as is, but it would probably be not a
bad idea to parse it to restructure returned info using named arrays,
making it easier to access single commands and their info.

We will keep it as is for now, and investigate this change later.
2014-07-18 13:26:54 +02:00
Daniele Alessandri 287b4c76ce Rebase me 2014-07-18 13:12:23 +02:00
Daniele Alessandri ba6cfa7887 Add dedicated server profile for Redis 3.0. 2014-07-18 13:10:52 +02:00
Daniele Alessandri adb4d82ea8 Update CHANGELOG. 2014-07-17 16:21:29 +02:00
Daniele Alessandri 8dac9c097e Switch to CLUSTER SLOTS instead of CLUSTER NODES for redis-cluster.
CLUSTER SLOTS returns a structured response easier to handle compared
to the bulk response of CLUSTER NODES which must be parsed making the
whole thing more fragile.

CLUSTER SLOTS has been added in Redis 3.0.0b7.
2014-07-17 16:21:25 +02:00
Daniele Alessandri 0df05ae008 Run php-cs against source code. 2014-07-17 16:04:48 +02:00
Daniele Alessandri c85137924c Update CHANGELOG 2014-07-17 12:48:10 +02:00
Daniele Alessandri b1761c55c5 Use same strategy for both client-side sharding and redis-cluster.
This change is possible because, after a few changes in redis-cluster,
our default cluster strategy used for client side sharding and the one
used for redis-cluster turned out to be exactly the same, except for
the hashing function used to calculate distribution.

Actually some checks used to enforce correctness are redundant in the
context of redis-cluster (e.g. the one used to make sure that keys in
requests performing cross-keys operations will hash to the same slot,
which is performed by the server) so we could also add a more dumb and
permissive cluster strategy that relies on checks performed by Redis.

The only difference between the client-side sharding strategy and the
one used for redis-cluster, aside from the hash function, is in the
rules used for extracting hash tags from keys since empty tags {} are
considered valid by redis-cluster. In v1.0.0 the strategy used for our
client-side sharding cluster will reflect this change, but we will not
change it in v0.8 since it can be considered a breaking changes as it
can potentially affect existing cluster deployments.
2014-07-17 12:38:47 +02:00
Daniele Alessandri 5b8c2e21f6 Update CHANGELOG. 2014-07-17 12:28:51 +02:00
Daniele Alessandri f6e97d4b94 Implement PING in PUB/SUB loop abstraction for Redis 3.0.0. 2014-07-17 12:27:30 +02:00
Daniele Alessandri 18f5cea8e6 Update CHANGELOG.
[ci skip]
2014-07-16 19:12:35 +02:00
Daniele Alessandri 727072c086 Minor changes in checks for transaction support in server profile. 2014-07-16 11:31:15 +02:00
Daniele Alessandri 5fde716098 Back to development.
[ci skip]
2014-07-15 12:38:13 +02:00
Daniele Alessandri 239864d9a0 Update CHANGELOG and bump VERSION. 2014-07-15 11:54:33 +02:00
Daniele Alessandri b006d2306c Rewrite pear packager script.
We still use Onion's package.ini for the configuration of the package
to keep things simple, but we might switch to a more compact solution
in the future since we do not really need much of its features.
2014-07-15 11:39:42 +02:00
Daniele Alessandri 34e394cc10 Reword CHANGELOG. 2014-07-15 11:36:13 +02:00
Daniele Alessandri 79539c3bcf Update CHANGELOG. 2014-07-14 16:26:06 +02:00
Daniele Alessandri 74817b5596 Porperly handle -ERR instead of +QUEUED inside MULTI ... EXEC.
Error responses such as -OOM or -ERR on invalid arguments in commands
are returned immediatly instead of +QUEUED when using a transaction,
which is a condition that had not been tested enough. This condition
led to a bug in which Predis was not invalidating the transaction, so
when trying to create a new transaction Redis returned a "-ERR MULTI
calls can not be nested".

This commit fixes #187.
2014-07-14 16:23:18 +02:00
Daniele Alessandri f967ecd66b Merge remote-tracking branch 'github/pr/184' into v0.8 2014-07-07 11:48:01 +02:00
Daniele Alessandri 261d0b824f Update CHANGELOG. 2014-07-07 11:10:54 +02:00
Daniele Alessandri b77da8476c Backport fix for cursor-based iterator abstraction from PR #182. 2014-07-07 10:58:33 +02:00
Ian Babrou 3fbd29d00b prevent uncaught broken pipe error 2014-07-07 11:30:44 +04:00
Daniele Alessandri 8373663486 Use stream_socket_recvfrom() in PhpiredisStreamConnection.
Similarly to the socket-ext based connection using phpiredis, in our
stream based PhpiredisStreamConnection class we should read data from
the stream using stream_socket_recvfrom() instead of fread() because
the latter could block until a timeout is reached when the read buffer
contains less data then the specified length.

IMPORTANT: stream_socket_recvfrom() bypasses stream wrappers which
means that TLS/SSL, as requested by PR #158, won't ever work with
this connection class as the function returns the original encrypted
bytes.

This commit fixes issue #180.
2014-06-23 14:25:30 +02:00
Daniele Alessandri 18853d1dbb Just rename a couple of local variables. 2014-06-10 10:58:02 +02:00
Daniele Alessandri 1da827c0ae [tests] Use annotations to specify expected Redis server version.
This change targets only method annotations and aims to make the body
of tests more readable using the @requiresRedisVersion annotation.

Tests using this annotation requires to be assigned the "connected"
group of tests because they create a connection to the Redis instance
specified in phpunit.xml to fetch its the version.

This is a quick example of how this annotation can be used:

    /**
     * @group connected
     * @requiresRedisVersion >= 2.8.9
     */
    public function testExecutedOnlyWithMatchingRedisVersion()
    {
    }

Future improvements (currently not needed) include:

  * Same annotation working on a class-level (but still applied only
    to test methods with an explicitly assigned @group connected).

  * Ability to specify a version range.
2014-06-09 17:42:33 +02:00
Daniele Alessandri aa8fd5cea4 Update CHANGELOG. 2014-06-09 14:50:09 +02:00
Daniele Alessandri 8d353cc223 New command: ZREMRANGEBYLEX (Redis 2.8.9).
Closes #177.
2014-06-09 14:50:02 +02:00
Daniele Alessandri 0a580c9d1b New command: ZRANGEBYLEX (Redis 2.8.9).
Closes #175.
2014-06-09 14:49:54 +02:00
Daniele Alessandri 58d1cc12dc New command: ZLEXCOUNT (Redis 2.8.9).
Closes #176.
2014-06-09 14:49:27 +02:00
Daniele Alessandri a8862f0fdd Update CHANGELOG.
[ci skip]
2014-06-03 16:57:22 +02:00
Daniele Alessandri e148dc831a Fix parsing of the output of CLUSTER NODES to fetch slots map.
There was an error in presence of slaves in the cluster configuration,
see #165 for reference.
2014-06-03 16:54:00 +02:00
Daniele Alessandri c4b592298c Skip integration tests for HyperLogLog commands on Redis < 2.8.9.
Caught this by running the test suite on TravisCI which still runs an
older version of Redis 2.8. We should find a more decent way to handle
differences in profiles when commands get added between patch releases
of Redis, our custom skip method will do for now.
2014-05-30 12:22:12 +02:00
Daniele Alessandri 0e3b105959 New commands: PFADD, PFCOUNT, PFMERGE (Redis 2.8.9).
Backported from the master branch.

Many thanks to @rubensayshi for his initial commits on this in #163,
but I had to start from scratch for the master branch first.
2014-05-30 12:01:17 +02:00
Daniele Alessandri 749bbfe0e3 Update CHANGELOG.
[ci skip]

Meh
2014-05-30 11:41:20 +02:00
Daniele Alessandri c2ae1c612e Merge remote-tracking branch 'github/pr/159' into v0.8 2014-05-28 12:58:03 +02:00
Daniele Alessandri a85f89e2c7 Merge remote-tracking branch 'github/pr/170' into v0.8 2014-05-28 12:48:53 +02:00
thbourlove dbb802f0f1 use phpunit 4.x instead of 3.x 2014-05-26 22:57:40 +08:00
thbourlove 64b79511bd isntall requirement with a better way 2014-05-21 17:43:32 +08:00
thbourlove 5a9e747b01 add phpunit as dev requirement 2014-05-21 17:21:16 +08:00
Alexey Kupershtokh cb0fbd01b4 Enable INCRBYFLOAT in cluster mode 2014-03-28 22:12:43 +07:00
Daniele Alessandri 83df2ef11c Update CHANGELOG and README. 2014-03-25 15:02:22 +01:00
Daniele Alessandri c94e5d3a10 Merge remote-tracking branch 'thbourlove/v0.8' into v0.8 2014-03-25 14:51:22 +01:00
Daniele Alessandri c90fbfc0ff Failures for PHP 5.6 on Travis CI are not allowed anymore. 2014-03-25 14:31:04 +01:00
Daniele Alessandri 21d6f0e7c6 Update CHANGELOG. 2014-03-25 14:29:05 +01:00
Daniele Alessandri f4a950b275 Merge remote-tracking branch 'thbourlove/server_info' into v0.8 2014-03-25 14:26:10 +01:00
thbourlove 5ec51c899f original array format 2014-03-15 01:16:38 +08:00
thbourlove e1d700dd0c fix #154 2014-03-15 00:59:55 +08:00
Daniele Alessandri 809f76a7b3 Fix implementation for hash tags extraction from keys.
We now fully comply with the specifications defined by Redis.
2014-02-11 19:10:09 +01:00
Daniele Alessandri ab6cbe7e10 Add PHP 5.6 to the build matix of Travis-CI.
Builds are allowed to fail for now as PHP 5.6 is still in alpha stage.
2014-02-11 16:34:40 +01:00
Daniele Alessandri 80bcb9f475 Add HHVM to the build matrix of Travis-CI for v0.8.
Travis-CI recently provisioned virtual machines with HHVM 2.4.0 which
fixed the issues preventing Predis v0.8 from working correctly.
2014-02-11 16:29:44 +01:00
Daniele Alessandri c8e671a42a [HHVM] Work around a bug in class_exists() with non-string values. 2014-02-11 16:24:09 +01:00
Daniele Alessandri 838e6af403 [tests] Adapt for testing with HHVM. 2014-02-11 15:58:38 +01:00
Daniele Alessandri ba2ffb612a Add support for key hash tags in redis-cluster (Redis 3.0.0b1).
Multi-keys operations are not allowed even when keys generate the same
hash but this will probably be supported in later betas of Redis which
means we will basically end up reusing the whole strategy used for
client-side sharding.
2014-02-11 15:33:07 +01:00
Daniele Alessandri fdaf73987b Update CHANGELOG. 2014-02-11 12:44:00 +01:00
Daniele Alessandri 50f8922df2 Update README about HHVM compatibility. 2014-02-11 12:44:00 +01:00
Daniele Alessandri 60733eb5c9 Make Predis v0.8 compatible with HHVM 2.4.0.
Since we cannot rely on the PHP version to detect the availability of
socket_import_stream(), we switched to function_exists(). As an added
bonus, using function_exists() is twice faster.
2014-02-11 12:44:00 +01:00
Daniele Alessandri a13dd4e180 Back to development. 2014-02-11 12:44:00 +01:00
thbourlove 1678e486b1 use 2.8 as the default server version 2014-01-25 10:55:47 +08:00
Daniele Alessandri 5f2eea628e Update CHANGELOG and bump VERSION. 2014-01-16 15:10:29 +01:00
Daniele Alessandri 0f01b89d44 Bump year in LICENSE.
[ci skip]
2014-01-10 17:46:58 +01:00
Daniele Alessandri 9d0201ed9e [bin] Add missing executable in the repository for new script. 2014-01-08 17:38:22 +01:00
Daniele Alessandri 0cead0245d [bin] Add helper script to automate PEAR package creation.
Note that this script takes care of customizing `phpunit.xml.dist` so
that tests can be run as soon as the PEAR package has been installed
as requested in #126.
2014-01-08 17:30:54 +01:00
Daniele Alessandri 0a4eed1dff [tests] Use Predis\Autoload from include_path when not in repository.
This is useful only for running the test suite after installing the
PEAR package, see #126 for reference.
2014-01-08 17:22:15 +01:00
Daniele Alessandri d2debfd43e Apply some more minor phpdoc fixes. 2013-12-22 17:51:18 +01:00
Daniele Alessandri 9a1884d960 Merge branch 'v0.8-backport-fixes' into v0.8 2013-12-22 17:35:36 +01:00
Daniele Alessandri a6dd685f8d Run php-cs against source code. 2013-12-22 17:34:50 +01:00
Daniele Alessandri f499d5695a Explicitly return null value. 2013-12-22 17:34:50 +01:00
Daniele Alessandri 23c4b95a0b Remove useless return statement.
Backported from da8a203 (master).
2013-12-22 17:34:50 +01:00
Daniele Alessandri 80cb31514a [tests] Remove unused "use" directive. 2013-12-22 17:34:50 +01:00
Daniele Alessandri 6306509086 [tests] Fix a couple of innocuous glitches. 2013-12-22 17:34:50 +01:00
Daniele Alessandri 2aab474670 Apply more phpdoc fixes.
Backported from 6a577a0 (master).
2013-12-22 17:34:50 +01:00
Daniele Alessandri d628c0f23b [tests] Fix a couple of glitches in our base PHPUnit classes. 2013-12-22 17:34:50 +01:00
Daniele Alessandri ad92618003 [tests] No need to reassign $exception.
Backported from 728d9dc (master).
2013-12-22 17:34:49 +01:00
Daniele Alessandri 0fb7674762 Fix phpdocs for fluent interfaces.
Backported from a092f6d (master).

[ci skip]
2013-12-22 17:34:49 +01:00
Daniele Alessandri e778baa491 Make Pipeline::executeCommand() return $this for fluent interface.
Backported from 3befbb3 (master).
2013-12-22 17:34:49 +01:00
Daniele Alessandri d88a280977 Make PhpiredisConnection::getAddress() static and protected.
This method is mostly an utility method which is the main reason for
it bein static, but can be useful to have it available when extending
this class.

This change was inspired by b9b899e (master) even though the original
commit is meant to fix an issue not affecting v0.8.
2013-12-22 17:34:49 +01:00
Daniele Alessandri eb6cbef4e7 Remove erroneous duplication of INFO in Redis profiles.
This oversight didn't end up in a blatant bug only because the correct
handler definition for INFO replaced the old class in the profile.

Backported from 5065541 (master).
2013-12-22 17:34:49 +01:00
Daniele Alessandri 2aa771004d Add a note about missing break in switch statement.
[ci skip]
2013-12-22 17:34:49 +01:00
Daniele Alessandri 43bf3096a0 Fix some errors in phpdocs.
Backported from 25cd430 (master).
2013-12-22 17:34:40 +01:00
Daniele Alessandri 317fa42891 Improve handling of -MOVED and -ASK responses.
This change improves code reusing and simplifies the internals of
our redis-cluster connection backend.
2013-12-21 12:00:44 +01:00
Daniele Alessandri b45f87caf2 [tests] Add $message as third parameter for our custom assertions. 2013-12-19 11:51:45 +01:00
Daniele Alessandri b7cba07014 Update README.
[ci skip]
2013-12-18 16:09:01 +01:00
Daniele Alessandri 56627f30f8 Run php-cs against test suite. 2013-12-17 13:00:39 +01:00
Daniele Alessandri 9d4524cae7 Run php-cs against codebase. 2013-12-17 12:59:31 +01:00
Daniele Alessandri fd1c6f244f Apply minor change in command serialization. 2013-12-16 18:52:11 +01:00
Daniele Alessandri aa4d0a05ac Update README.
[ci skip]
2013-12-16 17:05:22 +01:00
Daniele Alessandri d45b5f7ec8 Update FAQ.
[ci skip]
2013-12-16 16:59:23 +01:00
Daniele Alessandri 87ca5a1a49 Update testing README.
[ci skip]
2013-12-16 11:45:50 +01:00
Daniele Alessandri e5ec950a11 [bin] Drop extension from scripts in the bin directory. 2013-12-16 11:26:45 +01:00
Daniele Alessandri 3f2c2b9031 [bin] Handle duplicate class names or aliases in same namespace.
This condition can occur when merging multiple class files (with their
own independent "use" directives) in one big file: when imported class
names or aliases clashes, the script now tries to automatically rename
them by defining a new alias in the "use" directive and renaming the
occurences in the buffer containing the class code.

This fix is naive at best, but seems to work fine so we will just live
with it for now.
2013-12-16 11:26:34 +01:00
Daniele Alessandri 88ea08fe83 [bin] Fix broken rendering of "use ... as ..." in output. 2013-12-16 11:26:21 +01:00
Daniele Alessandri bd48225edc Set the test suite to run against Redis 2.8 on Travis CI.
Please note that the distributed phpunit.xml.dist still targets Redis
2.6 to match the default server profile used by Predis v0.8.
2013-12-14 20:47:23 +01:00
Daniele Alessandri ea9809d4e8 Use path to differentiate persistent TCP streams.
Closes #139.

Backported from master (commit e7f2d28).
2013-12-14 14:21:47 +01:00
Daniele Alessandri 09cb6677e2 Improve URI parsing for connection parameters.
Using PHP's "parse_str()" to parse the query string is slightly more
efficient then our own code especially when the number of fields in
the query string grows, with the additional benefit of supporting
arrays for values when brackets are present in fieldnames.

So after this commit, providing this URI string:

  $string = 'tcp://127.0.0.1?metavars[]=foo&metavars[]=hoge';

Is equivalent to providing the following named array:

  $array = [
    'scheme' => 'tcp',
    'host' => '127.0.0.1',
    'metavars' => ['foo', 'hoge'],
  ];

Other improvements are that parsing does not break when the value of a
field contains one or more "=" and empty or incomplete "key=value"
pairs result in an empty string for "key".
2013-12-11 13:10:05 +01:00
Daniele Alessandri c32f6c7152 Add "path" in phpdoc of connections supporting UNIX domain sockets. 2013-12-11 12:21:09 +01:00
Daniele Alessandri 78fd475747 Apply another minor fix to README.
[ci skip]
2013-12-10 22:16:09 +01:00
Daniele Alessandri 86a4343e59 Fix README.
[ci skip]
2013-12-10 22:01:42 +01:00
Daniele Alessandri ecf1cc07e5 Tweak CONTRIBUTING.md.
[ci skip]
2013-12-09 12:22:42 +01:00
Daniele Alessandri 2fc1ea88cd Last minor tweaks in the README.
Hope I won't need to touch README in v0.8 for a while.

[ci skip]
2013-12-09 12:03:47 +01:00
Daniele Alessandri 72e9187662 More README tweaks.
[ci skip]
2013-12-09 11:53:58 +01:00
Daniele Alessandri 98410bbcaa Pad headers lines in CHANGELOG.
[ci skip]
2013-12-09 11:47:57 +01:00
Daniele Alessandri 5ea1d6fe92 Backport rewrite of README from master (v0.9) with necessary changes.
[ci skip]
2013-12-09 11:42:09 +01:00
Daniele Alessandri 73780da52d Backport improvements for redis-cluster from master (v0.9).
List of changes:

  - The cluster connection sends an ASKING command before retrying
    a command on a different node when Redis returns a -ASK response.
  - The cluster connection can be initialized with a partial list of
    nodes, the full slots map will be fetched from Redis itself using
    the CLUSTER NODES command.
  - The slots map can be optionally retrieved from Redis if the server
    returns a -MOVE response, otherwise only the interested slot will
    be permanently reassigned to the new target node.
  - It is possible to specify a set of common parameters applied to
    connections created on the fly when not part of the current pool
    upon -MOVE and -ASK responses returned by Redis for redirections.
  - $cluster->connect() connects to a random connection in the pool
    instead of forcing the connect operation on all the connections.
2013-12-08 15:57:15 +01:00
Daniele Alessandri 381023ad8f Make sure raw commands IDs are normalized to uppercase. 2013-12-02 11:44:40 +01:00
Daniele Alessandri c3c819cc9c [tests] Minor tweak for timeouts. 2013-12-02 11:44:37 +01:00
Daniele Alessandri 987f27556f Merge branch 'backport/test-suite-enhancements' into v0.8 2013-12-01 15:21:05 +01:00
Daniele Alessandri a9b42924e5 [tests] Improve the basic framework of our test suite.
We now have a base test case class for Predis (namely PredisTestCase)
grouping various commonly used utility methods shared by all of the
tests in the suite, greatly improving reusability.

NOTE: Backported from v0.9-dev.
2013-12-01 15:19:51 +01:00
Daniele Alessandri e4e2238f34 Merge branch 'backport/tests-command-constraint' into v0.8 2013-12-01 14:06:31 +01:00
Daniele Alessandri e1f6c8644f [tests] Normalize casing of command identifiers in constraint.
We do not care much about the casing of command IDs in our constraint,
so it makes no difference for use if it is "SET" or "set".
2013-12-01 14:01:37 +01:00
Daniele Alessandri d16a1c8705 [tests] Add a PHPUnit constraint to verify commands.
Commands are verified by checking their IDs and optionally arguments.
Passing a command instance is only a shortcut to set the expected ID
and arguments, commands are never compared for identity but always for
equivalence.
2013-12-01 13:59:27 +01:00
Daniele Alessandri 32aff28ef5 [tests] Fix stupid change. 2013-11-30 14:58:00 +01:00
Daniele Alessandri eb906bb29c Update CHANGELOG.
[ci skip]
2013-11-30 14:53:08 +01:00
Daniele Alessandri 33ef043b8e [tests] Update tests for Predis\Command\AbstractCommand. 2013-11-30 14:48:26 +01:00
Daniele Alessandri 973c9a7031 Add new command class useful to execute "raw" Redis commands.
By raw we mean that input arguments are not filtered and responses are
not parsed, which means arguments must follow the command signature as
defined by Redis and complex responses are left untouched.

When instantiating an instance of `Predis\Command\RawCommand` you must
pass at least the command ID. You can pass further arguments in the
array or you can just set them later with `RawCommand::setArguments()`
but you cannot modify the command ID once instantiated.

  $command = new Predis\Command\RawCommand(['SET', 'foo', 'bar']);
  $response = $client->executeCommand($command);

While higher level abstractions built upon `Predis\Client` should just
use commands created by the profile in use, inner parts of the library
might use raw commands to provide certain functionalities making sure
that input and output of commands are always consistent, independent
of the profile.

NOTE: Backported from v0.9.
2013-11-30 14:37:15 +01:00
Daniele Alessandri db55fae961 Fix phpdoc.
[ci skip]
2013-11-30 14:17:59 +01:00
Daniele Alessandri 5192005862 [tests] Fix tests on PHP 5.3.
Stupid PHP 5.3.
2013-11-29 23:41:05 +01:00
Daniele Alessandri cdc6075b01 [tests] Improve tests of TTL. 2013-11-29 23:33:17 +01:00
Daniele Alessandri 623d1cc1a2 [tests] Improve base command test case. 2013-11-29 23:28:23 +01:00
Daniele Alessandri d53069f096 [tests] Add missing tests for SETNX.
For some obscure reason Predis/Command/StringSetPreserve had no tests.
2013-11-29 22:53:20 +01:00
Daniele Alessandri 8565a42605 [tests] Add "count" argument to SRANDMEMBER (since Redis >= 2.6). 2013-11-29 22:46:32 +01:00
Daniele Alessandri aac811f897 Fix phpdoc.
[ci skip]
2013-11-29 11:14:14 +01:00
Daniele Alessandri 4e664c6c90 Address #133 by reusing our own methods.
The main reason behind that code duplication was performance related
as we tried to reduce method calls when possible, even at the cost of
falling into the realm of early optimizations. Apparently we just lose
~400 req/sec on a 21000 req/sec basis ("SET foo bar") using PHP 5.5.3
(packaged by Ubuntu 13.10) on an Intel Q6600, so we will most likely
stick with this change for the sake of best practices.

Backported from v0.9-dev.
2013-11-22 15:17:50 +01:00
Daniele Alessandri b1a51b2ef9 Update CHANGELOG. 2013-11-09 20:00:33 +01:00
Daniele Alessandri de02c81398 Deprecate Predis\Client::multiExec().
This method will be replaced by Predis\Client::transaction() in the
next major release of Predis.
2013-11-09 20:00:29 +01:00
345 changed files with 5196 additions and 2350 deletions
+11 -1
View File
@@ -3,6 +3,8 @@ php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
branches:
except:
- v0.5
@@ -10,4 +12,12 @@ branches:
- php5.2_backport
- documentation
services: redis-server
script: phpunit -c phpunit.xml.travisci
before_script:
- composer self-update
- composer install --no-interaction --prefer-source --dev
script:
- vendor/bin/phpunit -c phpunit.xml.travisci
matrix:
allow_failures:
- php: hhvm
fast_finish: true
+116 -26
View File
@@ -1,8 +1,73 @@
v0.8.5 (2013-xx-xx)
===============================================================================
v0.8.7 (2014-08-01)
================================================================================
- Added `2.8` in the server profiles aliases list for the upcoming Redis 2.8.
`2.6` is still the default server profile. `dev` now targets Redis 3.0.
- Added `3.0` in the server profiles aliases list for Redis 3.0. `2.8` is still
the default server profile and `dev` still targets Redis 3.0.
- Added `COMMAND` to the server profile for Redis 2.8.
- Switched internally to the `CLUSTER SLOTS` command instead of `CLUSTER NODES`
to fetch the updated slots map from redis-cluster. This change requires users
to upgrade Redis nodes to >= 3.0.0b7.
- The updated slots map is now fetched automatically from redis-cluster upon the
first `-MOVED` response by default. This change makes it possible to feed the
client constructor with only a few nodes of the actual cluster composition,
without needing a more complex configuration.
- Implemented support for `PING` in PUB/SUB loop for Redis >= 3.0.0b8.
- The default client-side sharding strategy and the one for redis-cluster now
share the same implementations as they follow the same rules. One difference,
aside from the different hashing function used to calculate distribution, is
in how empty hash tags like {} are treated by redis-cluster.
- __FIX__: the patch applied to fix #180 introduced a regression affecting read/
write timeouts in `Predis\Connection\PhpiredisStreamConnection`. Unfortunately
the only possible solution requires PHP 5.4+. On PHP 5.3, read/write timeouts
will be ignored from now on.
v0.8.6 (2014-07-15)
================================================================================
- Redis 2.8 is now the default server profile as there are no changes that would
break compatibility with previous releases.
- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for
handling the HyperLogLog data structure introduced in Redis 2.8.9.
- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for
Redis 2.8 for handling lexicographic operations on members of sorted sets.
- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1).
- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0.
- __FIX__: responses to `INFO` are now properly parsed and will not break when
redis sentinel is being used (ISSUE #154).
- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication
configurations (ISSUE #159).
- __FIX__: fix parsing of the output of `CLUSTER NODES` to fetch the slots map
from a node when redis-cluster has slaves in its configuration (ISSUE #165).
- __FIX__: prevent a stack overflow when iterating over large Redis collections
using our abstraction for cursor-based iterators (ISSUE #182).
- __FIX__: properly discards transactions when the server immediately returns an
error response (e.g. -OOM or -ERR on invalid arguments for a command) instead
of a +QUEUED response (ISSUE #187).
- Upgraded to PHPUnit 4.* for the test suite.
v0.8.5 (2014-01-16)
================================================================================
- Added `2.8` in the server profiles aliases list for Redis 2.8. `2.6` is still
the default server profile and `dev` now targets Redis 3.0.
- Added `SCAN`, `SSCAN`, `ZSCAN`, `HSCAN` to the server profile for Redis 2.8.
@@ -14,14 +79,39 @@ v0.8.5 (2013-xx-xx)
- hashes (cursor-based iterator using `HSCAN`)
- lists (plain iterator using `LRANGE`)
- `Predis\Client::pubSubLoop()` should now be used instead of the deprecated
`Predis\Client::pubSub()` (which still works like usual to avoid B/C breaks).
`Predis\Client::pubSub()` will change in the next major version of Predis to
support the new PUBSUB command recently added in Redis 2.8.
- It is now possible to execute "raw commands" using `Predis\Command\RawCommand`
and a variable list of command arguments. Input arguments are not filtered and
responses are not parsed, which means arguments must follow the signature of
the command as defined by Redis and complex responses are left untouched.
- URI parsing for connection parameters has been improved and has slightly less
overhead when the number of fields in the querystring grows. New features are:
- Parsing does not break when value of a field contains one or more "=".
- Repeated fieldnames using [] produce an array of values.
- Empty or incomplete "key=value" pairs result in an empty string for "key".
- Various improvements and fixes to the redis-cluster connection backend:
- __FIX__: the `ASKING` command is sent upon -ASK redirections.
- An updated slots-map can be fetched from nodes using the `CLUSTER NODES`
command. By default this is a manual operation but can be enabled to get
automatically done upon -MOVED redirections.
- It is possible to specify a common set of connection parameters that are
applied to connections created on the fly upon redirections to nodes not
part of the initial pool.
- List of deprecated methods:
- `Predis\Client::multiExec()`: superseded by `Predis\Client::transaction()`
and to be removed in the next major release.
- `Predis\Client::pubSub()`: superseded by `Predis\Client::pubSubLoop()` and
to be removed in the next major release. This change was needed due to the
recently introduced `PUBSUB` command in Redis 2.8.
v0.8.4 (2013-07-27)
===============================================================================
================================================================================
- Added `DUMP` and `RESTORE` to the server profile for Redis 2.6.
@@ -45,7 +135,7 @@ v0.8.4 (2013-07-27)
v0.8.3 (2013-02-18)
===============================================================================
================================================================================
- Added `CLIENT SETNAME` and `CLIENT GETNAME` (ISSUE #102).
@@ -74,7 +164,7 @@ v0.8.3 (2013-02-18)
v0.8.2 (2013-02-03)
===============================================================================
================================================================================
- Added `Predis\Session\SessionHandler` to make it easy to store PHP sessions
on Redis using Predis. Please note that this class needs either PHP >= 5.4.0
@@ -89,7 +179,7 @@ v0.8.2 (2013-02-03)
v0.8.1 (2013-01-19)
===============================================================================
================================================================================
- The `connections` client option can now accept a callable object returning
an instance of `Predis\Connection\ConnectionFactoryInterface`.
@@ -121,7 +211,7 @@ v0.8.1 (2013-01-19)
v0.8.0 (2012-10-23)
===============================================================================
================================================================================
- The default server profile for Redis is now `2.6`.
@@ -186,7 +276,7 @@ v0.8.0 (2012-10-23)
v0.7.3 (2012-06-01)
===============================================================================
================================================================================
- New commands available in the Redis v2.6 profile (dev): `BITOP`, `BITCOUNT`.
@@ -208,7 +298,7 @@ v0.7.3 (2012-06-01)
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.
@@ -226,7 +316,7 @@ v0.7.2 (2012-04-01)
v0.7.1 (2011-12-27)
===============================================================================
================================================================================
- The PEAR channel on PearHub has been deprecated in favour of `pear.nrk.io`.
@@ -245,7 +335,7 @@ v0.7.1 (2011-12-27)
v0.7.0 (2011-12-11)
===============================================================================
================================================================================
- Predis now adheres to the PSR-0 standard which means that there is no more a
single file holding all the classes of the library, but multiple files (one
@@ -319,7 +409,7 @@ v0.7.0 (2011-12-11)
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
@@ -358,7 +448,7 @@ v0.6.6 (2011-04-01)
v0.6.5 (2011-02-12)
===============================================================================
================================================================================
- __FIX__: due to an untested internal change introduced in v0.6.4, a wrong
handling of bulk reads of zero-length values was producing protocol
@@ -366,7 +456,7 @@ v0.6.5 (2011-02-12)
v0.6.4 (2011-02-12)
===============================================================================
================================================================================
- Various performance improvements (15% ~ 25%) especially when dealing with
long multibulk replies or when using clustered connections.
@@ -380,7 +470,7 @@ v0.6.4 (2011-02-12)
v0.6.3 (2011-01-01)
===============================================================================
================================================================================
- New commands available in the Redis v2.2 profile (dev):
- Strings: `SETRANGE`, `GETRANGE`, `SETBIT`, `GETBIT`
@@ -394,7 +484,7 @@ v0.6.3 (2011-01-01)
v0.6.2 (2010-11-28)
===============================================================================
================================================================================
- Minor internal improvements and clean ups.
@@ -419,7 +509,7 @@ v0.6.2 (2010-11-28)
v0.6.1 (2010-07-11)
===============================================================================
================================================================================
- Minor internal improvements and clean ups.
@@ -461,7 +551,7 @@ v0.6.1 (2010-07-11)
v0.6.0 (2010-05-24)
===============================================================================
================================================================================
- Switched to the new multi-bulk request protocol for all of the commands
in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now
@@ -576,7 +666,7 @@ v0.6.0 (2010-05-24)
v0.5.1 (2010-01-23)
===============================================================================
================================================================================
* `RPOPLPUSH` has been changed from bulk command to inline command in Redis
1.2.1, so `ListPopLastPushHead` now extends `InlineCommand`. The old behavior
@@ -591,5 +681,5 @@ v0.5.1 (2010-01-23)
v0.5.0 (2010-01-09)
===============================================================================
================================================================================
* First versioned release of Predis
+19 -15
View File
@@ -1,18 +1,18 @@
## Filing bug reports ##
Bugs or feature requests can be posted online on the [GitHub issues](http://github.com/nrk/predis/issues)
Bugs or feature requests can be posted 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)).
2. version of Redis (check `redis_version` 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,
__Think about it__: we do not have a crystal ball and cannot predict things or 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
@@ -22,19 +22,23 @@ 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
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 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.
The recommended way to contribute to Predis is to fork the project on GitHub, create topic branches
on your newly created repository to fix bugs or add new features (possibly with tests covering your
modifications) and then open a 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.
We always aim for consistency in our code base so you should follow basic coding rules as defined by
[PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
and [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
and stick with the conventions used in Predis to name classes and interfaces. Indentation should be
done with 4 spaces and code should be wrapped at 100 columns (please try to stay within this limit
even if the above mentioned official coding guidelines set the soft limit to 120 columns).
Please also follow some basic [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines)
before opening pull requests.
Please follow these [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines) when
committing your code to Git and always write a meaningful (not necessarily extended) description of
your changes before opening pull requests.
+108 -99
View File
@@ -1,41 +1,39 @@
# Some frequently asked questions about Predis #
_________________________________________________
________________________________________________
### What is the point of Predis? ###
The main point of Predis is about offering a highly customizable client for Redis that can be easily
extended by developers while still being reasonabily fast. With Predis you can swap almost any class
used internally with your own custom implementation: you can build connection classes, or new
distribution strategies for client-side sharding, or class handlers to replace existing commands or
add new ones. All of this can be achieved without messing with the source code of the library and
directly in your own application. Given the fast pace at which Redis is developed and adds new
features, this can be a great asset that allows you to add new and still missing features or commands,
or change the behaviour of the library without the need to break your dependencies in production code
(well, at least to some degree).
The main point of Predis is about offering a highly customizable and extensible client for Redis,
that can be easily extended by developers while still being reasonabily fast. With Predis you can
swap almost any class with your own custom implementation: you can have custom connection classes,
new distribution strategies for client-side sharding, or handlers to replace or add Redis commands.
All of this can be achieved without messing with the source code of the library and directly in your
own application. Given the fast pace at which Redis is developed and adds new features, this can be
a great asset since it allows developers to add new and still missing features or commands or change
the standard behaviour of the library without the need to break dependencies in production code (at
least to some degree).
### Does Predis support UNIX domain sockets and persistent connections? ###
Yes. Obviously, persistent connections actually work when using PHP configured as a persistent process that
gets recycled between requests (see [PHP-FPM](http://php-fpm.org/)).
Yes. Obviously persistent connections actually work only when using PHP configured as a persistent
process reused by the web server (see [PHP-FPM](http://php-fpm.org)).
### Does Predis support transparent (de)serialization of values? ###
No, and it will not ever do that for you by default. The reason behind this decision is that serialization
is usually something that developers prefer to customize depending on their needs and can not be easily
generalized when using Redis because of the many possible access patterns for the data. This does not
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.
No and it will not ever do that by default. The reason behind this decision is that serialization is
usually something that developers prefer to customize depending on their needs and can not be easily
generalized when using Redis because of the many possible access patterns for your data. This does
not mean that it is impossible to have such a feature since you can leverage the extensibility of
this library to define your own serialization-aware commands. You can find more details about how to
do that [on this issue](http://github.com/nrk/predis/issues/29#issuecomment-1202624).
### 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:
Explicitly connecting to Redis is usually not needed since the client initializes connections lazily
only when they are needed. Admittedly, this behavior can be inconvenient in certain scenarios when
you absolutely need to perform an upfront check to determine if the server is up and running and
eventually catch exceptions on failures. Forcing the client to open the underlying connection can be
done by invoking `Predis\Client::connect()`:
```php
$client = new Predis\Client();
@@ -49,112 +47,123 @@ try {
$client->info();
```
### How Predis abstracts Redis commands? ###
### 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:
The approach used to implement Redis commands is quite simple: by default each command follows the
same signature as defined on the [Redis documentation](http://redis.io/commands) which makes things
pretty easy if you already know how Redis works or 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. Commands such as [`RPUSH`](http://redis.io/commands/rpush) and
[`HMSET`](http://redis.io/commands/hmset) are great examples:
```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->rpush('my:list', 'value1', 'value2', 'value3'); // plain method arguments
$client->rpush('my:list', ['value1', 'value2', 'value3']); // 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
$client->hmset('my:hash', 'field1', 'value1', 'field2', 'value2'); // plain method arguments
$client->hmset('my:hash', ['field1'=>'value1', 'field2'=>'value2']); // 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](tests/Predis/Command/KeySortTest.php#L56-77).
An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which modifiers are passed
[using a named array](tests/Predis/Command/KeySortTest.php#L54-L75).
# Frequently asked questions about performances #
# Speaking 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):
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 with a simple test that uses a single client and is executed by PHP 5.5.6
against a local instance of Redis 2.8 that runs under Ubuntu 13.10 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 *_.
```
21000 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)?
How does it compare with [__phpredis__](http://github.com/nicolasff/phpredis), a nice C extension
providing an efficient client for Redis?
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 *"".
```
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.
Wow __phpredis__ seems 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 using a
for-loop on 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?
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 physical network by connecting to remote Redis
instances?
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 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 *".
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.
There you go, you get almost the same average numbers and the reason is simple: network latency is a
real performance killer and you cannot do (almost) anything about that. As a disclaimer, remember
that we are measuring the overhead of client libraries implementations and the effects of network
round-trip times, so we are not really measuring how fast Redis is. Redis shines 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 responses are still worse ###
### I am convinced, but performances for multi-bulk replies (e.g. _KEYS *_) are still worse ###
Fair enough, but there is an option available if you need even more speed and consists on installing
__[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name) and let the
client use it. __phpiredis__ is another C extension that wraps __hiredis__ (the official C client
library for Redis) with a thin layer exposing its features to PHP. You can then choose between two
different connection classes:
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 can choose between
two different connection backend classes: `Predis\Connection\PhpiredisConnection` (it depends on the
`socket` extension) and `Predis\Connection\PhpiredisStreamConnection` (it uses PHP's native streams).
You will now get the benefits of a faster protocol parser just by adding a couple of lines of code:
- `Predis\Connection\PhpiredisStreamConnection` (using native PHP streams).
- `Predis\Connection\PhpiredisConnection` (requires `ext-socket`).
You will now get the benefits of a faster protocol serializer and parser just by adding a couple of
lines of code:
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array(
'tcp' => 'Predis\Connection\PhpiredisConnection',
'unix' => 'Predis\Connection\PhpiredisConnection',
),
'tcp' => 'Predis\Connection\PhpiredisStreamConnection',
'unix' => '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:
Dead simple. Nothing changes in the way you use the library in your application. So how fast is it
our basic benchmark script now? There are not much improvements for inline or short bulk responses
like the ones returned by `SET` and `GET`, but the speed for parsing multi-bulk responses is now on
par with phpredis:
Using Predis with a phpiredis-based connection to fetch 30000 keys using _KEYS *_:
```
Fatching 30000 keys with _KEYS *_ using Predis paired with phpiredis::
0.035 seconds from a local Redis instance
0.047 seconds from a remote Redis instance
0.035 seconds from a local Redis instance
0.047 seconds from a remote Redis instance
```
### If I need an extension to get better performances, why not using phpredis? ###
### 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.
Good question. Generically speaking if you need absolute uber-speed using Redis on the localhost 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.8 and the current development version), then
using __phpredis__ makes absolutely sense. Otherwise, Predis is perfect for the job and by adding
__phpiredis__ you can get a nice speed bump almost for free.
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2013 Daniele Alessandri
Copyright (c) 2009-2014 Daniele Alessandri
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
+277 -151
View File
@@ -1,166 +1,253 @@
# Predis #
[![Latest Stable Version](https://poser.pugx.org/predis/predis/v/stable.png)](https://packagist.org/packages/predis/predis)
[![Total Downloads](https://poser.pugx.org/predis/predis/downloads.png)](https://packagist.org/packages/predis/predis)
Predis is a flexible and feature-complete PHP (>= 5.3) client library for the Redis key-value store.
Predis is a flexible and feature-complete [Redis](http://redis.io) client library for PHP >= 5.3.
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.
By default Predis does not require any additional C extension, but it can be optionally paired with
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of serializing and parsing the
Redis protocol. An asynchronous implementation of the client, albeit experimental, is also available
through [Predis\Async](https://github.com/nrk/predis-async).
For a list of frequently asked questions about Predis see our [FAQ](FAQ.md).
More details are available on the [official wiki](http://wiki.github.com/nrk/predis) of the project.
Predis can be used with [HHVM](http://www.hhvm.com) >= 2.3.0, but there are no guarantees you will
not run into unexpected issues (especially when the JIT compiler is enabled via `Eval.Jit = true`)
due to HHVM being still under heavy development, thus unstable and not yet 100% compatible with PHP.
More details about the project can be found in our [frequently asked questions](FAQ.md) section or
on the online [wiki](https://github.com/nrk/predis/wiki).
## Main features ##
- Wide range of Redis versions supported (from __1.2__ to __2.8__ 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).
- 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).
- Abstraction for Lua scripting (Redis >= 2.6) capable of automatically switching between `EVAL` and `EVALSHA`.
- Abstraction based on PHP iterators for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8).
- 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 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.
- Wide range of Redis versions supported (from __1.2__ to __3.0__ and __unstable__) using profiles.
- Cluster of nodes via client-side sharding using consistent hashing or custom distributors.
- Smart support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication configurations (write on master, read from slaves).
- Transparent key prefixing for all known Redis commands.
- Command pipelining (works on both single nodes and aggregate connections).
- Abstraction for Redis transactions (Redis >= 2.0) supporting CAS operations (Redis >= 2.2).
- Abstraction for Lua scripting (Redis >= 2.6) with automatic switching between `EVALSHA` or `EVAL`.
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
- Connections to Redis are established lazily by the client upon the first command.
- Support for both TCP/IP and UNIX domain sockets and persistent connections.
- Support for [Webdis](http://webd.is) (both `ext-curl` and `ext-phpiredis` are needed).
- Support for custom connection classes for providing different network or protocol backends.
- Flexible system for defining and registering custom sets of supported commands or profiles.
## How to use Predis ##
Predis is available on [Packagist](http://packagist.org/packages/predis/predis) for an easy installation
using [Composer](http://packagist.org/about-composer). Composer helps you manage dependencies for your
projects and libraries without much hassle which makes it the preferred way to get up and running with
new applications. Alternatively, the library is available on our [own PEAR channel](http://pear.nrk.io)
for a more traditional installation via PEAR. Zip and tar.gz archives are also downloadable from GitHub
by browsing the list of [tagged releases](http://github.com/nrk/predis/tags).
Predis is available on [Packagist](http://packagist.org/packages/predis/predis) which allows a quick
installation using [Composer](http://packagist.org/about-composer). Alternatively, the library can
be found on our [own PEAR channel](http://pear.nrk.io) for a more traditional installation via PEAR.
Ultimately, archives of each release are [available on GitHub](https://github.com/nrk/predis/tags).
### Loading the library ###
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:
[PSR-0 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) which makes
it compatible with most PHP frameworks. Autoloading is handled automatically when dependencies are
managed through Composer, but it is also possible to leverage its own autoloader in projects or
scripts not having any autoload facility:
```php
<?php
// prepend a base path if Predis is not present in your "include_path".
// Prepend a base path if Predis is not available in your "include_path".
require 'Predis/Autoloader.php';
Predis\Autoloader::register();
```
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 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.
It is possible to easily create a [phar](http://www.php.net/manual/en/intro.phar.php) archive from
the repository just by launching `bin/create-phar`. The generated phar contains a stub defining an
autoloader function for Predis, so you just need to require the phar to start using the library.
Alternatively it is possible to generate one single PHP file holding every class like older versions
of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
### Connecting to Redis ###
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:
When not specifying any connection parameter to create a new client, Predis assumes `127.0.0.1` and
`6379` as the default host and port with a connection timeout of 5 seconds:
```php
$redis = new Predis\Client();
$redis->set('foo', 'bar');
$value = $redis->get('foo');
$client = new Predis\Client();
$client->set('foo', 'bar');
$value = $client->get('foo');
```
It is possible to specify the various connection parameters using URI strings or named arrays:
Connection parameters can be supplied either in the form of URI strings or named arrays. While the
latter is the preferred way to supply parameters, URI strings can be useful for quick configurations
or when parameters are read from a non-structured source:
```php
$redis = new Predis\Client('tcp://10.0.0.1:6379');
// is equivalent to:
$redis = new Predis\Client(array(
// Named array of connection parameters:
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '10.0.0.1',
'port' => 6379,
));
]);
// Same set of parameters, but using an URI string:
$client = new Predis\Client('tcp://10.0.0.1:6379');
```
### 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.
When an array of connection parameters is provided, Predis automatically works in cluster mode using
client-side sharding. Both named arrays and URI strings can be mixed when providing configurations
for each node:
```php
$redis = new Predis\Client(array(
array('host' => '10.0.0.1', 'port' => 6379),
array('host' => '10.0.0.2', 'port' => 6379)
));
$client = new Predis\Client([
'tcp://10.0.0.1?alias=first-node',
['host' => '10.0.0.2', 'alias' => 'second-node'],
]);
```
$replies = $redis->pipeline(function ($pipe) {
The actual list of supported connection parameters can vary depending on each connection backend so
it is recommended to refer to their specific documentation or implementation for details.
### Client configuration ###
Various aspects of the client can be configured simply by passing options to the second argument of
`Predis\Client::__construct()`. Options are managed using a mini DI-alike container and their values
are usually lazily initialized only when needed. Predis by default supports the following options:
- `profile`: which profile to use in order to match a specific version of Redis.
- `prefix`: a prefix string that is automatically applied to keys found in commands.
- `exceptions`: whether the client should throw or return responses upon Redis errors.
- `connections`: connection backends or a connection factory to be used by the client.
- `cluster`: which backend to use for clustering (`predis`, `redis` or custom configuration).
- `replication`: which backend to use for replication (predis or custom configuration).
Users can provide custom option values, they are stored in the options container and can be accessed
later through the library.
### Aggregate connections ###
Predis is able to aggregate multiple connections which is the base for cluster and replication. By
default the client implements a cluster of nodes using either client-side sharding (default) or a
Redis-backed solution using [redis-cluster](http://redis.io/topics/cluster-tutorial).
As for replication, Predis can handle a single-master and multiple-slaves setup by executing read
operations on slaves and switching to the master for write operations. The replication behavior is
fully configurable.
#### Replication ####
The client can be configured to work in a master / slave replication setup by executing read-only
commands on slave nodes and automatically switch to the master node as soon as a command performing
a write operation is executed. This is the basic configuration needed to work with replication:
```php
// Parameters require one master node specifically marked with `alias=master`.
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => true];
$client = new Predis\Client($parameters, $options);
```
While Predis is able to distinguish commands performing write and read-only operations, `EVAL` and
`EVALSHA` represent a corner case in which the client switches to the master node because it is not
able to tell when a Lua script is safe to be executed on slaves. While this is the default behavior,
when certain Lua scripts do not perform write operations it is possible to provide an hint to tell
the client to stick with slaves for their execution.
```php
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => function () {
// Set scripts that won't trigger a switch from a slave to the master node.
$strategy = new Predis\Replication\ReplicationStrategy();
$strategy->setScriptReadOnly($LUA_SCRIPT);
return new Predis\Connection\Aggregate\MasterSlaveReplication($strategy);
}];
$client = new Predis\Client($parameters, $options);
$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`...
$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too.
```
The `examples` directory contains two complete scripts showing how replication can be configured for
[simple](examples/MasterSlaveReplication.php) or [complex](examples/MasterSlaveReplicationComplex.php)
scenarios.
#### Cluster ####
Simply passing an array of connection parameters to the client constructor configures Predis to work
in cluster mode using client-side sharding. If you, on the other hand, want to leverage Redis >= 3.0
nodes coordinated by redis-cluster, then the client must be initialized like this:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2'];
$options = ['cluster' => 'redis'];
$client = new Predis\Client($parameters, $options);
```
When using redis-cluster, it is not necessary to pass all of the nodes that compose your cluster but
you can simply specify only a few nodes: Predis will automatically fetch the full and updated slots
map directly from Redis by contacting one of the servers.
__NOTE__: our support for redis-cluster does not currently consider master / slave replication but
this feature will be added in a future release of this library.
### Command pipelines ###
Pipelining can help with performances when many commands need to be sent to a server by reducing the
latency introduced by network round-trip timings. Pipelining also works with aggregate connections.
The client can execute the pipeline inside a callable block or return a pipeline instance with the
ability to chain commands thanks to its fluent interface:
```php
// Executes a pipeline inside a given callable block:
$responses = $client->pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
$pipe->get("key:$i");
}
});
// Returns a pipeline instance with fluent interface:
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();
```
### Multiple and customizable connection backends ###
### Transactions ###
Predis can optionally use different connection backends to connect to Redis. Two of them leverage
the [phpiredis](http://github.com/nrk/phpiredis) C-based extension resulting in a major speed bump
especially when dealing with long multibulk replies, namely `Predis\Connection\PhpiredisConnection`
(the `socket` extension is also required) and `Predis\Connection\StreamPhpiredisConnection` (it
does not require additional extensions since it relies on PHP's native streams). Both of them can
connect to Redis using standard TCP/IP connections or UNIX domain sockets:
The client provides an abstraction for Redis transactions based on `MULTI` and `EXEC` with a similar
interface to command pipelines:
```php
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array(
'tcp' => 'Predis\Connection\PhpiredisConnection',
'unix' => 'Predis\Connection\PhpiredisStreamConnection',
)
));
// Executes a transaction inside a given callable block:
$responses = $client->transaction(function ($tx) {
$tx->set('foo', 'bar');
$tx->get('foo');
});
// Returns a transaction instance with fluent interface:
$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute();
```
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
}
// Let Predis automatically use your own class to handle connections identified by the tcp scheme.
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array('tcp' => 'MyConnectionClass')
));
```
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 the `Predis\Connection` namespace.
### Defining and registering new commands on the client at runtime ###
Let's suppose Redis just added the support for a brand new feature associated with a new command. If
you want to start using the above mentioned new feature right away without messing with Predis source
code or waiting for it to find its way into a stable Predis release, then you can start off by creating
a new class that matches the command type and its behaviour and then bind it to a client instance at
runtime. Actually, it is easier done than said:
This abstraction can perform check-and-set operations thanks to `WATCH` and `UNWATCH` and provides
automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example
of a transaction using CAS you can see [the following example](examples/TransactionWithCAS.php).
__NOTE__: the method `transaction()` is available since `v0.8.5`, older versions used `multiExec()`
for the same purpose but it has been deprecated and will be removed in the next major release.
### Adding new Redis commands ###
While we try to update Predis to stay up to date with all the commands available in Redis, you might
prefer to stick with an older version of the library or provide a different way to filter arguments
or parse responses for specific commands. To achieve that, Predis provides the ability to implement
new command classes to define or override commands in the server profiles used by the client:
```php
// Define a new command by extending Predis\Command\AbstractCommand:
class BrandNewRedisCommand extends Predis\Command\AbstractCommand
{
public function getId()
@@ -169,22 +256,27 @@ class BrandNewRedisCommand extends Predis\Command\AbstractCommand
}
}
$redis = new Predis\Client();
$redis->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
$redis->newcmd();
// Inject your command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
$response = $client->newcmd();
```
### Abstraction for handling Lua scripts as plain Redis commands ###
### Script 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:
While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using
[`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha), Predis
offers script commands as an higher level abstraction aiming to make things simple. Script commands
can be registered in the server profile used by the client and are accessible as if they were plain
Redis commands, but they define Lua scripts that get transmitted to the server for remote execution.
Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and identify a script
by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval) is used as a fall
back when needed:
```php
// Define a new scriptable command by extending Predis\Command\ScriptedCommand:
class ListPushRandomValue extends Predis\Command\ScriptedCommand
{
public function getKeysCount()
@@ -194,8 +286,7 @@ class ListPushRandomValue extends Predis\Command\ScriptedCommand
public function getScript()
{
return
<<<LUA
return <<<LUA
math.randomseed(ARGV[1])
local rnd = tostring(math.random())
redis.call('lpush', KEYS[1], rnd)
@@ -204,61 +295,96 @@ LUA;
}
}
// Inject your script command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
$value = $client->lpushrand('random_values', $seed = mt_rand());
$response = $client->lpushrand('random_values', $seed = mt_rand());
```
## Test suite ##
### Customizable connection backends ###
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running in
production environments or containing data you are interested in!
Predis can use different connection backends to connect to Redis. Two of them leverage a third party
extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains
especially when dealing with big multibulk responses. While one is based on PHP streams, the other
is based on socket resources provided by `ext-socket`. Both support TCP/IP or UNIX domain sockets:
Predis has a comprehensive test suite covering every aspect of the library. The suite performs integration
tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct behaviour of the
implementation of each command and automatically skips commands not defined in the selected version of
Redis. If you do not have Redis up and running, integration tests can be disabled. By default, the test
suite is configured to execute integration tests using the server profile for Redis v2.4 (which is the
current stable version of Redis). You can optionally run the suite against a Redis instance built from
the `unstable` branch with the development profile by changing the `REDIS_SERVER_VERSION` to `dev` in
the `phpunit.xml` file. More details on testing Predis can be found in [the tests README](tests/README.md).
```php
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => [
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
'unix' => 'Predis\Connection\PhpiredisConnection', // ext-socket
],
]);
```
Predis uses Travis CI for continuous integration. You can find the results of the test suite and the build
history [on its project page](http://travis-ci.org/nrk/predis).
Developers can create their own connection classes to support whole new network backends, extend
existing ones or provide completely different implementations. Connection classes must implement
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
```php
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
{
// Implementation goes here...
}
// Use MyConnectionClass to handle connections for the `tcp` scheme:
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => ['tcp' => 'MyConnectionClass'],
]);
```
For a more in-depth insight on how to create new connection backends you can refer to the actual
implementation of the standard connection classes available in the `Predis\Connection` namespace.
## Dependencies ##
## Development ##
- PHP >= 5.3.2
- PHPUnit >= 3.5.0 (needed to run the test suite)
## Links ##
### Reporting bugs and contributing code ###
### Project ###
- [Source code](http://github.com/nrk/predis/)
- [Wiki](http://wiki.github.com/nrk/predis/)
- [Issue tracker](http://github.com/nrk/predis/issues)
Contributions to Predis are highly appreciated either in the form of pull requests for new features,
bug fixes, or just bug reports. We only ask you to adhere to a [basic set of rules](CONTRIBUTING.md)
before submitting your changes or filing bugs on the issue tracker to make it easier for everyone to
stay consistent while working on the project.
### Test suite ###
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running
in production environments or containing data you are interested in!
Predis has a comprehensive test suite covering every aspect of the library. This test suite performs
integration tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct
behavior of the implementation of each command and automatically skips commands not defined in the
specified Redis profile. If you do not have Redis up and running, integration tests can be disabled.
By default the test suite is configured to execute integration tests using the profile for Redis 2.8
(which is the current stable version of Redis) but can optionally target a Redis instance built from
the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that
the development server profile will be used. You can refer to [the tests README](tests/README.md)
for more detailed information about testing Predis.
Predis uses Travis CI for continuous integration and the history for past and current builds can be
found [on its project page](http://travis-ci.org/nrk/predis).
## Other ##
### Project related links ###
- [Source code](https://github.com/nrk/predis)
- [Wiki](https://wiki.github.com/nrk/predis)
- [Issue tracker](https://github.com/nrk/predis/issues)
- [PEAR channel](http://pear.nrk.io)
### Related ###
- [Redis](http://redis.io/)
- [PHP](http://php.net/)
- [PHPUnit](http://www.phpunit.de/)
- [Git](http://git-scm.com/)
## Author ##
### Author ###
- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN))
## Contributors ##
- [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 past work on extending [phpiredis](http://github.com/nrk/phpiredis) for Predis.
## License ##
### License ###
The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
+1 -1
View File
@@ -1 +1 @@
0.8.5-dev
0.8.7
@@ -171,13 +171,11 @@ class CommandTestCaseGenerator
namespace Predis\Command;
use \PHPUnit_Framework_TestCase as StandardTestCase;
/**
* @group commands
* @group realm-$realm
*/
class $class extends CommandTestCase
class $class extends PredisCommandTestCase
{
/**
* {@inheritdoc}
+221
View File
@@ -0,0 +1,221 @@
#!/usr/bin/env php
<?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.
*/
// -------------------------------------------------------------------------- //
// In order to be able to execute this script to create a PEAR package of Predis
// the `pear` binary must be available and executable in your $PATH.
// The parts used to parse author and version strings are taken from Onion (used
// by this library in the past) just to keep on relying on the package.ini file
// to simplify things. We might consider to switch to using the PEAR classes
// directly in the future.
// -------------------------------------------------------------------------- //
function executeWithBackup($file, $callback)
{
$exception = null;
$backup = "$file.backup";
copy($file, $backup);
try {
call_user_func($callback, $file);
} catch (Exception $exception) {
// NOOP
}
unlink($file);
rename($backup, $file);
if ($exception) {
throw $exception;
}
}
function parseAuthor($string)
{
$author = array();
if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
if (count($regs) == 4) {
list($orig,$name,$user,$email) = $regs;
$author['name'] = $name;
$author['user'] = $user;
$author['email'] = $email;
} elseif (count($regs) == 3) {
list($orig,$name,$email) = $regs;
$author['name'] = $name;
$author['email'] = $email;
}
} else {
$author['name'] = $string;
}
return $author;
}
function parseVersion($string)
{
$version_pattern = '([0-9.]+)';
if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('max' => $regs[1]);
} elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) {
return array(
'min' => $regs[1] ?: '0.0.0',
'max' => $regs[2],
);
}
}
function addRolePath($pkg, $path, $role)
{
if (is_dir($path)) {
$dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($dirTree as $fileinfo) {
if ($fileinfo->isFile()) {
addPackageFile($pkg, $fileinfo, $role, $path);
}
}
} else {
foreach (glob($path) as $filename) {
addPackageFile($pkg, new SplFileInfo($filename), $role);
}
}
}
function addPackageFile($pkg, $fileinfo, $role, $baseDir = '')
{
$fileNode = $pkg->contents->dir->addChild('file');
$fileNode->addAttribute('name', $filepath = $fileinfo->getPathname());
$fileNode->addAttribute('role', $role);
$fileNode->addAttribute('md5sum', md5_file($filepath));
$installNode = $pkg->phprelease->filelist->addChild('install');
$installNode->addAttribute('name', $filepath);
$installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1));
}
function generatePackageXml($packageINI)
{
$XML = <<<XML
<?xml version="1.0"?>
<package packagerversion="1.4.10" version="2.0"
xmlns="http://pear.php.net/dtd/package-2.0"
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd" />
XML;
$cfg = parse_ini_file($packageINI, true);
$pkg = new SimpleXMLElement($XML);
$pkg->name = $cfg['package']['name'];
$pkg->channel = $cfg['package']['channel'];
$pkg->summary = $cfg['package']['desc'];
$pkg->description = $cfg['package']['desc'];
$author = parseAuthor($cfg['package']['author']);
$pkg->addChild('lead');
$pkg->lead->name = $author['name'];
$pkg->lead->user = $author['user'];
$pkg->lead->email = $author['email'];
$pkg->lead->active = 'yes';
$datetime = new DateTime('now');
$pkg->date = $datetime->format('Y-m-d');
$pkg->time = $datetime->format('H:i:s');
$pkg->addChild('version');
$pkg->version->release = $cfg['package']['version'];
$pkg->version->api = $cfg['package']['version'];
$pkg->addChild('stability');
$pkg->stability->release = $cfg['package']['stability'];
$pkg->stability->api = $cfg['package']['stability'];
$pkg->license = $cfg['package']['license'];
$pkg->notes = '-';
$pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/');
$pkg->addChild('dependencies')->addChild('required');
foreach ($cfg['require'] as $required => $version) {
$version = parseVersion($version);
$pkg->dependencies->required->addChild($required);
if (isset($version['min'])) {
$pkg->dependencies->required->$required->min = $version['min'];
}
if (isset($version['max'])) {
$pkg->dependencies->required->$required->min = $version['max'];
}
}
$pkg->addChild('phprelease')->addChild('filelist');
$pathToRole = array(
'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc',
'lib' => 'php', 'src' => 'php',
'test' => 'test', 'tests' => 'test',
);
foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) {
addRolePath($pkg, $path, $role);
}
return $pkg;
}
function savePackageXml($xml)
{
$dom = new DOMDocument("1.0");
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
file_put_contents('package.xml', $dom->saveXML());
}
function buildPackage()
{
passthru('pear -q package && rm package.xml');
}
function modifyPhpunitXml($file)
{
$cfg = new SimpleXMLElement($file, null, true);
$cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']);
$cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory);
$cfg->saveXml($file);
}
// -------------------------------------------------------------------------- //
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
modifyPhpunitXml($file);
$pkg = generatePackageXml('package.ini');
savePackageXml($pkg);
buildPackage();
});
@@ -321,13 +321,15 @@ class PhpUseDirectives implements Countable, IteratorAggregate
{
private $use;
private $aliases;
private $reverseAliases;
private $namespace;
public function __construct(PhpNamespace $namespace)
{
$this->namespace = $namespace;
$this->use = array();
$this->aliases = array();
$this->namespace = $namespace;
$this->reverseAliases = array();
}
public function add($use, $as = null)
@@ -336,8 +338,35 @@ class PhpUseDirectives implements Countable, IteratorAggregate
return;
}
$rename = null;
$this->use[] = $use;
$this->aliases[$as ?: PhpClass::extractName($use)] = $use;
$aliasedClassName = $as ?: PhpClass::extractName($use);
if (isset($this->aliases[$aliasedClassName])) {
$parentNs = $this->getParentNamespace();
if ($parentNs && false !== $pos = strrpos($parentNs, '\\')) {
$parentNs = substr($parentNs, $pos);
}
$newAlias = "{$parentNs}_{$aliasedClassName}";
$rename = (object) array(
'namespace' => $this->namespace,
'from' => $aliasedClassName,
'to' => $newAlias,
);
$this->aliases[$newAlias] = $use;
$as = $newAlias;
} else {
$this->aliases[$aliasedClassName] = $use;
}
if ($as !== null) {
$this->reverseAliases[$use] = $as;
}
return $rename;
}
public function getList()
@@ -352,8 +381,14 @@ class PhpUseDirectives implements Countable, IteratorAggregate
public function getPhpCode()
{
$reducer = function ($str, $use) {
return $str .= "use $use;\n";
$reverseAliases = $this->reverseAliases;
$reducer = function ($str, $use) use ($reverseAliases) {
if (isset($reverseAliases[$use])) {
return $str .= "use $use as {$reverseAliases[$use]};\n";
} else {
return $str .= "use $use;\n";
}
};
return array_reduce($this->getList(), $reducer, '');
@@ -364,6 +399,15 @@ class PhpUseDirectives implements Countable, IteratorAggregate
return $this->namespace;
}
public function getParentNamespace()
{
if (false !== $pos = strrpos($this->namespace, '\\')) {
return substr($this->namespace, 0, $pos);
}
return '';
}
public function getFQN($className)
{
if (($nsSepFirst = strpos($className, '\\')) === false) {
@@ -430,10 +474,19 @@ LICENSE;
private function extractData()
{
$renames = array();
$useDirectives = $this->getNamespace()->getUseDirectives();
$useExtractor = function ($m) use ($useDirectives) {
$useDirectives->add(($namespacedPath = $m[1]));
$useExtractor = function ($m) use ($useDirectives, &$renames) {
array_shift($m);
if (isset($m[1])) {
$m[1] = str_replace(" as ", '', $m[1]);
}
if ($rename = call_user_func_array(array($useDirectives, 'add'), $m)) {
$renames[] = $rename;
}
};
$classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r'));
@@ -445,6 +498,10 @@ LICENSE;
$classBuffer = preg_replace('/namespace\s+[\w\d_\\\\]+;\s?/', '', $classBuffer);
$classBuffer = preg_replace_callback('/use\s+([\w\d_\\\\]+)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer);
foreach ($renames as $rename) {
$classBuffer = str_replace($rename->from, $rename->to, $classBuffer);
}
$this->body = trim($classBuffer);
$this->extractHierarchy();
+3
View File
@@ -15,6 +15,9 @@
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"suggest": {
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
"ext-curl": "Allows access to Webdis when paired with phpiredis"
+5
View File
@@ -84,6 +84,11 @@ for ($i = 0; $i < 100; $i++) {
$server1 = $client->getClientFor('first')->info();
$server2 = $client->getClientFor('second')->info();
if (isset($server1['Keyspace'], $server2['Keyspace'])) {
$server1 = $server1['Keyspace'];
$server2 = $server2['Keyspace'];
}
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
);
+4 -3
View File
@@ -20,7 +20,6 @@ require 'SharedConfigurations.php';
use Predis\Command\ScriptedCommand;
use Predis\Connection\MasterSlaveReplication;
use Predis\Profile\ServerProfile;
use Predis\Replication\ReplicationStrategy;
// ------------------------------------------------------------------------- //
@@ -28,7 +27,8 @@ use Predis\Replication\ReplicationStrategy;
// Define a new scripted command that returns all the fields
// of a variable number of hashes with a single roundtrip.
class HashMultipleGetAll extends ScriptedCommand {
class HashMultipleGetAll extends ScriptedCommand
{
const BODY = <<<EOS
local hashes = {}
for _, key in pairs(KEYS) do
@@ -38,7 +38,8 @@ end
return hashes
EOS;
public function getScript() {
public function getScript()
{
return self::BODY;
}
}
@@ -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->transaction($options, function ($tx) use ($key, &$element) {
@list($element) = $tx->zrange($key, 0, 0);
if (isset($element)) {
+1 -1
View File
@@ -36,7 +36,7 @@ class Autoloader
/**
* Registers the autoloader class with the PHP SPL autoloader.
*
* @param boolean $prepend Prepend the autoloader on the stack instead of appending it.
* @param bool $prepend Prepend the autoloader on the stack instead of appending it.
*/
public static function register($prepend = false)
{
+1 -1
View File
@@ -24,7 +24,7 @@ interface BasicClientInterface
/**
* Executes the specified Redis command.
*
* @param CommandInterface $command A Redis command.
* @param CommandInterface $command A Redis command.
* @return mixed
*/
public function executeCommand(CommandInterface $command);
+55 -43
View File
@@ -20,7 +20,6 @@ use Predis\Monitor\MonitorContext;
use Predis\Option\ClientOptions;
use Predis\Option\ClientOptionsInterface;
use Predis\Pipeline\PipelineContext;
use Predis\Profile\ServerProfile;
use Predis\PubSub\PubSubContext;
use Predis\Transaction\MultiExecContext;
@@ -31,7 +30,7 @@ use Predis\Transaction\MultiExecContext;
*/
class Client implements ClientInterface
{
const VERSION = '0.8.5-dev';
const VERSION = '0.8.7';
private $options;
private $profile;
@@ -41,7 +40,7 @@ class Client implements ClientInterface
* Initializes a new client with optional connection parameters and client options.
*
* @param mixed $parameters Connection parameters for one or multiple servers.
* @param mixed $options Options that specify certain behaviours for the client.
* @param mixed $options Options that specify certain behaviours for the client.
*/
public function __construct($parameters = null, $options = null)
{
@@ -55,7 +54,7 @@ class Client implements ClientInterface
* 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.
* @param mixed $options Client options.
* @return ClientOptions
*/
protected function filterOptions($options)
@@ -80,7 +79,7 @@ class Client implements ClientInterface
* types of arguments (string, array) or returns the passed object if it
* implements Predis\Connection\ConnectionInterface.
*
* @param mixed $parameters Connection parameters or instance.
* @param mixed $parameters Connection parameters or instance.
* @return ConnectionInterface
*/
protected function initializeConnection($parameters)
@@ -143,6 +142,7 @@ class Client implements ClientInterface
* client is connected to a cluster. The new instance will use the same
* options of the original client.
*
* @param string $connectionID Identifier for the connection.
* @return Client
*/
public function getClientFor($connectionID)
@@ -183,8 +183,8 @@ class Client implements ClientInterface
/**
* Checks if the underlying connection is connected to Redis.
*
* @return Boolean True means that the connection is open.
* False means that the connection is closed.
* @return bool True means that the connection is open.
* False means that the connection is closed.
*/
public function isConnected()
{
@@ -202,7 +202,7 @@ class Client implements ClientInterface
/**
* Retrieves a single connection out of an aggregated connections instance.
*
* @param string $connectionId Index or alias of the connection.
* @param string $connectionId Index or alias of the single connection.
* @return Connection\SingleConnectionInterface
*/
public function getConnectionById($connectionId)
@@ -215,34 +215,27 @@ class Client implements ClientInterface
}
/**
* Dynamically invokes a Redis command with the specified arguments.
* Creates a Redis command with the specified arguments and sends a request
* to the server.
*
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
* @param string $commandID Command ID.
* @param array $arguments Arguments for the command.
* @return mixed
*/
public function __call($method, $arguments)
public function __call($commandID, $arguments)
{
$command = $this->profile->createCommand($method, $arguments);
$response = $this->connection->executeCommand($command);
$command = $this->createCommand($commandID, $arguments);
$response = $this->executeCommand($command);
if ($response instanceof ResponseObjectInterface) {
if ($response instanceof ResponseErrorInterface) {
$response = $this->onResponseError($command, $response);
}
return $response;
}
return $command->parseResponse($response);
return $response;
}
/**
* {@inheritdoc}
*/
public function createCommand($method, $arguments = array())
public function createCommand($commandID, $arguments = array())
{
return $this->profile->createCommand($method, $arguments);
return $this->profile->createCommand($commandID, $arguments);
}
/**
@@ -266,8 +259,8 @@ class Client implements ClientInterface
/**
* Handles -ERR responses returned by Redis.
*
* @param CommandInterface $command The command that generated the error.
* @param ResponseErrorInterface $response The error response instance.
* @param CommandInterface $command The command that generated the error.
* @param ResponseErrorInterface $response The error response instance.
* @return mixed
*/
protected function onResponseError(CommandInterface $command, ResponseErrorInterface $response)
@@ -297,8 +290,8 @@ class Client implements ClientInterface
*
* TODO: Invert $argv and $initializer.
*
* @param array $argv Arguments for the initializer.
* @param string $initializer The initializer method.
* @param array $argv Arguments for the initializer.
* @param string $initializer The initializer method.
* @return mixed
*/
private function sharedInitializer($argv, $initializer)
@@ -309,10 +302,12 @@ class Client implements ClientInterface
case 1:
list($arg0) = $argv;
return is_array($arg0) ? $this->$initializer($arg0) : $this->$initializer(null, $arg0);
case 2:
list($arg0, $arg1) = $argv;
return $this->$initializer($arg0, $arg1);
default:
@@ -324,7 +319,7 @@ class Client implements ClientInterface
* Creates a new pipeline context and returns it, or returns the results of
* a pipeline executed inside the optionally provided callable object.
*
* @param mixed $arg,... Options for the context, a callable object, or both.
* @param mixed ... Options for the context, a callable object, or both.
* @return PipelineContext|array
*/
public function pipeline(/* arguments */)
@@ -335,8 +330,8 @@ class Client implements ClientInterface
/**
* Pipeline context initializer.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @return PipelineContext|array
*/
protected function initPipeline(Array $options = null, $callable = null)
@@ -356,8 +351,8 @@ class Client implements ClientInterface
/**
* Executes a pipeline context when a callable object is passed.
*
* @param array $options Options of the context initialization.
* @param mixed $callable Optional callable object used to execute the context.
* @param PipelineContext $pipeline Pipeline context instance.
* @param mixed $callable Optional callable object used to execute the context.
* @return PipelineContext|array
*/
private function pipelineExecute(PipelineContext $pipeline, $callable)
@@ -369,7 +364,11 @@ class Client implements ClientInterface
* Creates a new transaction context and returns it, or returns the results of
* a transaction executed inside the optionally provided callable object.
*
* @param mixed $arg,... Options for the context, a callable object, or both.
* @deprecated You should start using the new Client::transaction() method
* as it will replace Client::multiExec() in the next major
* version of the library.
*
* @param mixed ... Options for the context, a callable object, or both.
* @return MultiExecContext|array
*/
public function multiExec(/* arguments */)
@@ -377,16 +376,29 @@ class Client implements ClientInterface
return $this->sharedInitializer(func_get_args(), 'initMultiExec');
}
/**
* Creates a new transaction context and returns it, or returns the results of
* a transaction executed inside the optionally provided callable object.
*
* @param mixed ... Options for the context, a callable object, or both.
* @return MultiExecContext|array
*/
public function transaction(/* arguments */)
{
return $this->sharedInitializer(func_get_args(), 'initMultiExec');
}
/**
* Transaction context initializer.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @return MultiExecContext|array
*/
protected function initMultiExec(Array $options = null, $callable = null)
{
$transaction = new MultiExecContext($this, $options ?: array());
return isset($callable) ? $transaction->execute($callable) : $transaction;
}
@@ -399,8 +411,8 @@ class Client implements ClientInterface
* Client::pubSubLoop() to create Predis\PubSub\PubSubContext
* instances from now on.
*
* @param mixed $arg,... Options for the context, a callable object, or both.
* @return PubSubExecContext|array
* @param mixed ... Options for the context, a callable object, or both.
* @return PubSubContext|array
*/
public function pubSub(/* arguments */)
{
@@ -411,8 +423,8 @@ class Client implements ClientInterface
* Creates a new Publish / Subscribe context and returns it, or executes it
* inside the optionally provided callable object.
*
* @param mixed $arg,... Options for the context, a callable object, or both.
* @return PubSubExecContext|array
* @param mixed ... Options for the context, a callable object, or both.
* @return PubSubContext|array
*/
public function pubSubLoop(/* arguments */)
{
@@ -422,8 +434,8 @@ class Client implements ClientInterface
/**
* Publish / Subscribe context initializer.
*
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @param array $options Options for the context.
* @param mixed $callable Optional callable object used to execute the context.
* @return PubSubContext
*/
protected function initPubSub(Array $options = null, $callable = null)
+2 -2
View File
@@ -58,8 +58,8 @@ interface ClientInterface extends BasicClientInterface
/**
* 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.
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
* @return Command\CommandInterface
*/
public function createCommand($method, $arguments = array());
@@ -27,7 +27,7 @@ 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.
* @param CommandInterface $command Command to be hashed.
* @return int
*/
public function getHash(CommandInterface $command);
@@ -35,7 +35,7 @@ interface CommandHashStrategyInterface
/**
* Returns the hash for the given key using the specified algorithm.
*
* @param string $key Key to be hashed.
* @param string $key Key to be hashed.
* @return string
*/
public function getKeyHash($key);
@@ -24,8 +24,8 @@ interface DistributionStrategyInterface
/**
* Adds a node to the distributor with an optional weight.
*
* @param mixed $node Node object.
* @param int $weight Weight for the node.
* @param mixed $node Node object.
* @param int $weight Weight for the node.
*/
public function add($node, $weight = null);
@@ -39,6 +39,7 @@ interface DistributionStrategyInterface
/**
* Gets a node from the distributor using the computed hash of a key.
*
* @param mixed $key
* @return mixed
*/
public function get($key);
+14 -14
View File
@@ -34,7 +34,7 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
private $nodes = array();
/**
* @param int $replicas Number of replicas in the ring.
* @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, $nodeHashCallback = null)
@@ -46,8 +46,8 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
/**
* Adds a node to the ring with an optional weight.
*
* @param mixed $node Node object.
* @param int $weight Weight for the node.
* @param mixed $node Node object.
* @param int $weight Weight for the node.
*/
public function add($node, $weight = null)
{
@@ -90,7 +90,7 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
/**
* Returns the initialization status of the distributor.
*
* @return Boolean
* @return bool
*/
private function isInitialized()
{
@@ -143,10 +143,10 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
/**
* Implements the logic needed to add a node to the hashring.
*
* @param array $ring Source hashring.
* @param mixed $node Node object to be added.
* @param int $totalNodes Total number of nodes.
* @param int $replicas Number of replicas in the ring.
* @param array $ring Source hashring.
* @param mixed $node Node object to be added.
* @param int $totalNodes Total number of nodes.
* @param int $replicas Number of replicas in the ring.
* @param float $weightRatio Weight ratio for the node.
*/
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
@@ -176,7 +176,7 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
/**
* Calculates the hash for the specified value.
*
* @param string $value Input value.
* @param string $value Input value.
* @return int
*/
public function hash($value)
@@ -195,7 +195,7 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
/**
* Calculates the corrisponding key of a node distributed in the hashring.
*
* @param int $key Computed hash of a key.
* @param int $key Computed hash of a key.
* @return int
*/
private function getNodeKey($key)
@@ -211,7 +211,7 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
if ($item > $key) {
$upper = $index - 1;
} else if ($item < $key) {
} elseif ($item < $key) {
$lower = $index + 1;
} else {
return $item;
@@ -224,9 +224,9 @@ class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
/**
* Implements a strategy to deal with wrap-around errors during binary searches.
*
* @param int $upper
* @param int $lower
* @param int $ringKeysCount
* @param int $upper
* @param int $lower
* @param int $ringKeysCount
* @return int
*/
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
@@ -55,6 +55,7 @@ class KetamaPureRing extends HashRing
public function hash($value)
{
$hash = unpack('V', md5($value, true));
return $hash[1];
}
@@ -22,7 +22,7 @@ interface HashGeneratorInterface
/**
* Generates an hash that is used by the distributor algorithm
*
* @param string $value Value used to generate the hash.
* @param string $value Value used to generate the hash.
* @return int
*/
public function hash($value);
+113 -103
View File
@@ -23,8 +23,8 @@ use Predis\Command\ScriptedCommand;
*/
class PredisClusterHashStrategy implements CommandHashStrategyInterface
{
private $commands;
private $hashGenerator;
protected $commands;
protected $hashGenerator;
/**
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
@@ -42,117 +42,126 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
*/
protected function getDefaultCommands()
{
$keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
$keysAreAllArguments = array($this, 'getKeyFromAllArguments');
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
$getKeyFromAllArguments = 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
'DUMP' => $keyIsFirstArgument,
'RESTORE' => $keyIsFirstArgument,
'EXISTS' => $getKeyFromFirstArgument,
'DEL' => $getKeyFromAllArguments,
'TYPE' => $getKeyFromFirstArgument,
'EXPIRE' => $getKeyFromFirstArgument,
'EXPIREAT' => $getKeyFromFirstArgument,
'PERSIST' => $getKeyFromFirstArgument,
'PEXPIRE' => $getKeyFromFirstArgument,
'PEXPIREAT' => $getKeyFromFirstArgument,
'TTL' => $getKeyFromFirstArgument,
'PTTL' => $getKeyFromFirstArgument,
'SORT' => $getKeyFromFirstArgument, // TODO
'DUMP' => $getKeyFromFirstArgument,
'RESTORE' => $getKeyFromFirstArgument,
/* 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,
'APPEND' => $getKeyFromFirstArgument,
'DECR' => $getKeyFromFirstArgument,
'DECRBY' => $getKeyFromFirstArgument,
'GET' => $getKeyFromFirstArgument,
'GETBIT' => $getKeyFromFirstArgument,
'MGET' => $getKeyFromAllArguments,
'SET' => $getKeyFromFirstArgument,
'GETRANGE' => $getKeyFromFirstArgument,
'GETSET' => $getKeyFromFirstArgument,
'INCR' => $getKeyFromFirstArgument,
'INCRBY' => $getKeyFromFirstArgument,
'INCRBYFLOAT' => $getKeyFromFirstArgument,
'SETBIT' => $getKeyFromFirstArgument,
'SETEX' => $getKeyFromFirstArgument,
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
'SETNX' => $keyIsFirstArgument,
'SETRANGE' => $keyIsFirstArgument,
'STRLEN' => $keyIsFirstArgument,
'SUBSTR' => $keyIsFirstArgument,
'SETNX' => $getKeyFromFirstArgument,
'SETRANGE' => $getKeyFromFirstArgument,
'STRLEN' => $getKeyFromFirstArgument,
'SUBSTR' => $getKeyFromFirstArgument,
'BITOP' => array($this, 'getKeyFromBitOp'),
'BITCOUNT' => $keyIsFirstArgument,
'BITCOUNT' => $getKeyFromFirstArgument,
/* commands operating on lists */
'LINSERT' => $keyIsFirstArgument,
'LINDEX' => $keyIsFirstArgument,
'LLEN' => $keyIsFirstArgument,
'LPOP' => $keyIsFirstArgument,
'RPOP' => $keyIsFirstArgument,
'RPOPLPUSH' => $keysAreAllArguments,
'LINSERT' => $getKeyFromFirstArgument,
'LINDEX' => $getKeyFromFirstArgument,
'LLEN' => $getKeyFromFirstArgument,
'LPOP' => $getKeyFromFirstArgument,
'RPOP' => $getKeyFromFirstArgument,
'RPOPLPUSH' => $getKeyFromAllArguments,
'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,
'LPUSH' => $getKeyFromFirstArgument,
'LPUSHX' => $getKeyFromFirstArgument,
'RPUSH' => $getKeyFromFirstArgument,
'RPUSHX' => $getKeyFromFirstArgument,
'LRANGE' => $getKeyFromFirstArgument,
'LREM' => $getKeyFromFirstArgument,
'LSET' => $getKeyFromFirstArgument,
'LTRIM' => $getKeyFromFirstArgument,
/* commands operating on sets */
'SADD' => $keyIsFirstArgument,
'SCARD' => $keyIsFirstArgument,
'SDIFF' => $keysAreAllArguments,
'SDIFFSTORE' => $keysAreAllArguments,
'SINTER' => $keysAreAllArguments,
'SINTERSTORE' => $keysAreAllArguments,
'SUNION' => $keysAreAllArguments,
'SUNIONSTORE' => $keysAreAllArguments,
'SISMEMBER' => $keyIsFirstArgument,
'SMEMBERS' => $keyIsFirstArgument,
'SSCAN' => $keyIsFirstArgument,
'SPOP' => $keyIsFirstArgument,
'SRANDMEMBER' => $keyIsFirstArgument,
'SREM' => $keyIsFirstArgument,
'SADD' => $getKeyFromFirstArgument,
'SCARD' => $getKeyFromFirstArgument,
'SDIFF' => $getKeyFromAllArguments,
'SDIFFSTORE' => $getKeyFromAllArguments,
'SINTER' => $getKeyFromAllArguments,
'SINTERSTORE' => $getKeyFromAllArguments,
'SUNION' => $getKeyFromAllArguments,
'SUNIONSTORE' => $getKeyFromAllArguments,
'SISMEMBER' => $getKeyFromFirstArgument,
'SMEMBERS' => $getKeyFromFirstArgument,
'SSCAN' => $getKeyFromFirstArgument,
'SPOP' => $getKeyFromFirstArgument,
'SRANDMEMBER' => $getKeyFromFirstArgument,
'SREM' => $getKeyFromFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $keyIsFirstArgument,
'ZCARD' => $keyIsFirstArgument,
'ZCOUNT' => $keyIsFirstArgument,
'ZINCRBY' => $keyIsFirstArgument,
'ZADD' => $getKeyFromFirstArgument,
'ZCARD' => $getKeyFromFirstArgument,
'ZCOUNT' => $getKeyFromFirstArgument,
'ZINCRBY' => $getKeyFromFirstArgument,
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'ZRANGE' => $keyIsFirstArgument,
'ZRANGEBYSCORE' => $keyIsFirstArgument,
'ZRANK' => $keyIsFirstArgument,
'ZREM' => $keyIsFirstArgument,
'ZREMRANGEBYRANK' => $keyIsFirstArgument,
'ZREMRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANGE' => $keyIsFirstArgument,
'ZREVRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANK' => $keyIsFirstArgument,
'ZSCORE' => $keyIsFirstArgument,
'ZRANGE' => $getKeyFromFirstArgument,
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZRANK' => $getKeyFromFirstArgument,
'ZREM' => $getKeyFromFirstArgument,
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANGE' => $getKeyFromFirstArgument,
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANK' => $getKeyFromFirstArgument,
'ZSCORE' => $getKeyFromFirstArgument,
'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'ZSCAN' => $keyIsFirstArgument,
'ZSCAN' => $getKeyFromFirstArgument,
'ZLEXCOUNT' => $getKeyFromFirstArgument,
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
/* commands operating on hashes */
'HDEL' => $keyIsFirstArgument,
'HEXISTS' => $keyIsFirstArgument,
'HGET' => $keyIsFirstArgument,
'HGETALL' => $keyIsFirstArgument,
'HMGET' => $keyIsFirstArgument,
'HMSET' => $keyIsFirstArgument,
'HINCRBY' => $keyIsFirstArgument,
'HINCRBYFLOAT' => $keyIsFirstArgument,
'HKEYS' => $keyIsFirstArgument,
'HLEN' => $keyIsFirstArgument,
'HSET' => $keyIsFirstArgument,
'HSETNX' => $keyIsFirstArgument,
'HVALS' => $keyIsFirstArgument,
'HSCAN' => $keyIsFirstArgument,
'HDEL' => $getKeyFromFirstArgument,
'HEXISTS' => $getKeyFromFirstArgument,
'HGET' => $getKeyFromFirstArgument,
'HGETALL' => $getKeyFromFirstArgument,
'HMGET' => $getKeyFromFirstArgument,
'HMSET' => $getKeyFromFirstArgument,
'HINCRBY' => $getKeyFromFirstArgument,
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
'HKEYS' => $getKeyFromFirstArgument,
'HLEN' => $getKeyFromFirstArgument,
'HSET' => $getKeyFromFirstArgument,
'HSETNX' => $getKeyFromFirstArgument,
'HVALS' => $getKeyFromFirstArgument,
'HSCAN' => $getKeyFromFirstArgument,
/* commands operating on HyperLogLog */
'PFADD' => $getKeyFromFirstArgument,
'PFCOUNT' => $getKeyFromAllArguments,
'PFMERGE' => $getKeyFromAllArguments,
/* scripting */
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
@@ -180,7 +189,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
* 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.
* @param mixed $callback A valid callable object or NULL.
*/
public function setCommandHandler($commandId, $callback = null)
{
@@ -188,6 +197,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
if (!isset($callback)) {
unset($this->commands[$commandId]);
return;
}
@@ -201,7 +211,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
@@ -213,7 +223,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
* 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.
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromAllArguments(CommandInterface $command)
@@ -229,7 +239,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
* 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.
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
@@ -249,7 +259,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
/**
* Extracts the key from BLPOP and BRPOP commands.
*
* @param CommandInterface $command Command instance.
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
@@ -264,7 +274,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
/**
* Extracts the key from BITOP command.
*
* @param CommandInterface $command Command instance.
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBitOp(CommandInterface $command)
@@ -279,7 +289,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
/**
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
*
* @param CommandInterface $command Command instance.
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
@@ -295,7 +305,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
@@ -344,8 +354,8 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
/**
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys Array of keys.
* @return Boolean
* @param array $keys Array of keys.
* @return bool
*/
protected function checkSameHashForKeys(Array $keys)
{
@@ -372,7 +382,7 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
* 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.
* @param string $key A key.
* @return string
*/
protected function extractKeyTag($key)
+12 -253
View File
@@ -12,8 +12,7 @@
namespace Predis\Cluster;
use Predis\Cluster\Hash\CRC16HashGenerator;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptedCommand;
use Predis\Cluster\Hash\HashGeneratorInterface;
/**
* Default class used by Predis to calculate hashes out of keys of
@@ -21,271 +20,31 @@ use Predis\Command\ScriptedCommand;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RedisClusterHashStrategy implements CommandHashStrategyInterface
class RedisClusterHashStrategy extends PredisClusterHashStrategy
{
private $commands;
private $hashGenerator;
/**
*
*/
public function __construct()
public function __construct(HashGeneratorInterface $hashGenerator = null)
{
$this->commands = $this->getDefaultCommands();
$this->hashGenerator = new CRC16HashGenerator();
parent::__construct($hashGenerator ?: new CRC16HashGenerator());
}
/**
* Returns the default map of supported commands with their handlers.
* Returns only the hashable part of a key (delimited by "{...}"), or the
* whole key if a key tag is not found in the string.
*
* @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,
'SSCAN' => $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,
'ZSCAN' => $keyIsFirstArgument,
/* commands operating on hashes */
'HDEL' => $keyIsFirstArgument,
'HEXISTS' => $keyIsFirstArgument,
'HGET' => $keyIsFirstArgument,
'HGETALL' => $keyIsFirstArgument,
'HMGET' => $keyIsFirstArgument,
'HMSET' => $keyIsFirstArgument,
'HINCRBY' => $keyIsFirstArgument,
'HINCRBYFLOAT' => $keyIsFirstArgument,
'HKEYS' => $keyIsFirstArgument,
'HLEN' => $keyIsFirstArgument,
'HSET' => $keyIsFirstArgument,
'HSETNX' => $keyIsFirstArgument,
'HVALS' => $keyIsFirstArgument,
'HSCAN' => $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.
* @param string $key A key.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
protected function extractKeyTag($key)
{
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)
{
if ($command instanceof ScriptedCommand) {
$keys = $command->getKeys();
} else {
$keys = 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);
if (false !== $start = strpos($key, '{')) {
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
$key = substr($key, $start, $end - $start);
}
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function getKeyHash($key)
{
return $this->hashGenerator->hash($key);
return $key;
}
}
@@ -43,8 +43,8 @@ abstract class CursorBasedIterator implements Iterator
/**
* @param ClientInterface $client Client connected to Redis.
* @param string $match Pattern to match during the server-side iteration.
* @param int $count Hints used by Redis to compute the number of results per iteration.
* @param string $match Pattern to match during the server-side iteration.
* @param int $count Hints used by Redis to compute the number of results per iteration.
*/
public function __construct(ClientInterface $client, $match = null, $count = null)
{
@@ -60,8 +60,8 @@ abstract class CursorBasedIterator implements Iterator
* command required to fetch elements from the server to perform
* the iteration.
*
* @param ClientInterface Client connected to Redis.
* @param string $commandID Command ID.
* @param ClientInterface $client Client connected to Redis.
* @param string $commandID Command ID.
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
@@ -109,7 +109,7 @@ abstract class CursorBasedIterator implements Iterator
*
* @return array
*/
protected abstract function executeCommand();
abstract protected function executeCommand();
/**
* Populates the local buffer of elements fetched from the
@@ -166,16 +166,18 @@ abstract class CursorBasedIterator implements Iterator
*/
public function next()
{
if (!$this->elements && $this->fetchmore) {
$this->fetch();
}
tryFetch: {
if (!$this->elements && $this->fetchmore) {
$this->fetch();
}
if ($this->elements) {
$this->extractNext();
} else if ($this->cursor) {
$this->next();
} else {
$this->valid = false;
if ($this->elements) {
$this->extractNext();
} elseif ($this->cursor) {
goto tryFetch;
} else {
$this->valid = false;
}
}
}
+4 -4
View File
@@ -43,8 +43,8 @@ class ListKey implements Iterator
/**
* @param ClientInterface $client Client connected to Redis.
* @param string $key Redis list key.
* @param int $count Number of items retrieved on each fetch operation.
* @param string $key Redis list key.
* @param int $count Number of items retrieved on each fetch operation.
*/
public function __construct(ClientInterface $client, $key, $count = 10)
{
@@ -66,8 +66,8 @@ class ListKey implements Iterator
* command required to fetch elements from the server to perform
* the iteration.
*
* @param ClientInterface Client connected to Redis.
* @param string $commandID Command ID.
* @param ClientInterface $client Client connected to Redis.
* @param string $commandID Command ID.
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
+7 -11
View File
@@ -24,7 +24,7 @@ abstract class AbstractCommand implements CommandInterface
/**
* Returns a filtered array of the arguments.
*
* @param array $arguments List of arguments.
* @param array $arguments List of arguments.
* @return array
*/
protected function filterArguments(Array $arguments)
@@ -42,9 +42,7 @@ abstract class AbstractCommand implements CommandInterface
}
/**
* Sets the arguments array without filtering.
*
* @param array $arguments List of arguments.
* {@inheritdoc}
*/
public function setRawArguments(Array $arguments)
{
@@ -61,9 +59,7 @@ abstract class AbstractCommand implements CommandInterface
}
/**
* Gets the argument from the arguments list at the specified index.
*
* @param array $arguments Position of the argument.
* {@inheritdoc}
*/
public function getArgument($index)
{
@@ -101,8 +97,8 @@ abstract class AbstractCommand implements CommandInterface
/**
* Helper function used to reduce a list of arguments to a string.
*
* @param string $accumulator Temporary string.
* @param string $argument Current argument.
* @param string $accumulator Temporary string.
* @param string $argument Current argument.
* @return string
*/
protected function toStringArgumentReducer($accumulator, $argument)
@@ -133,7 +129,7 @@ abstract class AbstractCommand implements CommandInterface
/**
* Normalizes the arguments array passed to a Redis command.
*
* @param array $arguments Arguments for a command.
* @param array $arguments Arguments for a command.
* @return array
*/
public static function normalizeArguments(Array $arguments)
@@ -148,7 +144,7 @@ abstract class AbstractCommand implements CommandInterface
/**
* Normalizes the arguments array passed to a variadic Redis command.
*
* @param array $arguments Arguments for a command.
* @param array $arguments Arguments for a command.
* @return array
*/
public static function normalizeVariadic(Array $arguments)
+3 -2
View File
@@ -62,14 +62,15 @@ interface CommandInterface
/**
* Gets the argument of the command at the specified index.
*
* @return array
* @param int $index Index of the desired argument.
* @return mixed
*/
public function getArgument($index);
/**
* Parses a reply buffer and returns a PHP object.
*
* @param string $data Binary string containing the whole reply.
* @param string $data Binary string containing the whole reply.
* @return mixed
*/
public function parseResponse($data);
+1 -1
View File
@@ -41,7 +41,7 @@ class HashScan extends PrefixableCommand
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
* @param array $options List of options.
* @return array
*/
protected function prepareOptions($options)
+43
View File
@@ -0,0 +1,43 @@
<?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\Command;
/**
* @link http://redis.io/commands/pfadd
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogAdd extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFADD';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeVariadic($arguments);
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
+43
View File
@@ -0,0 +1,43 @@
<?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\Command;
/**
* @link http://redis.io/commands/pfcount
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogCount extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFCOUNT';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?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\Command;
/**
* @link http://redis.io/commands/pfmerge
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogMerge extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFMERGE';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ class KeyScan extends AbstractCommand
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
* @param array $options List of options.
* @return array
*/
protected function prepareOptions($options)
+5 -5
View File
@@ -22,7 +22,7 @@ class PrefixHelpers
* Applies the specified prefix only the first argument.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
* @param string $prefix Prefix string.
*/
public static function first(CommandInterface $command, $prefix)
{
@@ -36,7 +36,7 @@ class PrefixHelpers
* Applies the specified prefix to all the arguments.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
* @param string $prefix Prefix string.
*/
public static function all(CommandInterface $command, $prefix)
{
@@ -53,7 +53,7 @@ class PrefixHelpers
* Applies the specified prefix only to even arguments in the list.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
* @param string $prefix Prefix string.
*/
public static function interleaved(CommandInterface $command, $prefix)
{
@@ -72,7 +72,7 @@ class PrefixHelpers
* Applies the specified prefix to all the arguments but the first one.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
* @param string $prefix Prefix string.
*/
public static function skipFirst(CommandInterface $command, $prefix)
{
@@ -91,7 +91,7 @@ class PrefixHelpers
* Applies the specified prefix to all the arguments but the last one.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
* @param string $prefix Prefix string.
*/
public static function skipLast(CommandInterface $command, $prefix)
{
+157
View File
@@ -0,0 +1,157 @@
<?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\Command;
use InvalidArgumentException;
/**
* Class for generic "anonymous" Redis commands.
*
* This command class does not filter input arguments or parse responses, but
* can be used to leverage the standard Predis API to execute any command simply
* by providing the needed arguments following the command signature as defined
* by Redis in its documentation.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RawCommand implements CommandInterface
{
private $hash;
private $commandID;
private $arguments;
public function __construct(array $arguments)
{
if (!$arguments) {
throw new InvalidArgumentException("Arguments array is missing the command ID");
}
$this->commandID = strtoupper(array_shift($arguments));
$this->arguments = $arguments;
}
/**
* Creates a new raw command using a variadic method.
*
* @param string $commandID Redis command ID.
* @param string ... Arguments list for the command.
* @return CommandInterface
*/
public static function create($commandID /* [ $arg, ... */)
{
$arguments = func_get_args();
$command = new self($arguments);
return $command;
}
/**
* {@inheritdoc}
*/
public function getId()
{
return $this->commandID;
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
unset($this->hash);
}
/**
* {@inheritdoc}
*/
public function setRawArguments(array $arguments)
{
$this->setArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function getArguments()
{
return $this->arguments;
}
/**
* {@inheritdoc}
*/
public function getArgument($index)
{
if (isset($this->arguments[$index])) {
return $this->arguments[$index];
}
}
/**
* {@inheritdoc}
*/
public function setHash($hash)
{
$this->hash = $hash;
}
/**
* {@inheritdoc}
*/
public function getHash()
{
if (isset($this->hash)) {
return $this->hash;
}
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return $data;
}
/**
* Helper function used to reduce a list of arguments to a string.
*
* @param string $accumulator Temporary string.
* @param string $argument Current argument.
* @return string
*/
protected function toStringArgumentReducer($accumulator, $argument)
{
if (strlen($argument) > 32) {
$argument = substr($argument, 0, 32) . '[...]';
}
$accumulator .= " $argument";
return $accumulator;
}
/**
* Returns a partial string representation of the command with its arguments.
*
* @return string
*/
public function __toString()
{
return array_reduce(
$this->getArguments(),
array($this, 'toStringArgumentReducer'),
$this->getId()
);
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ abstract class ScriptedCommand extends ServerEvalSHA
*
* @return string
*/
public abstract function getScript();
abstract public function getScript();
/**
* Specifies the number of arguments that should be considered as keys.
+5 -2
View File
@@ -12,7 +12,10 @@
namespace Predis\Command;
/**
* @link http://redis.io/commands/client
* @link http://redis.io/commands/client-list
* @link http://redis.io/commands/client-kill
* @link http://redis.io/commands/client-getname
* @link http://redis.io/commands/client-setname
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerClient extends AbstractCommand
@@ -47,7 +50,7 @@ class ServerClient extends AbstractCommand
* Parses the reply buffer and returns the list of clients returned by
* the CLIENT LIST command.
*
* @param string $data Reply buffer
* @param string $data Reply buffer
* @return array
*/
protected function parseClientList($data)
+27
View File
@@ -0,0 +1,27 @@
<?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\Command;
/**
* @link http://redis.io/commands/command
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerCommand extends AbstractCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'COMMAND';
}
}
+26 -15
View File
@@ -34,31 +34,42 @@ class ServerInfo extends AbstractCommand
$infoLines = preg_split('/\r?\n/', $data);
foreach ($infoLines as $row) {
@list($k, $v) = explode(':', $row);
if ($row === '' || !isset($v)) {
if (strpos($row, ':') === false) {
continue;
}
if (!preg_match('/^db\d+$/', $k)) {
if ($k === 'allocation_stats') {
$info[$k] = $this->parseAllocationStats($v);
continue;
}
$info[$k] = $v;
} else {
$info[$k] = $this->parseDatabaseStats($v);
}
list($k, $v) = $this->parseRow($row);
$info[$k] = $v;
}
return $info;
}
/**
* Parses single row of the reply buffer and returns the key-value pair.
*
* @param string $row Single row of the reply buffer.
* @return array
*/
public function parseRow($row)
{
list($k, $v) = explode(':', $row, 2);
if (!preg_match('/^db\d+$/', $k)) {
if ($k === 'allocation_stats') {
$v = $this->parseAllocationStats($v);
}
} else {
$v = $this->parseDatabaseStats($v);
}
return array($k, $v);
}
/**
* Parses the reply buffer and extracts the statistics of each logical DB.
*
* @param string $str Reply buffer.
* @param string $str Reply buffer.
* @return array
*/
protected function parseDatabaseStats($str)
@@ -76,7 +87,7 @@ class ServerInfo extends AbstractCommand
/**
* Parses the reply buffer and extracts the allocation statistics.
*
* @param string $str Reply buffer.
* @param string $str Reply buffer.
* @return array
*/
protected function parseAllocationStats($str)
+2 -12
View File
@@ -41,18 +41,8 @@ class ServerInfoV26x extends ServerInfo
continue;
}
list($k, $v) = explode(':', $row);
if (!preg_match('/^db\d+$/', $k)) {
if ($k === 'allocation_stats') {
$current[$k] = $this->parseAllocationStats($v);
continue;
}
$current[$k] = $v;
} else {
$current[$k] = $this->parseDatabaseStats($v);
}
list($k, $v) = $this->parseRow($row);
$current[$k] = $v;
}
return $info;
+1 -1
View File
@@ -41,7 +41,7 @@ class SetScan extends PrefixableCommand
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
* @param array $options List of options.
* @return array
*/
protected function prepareOptions($options)
+1 -1
View File
@@ -33,7 +33,7 @@ class ZSetAdd extends PrefixableCommand
if (count($arguments) === 2 && is_array($arguments[1])) {
$flattened = array($arguments[0]);
foreach($arguments[1] as $member => $score) {
foreach ($arguments[1] as $member => $score) {
$flattened[] = $score;
$flattened[] = $member;
}
+27
View File
@@ -0,0 +1,27 @@
<?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\Command;
/**
* @link http://redis.io/commands/zlexcount
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZSetLexCount extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZLEXCOUNT';
}
}
+3 -2
View File
@@ -41,6 +41,7 @@ class ZSetRange extends PrefixableCommand
if ($lastType === 'array') {
$options = $this->prepareOptions(array_pop($arguments));
return array_merge($arguments, $options);
}
}
@@ -51,7 +52,7 @@ class ZSetRange extends PrefixableCommand
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
* @param array $options List of options.
* @return array
*/
protected function prepareOptions($options)
@@ -69,7 +70,7 @@ class ZSetRange extends PrefixableCommand
/**
* Checks for the presence of the WITHSCORES modifier.
*
* @return Boolean
* @return bool
*/
protected function withScores()
{
+54
View File
@@ -0,0 +1,54 @@
<?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\Command;
/**
* @link http://redis.io/commands/zrangebylex
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZSetRangeByLex extends ZSetRange
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZRANGEBYLEX';
}
/**
* {@inheritdoc}
*/
protected function prepareOptions($options)
{
$opts = array_change_key_case($options, CASE_UPPER);
$finalizedOpts = array();
if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
$limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
$finalizedOpts[] = 'LIMIT';
$finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
$finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
}
return $finalizedOpts;
}
/**
* {@inheritdoc}
*/
protected function withScores()
{
return false;
}
}
@@ -0,0 +1,27 @@
<?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\Command;
/**
* @link http://redis.io/commands/zremrangebylex
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZSetRemoveRangeByLex extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZREMRANGEBYLEX';
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ class ZSetScan extends PrefixableCommand
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
* @param array $options List of options.
* @return array
*/
protected function prepareOptions($options)
+1 -1
View File
@@ -50,7 +50,7 @@ class ZSetUnionStore extends PrefixableCommand
/**
* Returns a list of options and modifiers compatible with Redis.
*
* @param array $options List of options.
* @param array $options List of options.
* @return array
*/
private function prepareOptions($options)
+5 -5
View File
@@ -23,10 +23,10 @@ abstract class CommunicationException extends PredisException
private $connection;
/**
* @param SingleConnectionInterface $connection Connection that generated the exception.
* @param string $message Error message.
* @param int $code Error code.
* @param \Exception $innerException Inner exception for wrapping the original error.
* @param SingleConnectionInterface $connection Connection that generated the exception.
* @param string $message Error message.
* @param int $code Error code.
* @param \Exception $innerException Inner exception for wrapping the original error.
*/
public function __construct(
SingleConnectionInterface $connection, $message = null, $code = null, \Exception $innerException = null
@@ -48,7 +48,7 @@ abstract class CommunicationException extends PredisException
/**
* Indicates if the receiver should reset the underlying connection.
*
* @return Boolean
* @return bool
*/
public function shouldResetConnection()
{
+7 -5
View File
@@ -50,7 +50,8 @@ abstract class AbstractConnection implements SingleConnectionInterface
/**
* Checks some of the parameters used to initialize the connection.
*
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ConnectionParametersInterface $parameters Initialization parameters for the connection.
* @return ConnectionParametersInterface
*/
protected function checkParameters(ConnectionParametersInterface $parameters)
{
@@ -73,7 +74,7 @@ abstract class AbstractConnection implements SingleConnectionInterface
*
* @return mixed
*/
protected abstract function createResource();
abstract protected function createResource();
/**
* {@inheritdoc}
@@ -117,6 +118,7 @@ abstract class AbstractConnection implements SingleConnectionInterface
public function executeCommand(CommandInterface $command)
{
$this->writeCommand($command);
return $this->readResponse($command);
}
@@ -132,7 +134,7 @@ abstract class AbstractConnection implements SingleConnectionInterface
* Helper method to handle connection errors.
*
* @param string $message Error message.
* @param int $code Error code.
* @param int $code Error code.
*/
protected function onConnectionError($message, $code = null)
{
@@ -152,8 +154,8 @@ abstract class AbstractConnection implements SingleConnectionInterface
/**
* Helper method to handle not supported connection parameters.
*
* @param string $option Name of the option.
* @param mixed $parameters Parameters used to initialize the connection.
* @param string $option Name of the option.
* @param mixed $parameters Parameters used to initialize the connection.
*/
protected function onInvalidOption($option, $parameters = null)
{
@@ -31,15 +31,15 @@ interface AggregatedConnectionInterface extends ConnectionInterface
* Removes the specified connection instance from the aggregated
* connection.
*
* @param SingleConnectionInterface $connection Instance of a connection.
* @return Boolean Returns true if the connection was in the pool.
* @param SingleConnectionInterface $connection Instance of a connection.
* @return bool Returns true if the connection was in the pool.
*/
public function remove(SingleConnectionInterface $connection);
/**
* Gets the actual connection instance in charge of the specified command.
*
* @param CommandInterface $command Instance of a Redis command.
* @param CommandInterface $command Instance of a Redis command.
* @return SingleConnectionInterface
*/
public function getConnection(CommandInterface $command);
@@ -48,7 +48,7 @@ interface AggregatedConnectionInterface extends ConnectionInterface
* Retrieves a connection instance from the aggregated connection
* using an alias.
*
* @param string $connectionId Alias of a connection
* @param string $connectionId Alias of a connection
* @return SingleConnectionInterface
*/
public function getConnectionById($connectionId);
@@ -27,7 +27,7 @@ class ComposableStreamConnection extends StreamConnection implements ComposableC
/**
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ProtocolInterface $protocol A protocol processor.
* @param ProtocolInterface $protocol A protocol processor.
*/
public function __construct(ConnectionParametersInterface $parameters, ProtocolInterface $protocol = null)
{
+1 -2
View File
@@ -11,7 +11,6 @@
namespace Predis\Connection;
use Predis\Profile\ServerProfile;
use Predis\Profile\ServerProfileInterface;
/**
@@ -55,7 +54,7 @@ class ConnectionFactory implements ConnectionFactoryInterface
* implementing Predis\Connection\SingleConnectionInterface. Optionally,
* callable objects are used for lazy initialization of connection objects.
*
* @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
* @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
* @return mixed
*/
protected function checkInitializer($initializer)
@@ -22,8 +22,8 @@ interface ConnectionFactoryInterface
/**
* Defines or overrides the connection class identified by a scheme prefix.
*
* @param string $scheme URI scheme identifying the connection class.
* @param mixed $initializer FQN of a connection class or a callable object for lazy initialization.
* @param string $scheme URI scheme identifying the connection class.
* @param mixed $initializer FQN of a connection class or a callable object for lazy initialization.
*/
public function define($scheme, $initializer);
@@ -37,7 +37,7 @@ interface ConnectionFactoryInterface
/**
* Creates a new connection object.
*
* @param mixed $parameters Parameters for the connection.
* @param mixed $parameters Parameters for the connection.
* @return SingleConnectionInterface
*/
public function create($parameters);
@@ -45,8 +45,8 @@ interface ConnectionFactoryInterface
/**
* Prepares an aggregation of connection objects.
*
* @param AggregatedConnectionInterface $cluster Instance of an aggregated connection class.
* @param array $parameters List of parameters for each connection object.
* @param AggregatedConnectionInterface $cluster Instance of an aggregated connection class.
* @param array $parameters List of parameters for each connection object.
* @return AggregatedConnectionInterface
*/
public function createAggregated(AggregatedConnectionInterface $cluster, Array $parameters);
@@ -34,7 +34,7 @@ interface ConnectionInterface
/**
* Returns if the connection is open.
*
* @return Boolean
* @return bool
*/
public function isConnected();
@@ -48,7 +48,7 @@ interface ConnectionInterface
/**
* Reads the reply for a Redis command from the connection.
*
* @param CommandInterface $command Instance of a Redis command.
* @param CommandInterface $command Instance of a Redis command.
* @return mixed
*/
public function readResponse(CommandInterface $command);
@@ -56,7 +56,7 @@ interface ConnectionInterface
/**
* Writes a Redis command to the connection and reads back the reply.
*
* @param CommandInterface $command Instance of a Redis command.
* @param CommandInterface $command Instance of a Redis command.
* @return mixed
*/
public function executeCommand(CommandInterface $command);
+10 -12
View File
@@ -30,7 +30,7 @@ class ConnectionParameters implements ConnectionParametersInterface
);
/**
* @param string|array Connection parameters in the form of an URI string or a named array.
* @param string|array $parameters Connection parameters in the form of an URI string or a named array.
*/
public function __construct($parameters = array())
{
@@ -71,8 +71,8 @@ class ConnectionParameters implements ConnectionParametersInterface
/**
* Validates value as boolean.
*
* @param mixed $value Input value.
* @return boolean
* @param mixed $value Input value.
* @return bool
*/
private static function castBoolean($value)
{
@@ -82,7 +82,7 @@ class ConnectionParameters implements ConnectionParametersInterface
/**
* Validates value as float.
*
* @param mixed $value Input value.
* @param mixed $value Input value.
* @return float
*/
private static function castFloat($value)
@@ -93,7 +93,7 @@ class ConnectionParameters implements ConnectionParametersInterface
/**
* Validates value as integer.
*
* @param mixed $value Input value.
* @param mixed $value Input value.
* @return int
*/
private static function castInteger($value)
@@ -104,7 +104,7 @@ class ConnectionParameters implements ConnectionParametersInterface
/**
* Parses an URI string and returns an array of connection parameters.
*
* @param string $uri Connection string.
* @param string $uri Connection string.
* @return array
*/
public static function parseURI($uri)
@@ -119,12 +119,10 @@ class ConnectionParameters implements ConnectionParametersInterface
}
if (isset($parsed['query'])) {
foreach (explode('&', $parsed['query']) as $kv) {
@list($k, $v) = explode('=', $kv);
$parsed[$k] = $v;
}
parse_str($parsed['query'], $queryarray);
unset($parsed['query']);
$parsed = array_merge($parsed, $queryarray);
}
return $parsed;
@@ -133,7 +131,7 @@ class ConnectionParameters implements ConnectionParametersInterface
/**
* Validates and converts each value of the connection parameters array.
*
* @param array $parameters Connection parameters.
* @param array $parameters Connection parameters.
* @return array
*/
private function filter(Array $parameters)
@@ -22,15 +22,15 @@ interface ConnectionParametersInterface
/**
* Checks if the specified parameters is set.
*
* @param string $property Name of the property.
* @return Boolean
* @param string $parameter Name of the parameter.
* @return bool
*/
public function __isset($parameter);
/**
* Returns the value of the specified parameter.
*
* @param string $parameter Name of the parameter.
* @param string $parameter Name of the parameter.
* @return mixed
*/
public function __get($parameter);
+12 -8
View File
@@ -37,6 +37,7 @@ use Predis\Command\CommandInterface;
* - scheme: it can be either 'tcp' or 'unix'.
* - host: hostname or IP address of the server.
* - port: TCP port of the server.
* - path: path of a UNIX domain socket when scheme is 'unix'.
* - timeout: timeout to perform the connection.
* - read_write_timeout: timeout of read / write operations.
*
@@ -136,7 +137,6 @@ class PhpiredisConnection extends AbstractConnection
/**
* Gets the handler used by the protocol reader to handle Redis errors.
*
* @param Boolean $throw_errors Specify if Redis errors throw exceptions.
* @return \Closure
*/
private function getErrorHandler()
@@ -183,7 +183,7 @@ class PhpiredisConnection extends AbstractConnection
/**
* Sets options on the socket resource from the connection parameters.
*
* @param resource $socket Socket resource.
* @param resource $socket Socket resource.
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
*/
private function setSocketOptions($socket, ConnectionParametersInterface $parameters)
@@ -223,10 +223,10 @@ class PhpiredisConnection extends AbstractConnection
/**
* Gets the address from the connection parameters.
*
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @return string
*/
private function getAddress(ConnectionParametersInterface $parameters)
protected static function getAddress(ConnectionParametersInterface $parameters)
{
if ($parameters->scheme === 'unix') {
return $parameters->path;
@@ -235,9 +235,10 @@ class PhpiredisConnection extends AbstractConnection
$host = $parameters->host;
if (ip2long($host) === false) {
if (($addresses = gethostbynamel($host)) === false) {
$this->onConnectionError("Cannot resolve the address of $host");
if (false === $addresses = gethostbynamel($host)) {
return false;
}
return $addresses[array_rand($addresses)];
}
@@ -247,12 +248,15 @@ class PhpiredisConnection extends AbstractConnection
/**
* Opens the actual connection to the server with a timeout.
*
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @return string
*/
private function connectWithTimeout(ConnectionParametersInterface $parameters)
{
$host = self::getAddress($parameters);
if (false === $host = self::getAddress($parameters)) {
$this->onConnectionError("Cannot resolve the address of '$parameters->host'.");
}
$socket = $this->getResource();
socket_set_nonblock($socket);
@@ -37,6 +37,7 @@ use Predis\Command\CommandInterface;
* - scheme: it can be either 'tcp' or 'unix'.
* - host: hostname or IP address of the server.
* - port: TCP port of the server.
* - path: path of a UNIX domain socket when scheme is 'unix'.
* - timeout: timeout to perform the connection.
* - read_write_timeout: timeout of read / write operations.
* - async_connect: performs the connection asynchronously.
@@ -95,6 +96,52 @@ class PhpiredisStreamConnection extends StreamConnection
return parent::checkParameters($parameters);
}
/**
* {@inheritdoc}
*/
protected function tcpStreamInitializer(ConnectionParametersInterface $parameters)
{
$uri = "tcp://{$parameters->host}:{$parameters->port}";
$flags = STREAM_CLIENT_CONNECT;
$socket = null;
if (isset($parameters->async_connect) && $parameters->async_connect) {
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
}
if (isset($parameters->persistent) && $parameters->persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
$uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
}
$resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
if (!$resource) {
$this->onConnectionError(trim($errstr), $errno);
}
if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
$rwtimeout = (float) $parameters->read_write_timeout;
$rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
$timeout = array(
'sec' => $timeoutSeconds = floor($rwtimeout),
'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
);
$socket = $socket ?: socket_import_stream($resource);
@socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
@socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
}
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
$socket = $socket ?: socket_import_stream($resource);
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
}
return $resource;
}
/**
* Initializes the protocol reader resource.
*/
@@ -132,7 +179,6 @@ class PhpiredisStreamConnection extends StreamConnection
/**
* Gets the handler used by the protocol reader to handle Redis errors.
*
* @param Boolean $throw_errors Specify if Redis errors throw exceptions.
* @return \Closure
*/
protected function getErrorHandler()
@@ -151,11 +197,10 @@ class PhpiredisStreamConnection extends StreamConnection
$reader = $this->reader;
while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
$buffer = fread($socket, 4096);
$buffer = stream_socket_recvfrom($socket, 4096);
if ($buffer === false || $buffer === '') {
$this->onConnectionError('Error while reading bytes from the server');
return;
}
phpiredis_reader_feed($reader, $buffer);
+4 -4
View File
@@ -112,8 +112,8 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \
/**
* Removes a connection instance using its alias or index.
*
* @param string $connectionId Alias or index of a connection.
* @return Boolean Returns true if the connection was in the pool.
* @param string $connectionId Alias or index of a connection.
* @return bool Returns true if the connection was in the pool.
*/
public function removeById($connectionId)
{
@@ -151,7 +151,7 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \
/**
* Retrieves a connection instance from the cluster using a key.
*
* @param string $key Key of a Redis value.
* @param string $key Key of a Redis value.
* @return SingleConnectionInterface
*/
public function getConnectionByKey($key)
@@ -216,7 +216,7 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \
/**
* Executes the specified Redis command on all the nodes of a cluster.
*
* @param CommandInterface $command A Redis command.
* @param CommandInterface $command A Redis command.
* @return array
*/
public function executeCommandOnNodes(CommandInterface $command)
+303 -157
View File
@@ -11,24 +11,46 @@
namespace Predis\Connection;
use Predis\ClientException;
use Predis\Cluster\CommandHashStrategyInterface;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use OutOfBoundsException;
use Predis\NotSupportedException;
use Predis\ResponseErrorInterface;
use Predis\Cluster\CommandHashStrategyInterface;
use Predis\Cluster\RedisClusterHashStrategy;
use Predis\Command\CommandInterface;
use Predis\Command\RawCommand;
/**
* Abstraction for Redis cluster (Redis v3.0).
* Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
*
* This connection backend offers smart support for redis-cluster by handling
* automatic slots map (re)generation upon -MOVED or -ASK responses returned by
* Redis when redirecting a client to a different node.
*
* The cluster can be pre-initialized using only a subset of the actual nodes in
* the cluster, Predis will do the rest by adjusting the slots map and creating
* the missing underlying connection instances on the fly.
*
* It is possible to pre-associate connections to a slots range with the "slots"
* parameter in the form "$first-$last". This can greatly reduce runtime node
* guessing and redirections.
*
* It is also possible to ask for the full and updated slots map directly to one
* of the nodes and optionally enable such a behaviour upon -MOVED redirections.
* Asking for the cluster configuration to Redis is actually done by issuing a
* CLUSTER SLOTS command to a random node in the pool.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \Countable
class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Countable
{
private $pool;
private $slots;
private $askClusterNodes = true;
private $defaultParameters = array();
private $pool = array();
private $slots = array();
private $slotsMap;
private $slotsPerNode;
private $strategy;
private $connections;
@@ -37,8 +59,6 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
*/
public function __construct(ConnectionFactoryInterface $connections = null)
{
$this->pool = array();
$this->slots = array();
$this->strategy = new RedisClusterHashStrategy();
$this->connections = $connections ?: new ConnectionFactory();
}
@@ -62,7 +82,7 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
*/
public function connect()
{
foreach ($this->pool as $connection) {
if ($connection = $this->getRandomConnection()) {
$connection->connect();
}
}
@@ -83,10 +103,7 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
public function add(SingleConnectionInterface $connection)
{
$this->pool[(string) $connection] = $connection;
unset(
$this->slotsMap,
$this->slotsPerNode
);
unset($this->slotsMap);
}
/**
@@ -94,11 +111,10 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
*/
public function remove(SingleConnectionInterface $connection)
{
if (($id = array_search($connection, $this->pool, true)) !== false) {
if (false !== $id = array_search($connection, $this->pool, true)) {
unset(
$this->pool[$id],
$this->slotsMap,
$this->slotsPerNode
$this->slotsMap
);
return true;
@@ -108,18 +124,17 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
}
/**
* Removes a connection instance using its alias or index.
* Removes a connection instance by using its identifier.
*
* @param string $connectionId Alias or index of a connection.
* @return Boolean Returns true if the connection was in the pool.
* @param string $connectionID Connection identifier.
* @return bool True if the connection was in the pool.
*/
public function removeById($connectionId)
public function removeById($connectionID)
{
if (isset($this->pool[$connectionId])) {
if (isset($this->pool[$connectionID])) {
unset(
$this->pool[$connectionId],
$this->slotsMap,
$this->slotsPerNode
$this->pool[$connectionID],
$this->slotsMap
);
return true;
@@ -129,14 +144,17 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
}
/**
* Builds the slots map for the cluster.
* Generates the current slots map by guessing the cluster configuration out
* of the connection parameters of the connections in the pool.
*
* @return array
* Generation is based on the same algorithm used by Redis to generate the
* cluster, so it is most effective when all of the connections supplied on
* initialization have the "slots" parameter properly set accordingly to the
* current cluster configuration.
*/
public function buildSlotsMap()
{
$this->slotsMap = array();
$this->slotsPerNode = (int) (16384 / count($this->pool));
foreach ($this->pool as $connectionID => $connection) {
$parameters = $connection->getParameters();
@@ -145,8 +163,38 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
continue;
}
list($first, $last) = explode('-', $parameters->slots, 2);
$this->setSlots($first, $last, $connectionID);
$slots = explode('-', $parameters->slots, 2);
$this->setSlots($slots[0], $slots[1], $connectionID);
}
}
/**
* Generates an updated slots map fetching the cluster configuration using
* the CLUSTER SLOTS command against the specified node or a random one from
* the pool.
*
* @param SingleConnectionInterface $connection Optional connection instance.
* @return array
*/
public function askClusterNodes(SingleConnectionInterface $connection = null)
{
if (!$connection && !$connection = $this->getRandomConnection()) {
return array();
}
$command = RawCommand::create('CLUSTER', 'SLOTS');
$response = $connection->executeCommand($command);
foreach ($response as $slots) {
// We only support master servers for now, so we ignore subsequent
// elements in the $slots array identifying slaves.
list($start, $end, $master) = $slots;
if ($master[0] === '') {
$this->setSlots($start, $end, (string) $connection);
} else {
$this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
}
}
return $this->slotsMap;
@@ -167,79 +215,34 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
}
/**
* Preassociate a connection to a set of slots to avoid runtime guessing.
* Pre-associates a connection to a slots range to avoid runtime guessing.
*
* @todo Check type or existence of the specified connection.
* @todo Cluster loses the slots assigned with this methods when adding / removing connections.
*
* @param int $first Initial slot.
* @param int $last Last slot.
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
* @param SingleConnectionInterface|string $connection ID or connection instance.
*/
public function setSlots($first, $last, $connection)
{
if ($first < 0x0000 || $first > 0x3FFF || $last < 0x0000 || $last > 0x3FFF || $last < $first) {
throw new \OutOfBoundsException("Invalid slot values for $connection: [$first-$last]");
if ($first < 0x0000 || $first > 0x3FFF ||
$last < 0x0000 || $last > 0x3FFF ||
$last < $first
) {
throw new OutOfBoundsException(
"Invalid slot range for $connection: [$first-$last]"
);
}
$this->slotsMap = $this->getSlotsMap() + array_fill($first, $last - $first + 1, (string) $connection);
$slots = array_fill($first, $last - $first + 1, (string) $connection);
$this->slotsMap = $this->getSlotsMap() + $slots;
}
/**
* {@inheritdoc}
*/
public function getConnection(CommandInterface $command)
{
$hash = $this->strategy->getHash($command);
if (!isset($hash)) {
throw new NotSupportedException("Cannot use {$command->getId()} with redis-cluster");
}
$slot = $hash & 0x3FFF;
if (isset($this->slots[$slot])) {
return $this->slots[$slot];
}
$this->slots[$slot] = $connection = $this->pool[$this->guessNode($slot)];
return $connection;
}
/**
* Returns the connection associated to the specified slot.
* Guesses the correct node associated to a given slot using a precalculated
* slots map, falling back to the same logic used by Redis to initialize a
* cluster (best-effort).
*
* @param int $slot Slot ID.
* @return SingleConnectionInterface
*/
public function getConnectionBySlot($slot)
{
if ($slot < 0x0000 || $slot > 0x3FFF) {
throw new \OutOfBoundsException("Invalid slot value [$slot]");
}
if (isset($this->slots[$slot])) {
return $this->slots[$slot];
}
return $this->pool[$this->guessNode($slot)];
}
/**
* {@inheritdoc}
*/
public function getConnectionById($connectionId)
{
return isset($this->pool[$connectionId]) ? $this->pool[$connectionId] : null;
}
/**
* Tries guessing the correct node associated to the given slot using a precalculated
* slots map or the same logic used by redis-trib to initialize a redis cluster.
*
* @param int $slot Slot ID.
* @return string
* @param int $slot Slot index.
* @return string Connection ID.
*/
protected function guessNode($slot)
{
@@ -251,51 +254,109 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
return $this->slotsMap[$slot];
}
$index = min((int) ($slot / $this->slotsPerNode), count($this->pool) - 1);
$count = count($this->pool);
$index = min((int) ($slot / (int) (16384 / $count)), $count - 1);
$nodes = array_keys($this->pool);
return $nodes[$index];
}
/**
* Handles -MOVED or -ASK replies by re-executing the command on the server
* specified by the Redis reply.
* Creates a new connection instance from the given connection ID.
*
* @param CommandInterface $command Command that generated the -MOVE or -ASK reply.
* @param string $request Type of request (either 'MOVED' or 'ASK').
* @param string $details Parameters of the MOVED/ASK request.
* @return mixed
* @param string $connectionID Identifier for the connection.
* @return SingleConnectionInterface
*/
protected function onMoveRequest(CommandInterface $command, $request, $details)
protected function createConnection($connectionID)
{
list($slot, $host) = explode(' ', $details, 2);
$connection = $this->getConnectionById($host);
$host = explode(':', $connectionID, 2);
if (!isset($connection)) {
$parameters = array('host' => null, 'port' => null);
list($parameters['host'], $parameters['port']) = explode(':', $host, 2);
$connection = $this->connections->create($parameters);
$parameters = array_merge($this->defaultParameters, array(
'host' => $host[0],
'port' => $host[1],
));
$connection = $this->connections->create($parameters);
return $connection;
}
/**
* {@inheritdoc}
*/
public function getConnection(CommandInterface $command)
{
$hash = $this->strategy->getHash($command);
if (!isset($hash)) {
throw new NotSupportedException(
"Cannot use {$command->getId()} with redis-cluster"
);
}
switch ($request) {
case 'MOVED':
$this->move($connection, $slot);
return $this->executeCommand($command);
$slot = $hash & 0x3FFF;
case 'ASK':
return $connection->executeCommand($command);
default:
throw new ClientException("Unexpected request type for a move request: $request");
if (isset($this->slots[$slot])) {
return $this->slots[$slot];
} else {
return $this->getConnectionBySlot($slot);
}
}
/**
* Assign the connection instance to a new slot and adds it to the
* pool if the connection was not already part of the pool.
* Returns the connection currently associated to a given slot.
*
* @param SingleConnectionInterface $connection Connection instance
* @param int $slot Target slot.
* @param int $slot Slot index.
* @return SingleConnectionInterface
*/
public function getConnectionBySlot($slot)
{
if ($slot < 0x0000 || $slot > 0x3FFF) {
throw new OutOfBoundsException("Invalid slot [$slot]");
}
if (isset($this->slots[$slot])) {
return $this->slots[$slot];
}
$connectionID = $this->guessNode($slot);
if (!$connection = $this->getConnectionById($connectionID)) {
$connection = $this->createConnection($connectionID);
$this->pool[$connectionID] = $connection;
}
return $this->slots[$slot] = $connection;
}
/**
* {@inheritdoc}
*/
public function getConnectionById($connectionID)
{
if (isset($this->pool[$connectionID])) {
return $this->pool[$connectionID];
}
}
/**
* Returns a random connection from the pool.
*
* @return SingleConnectionInterface
*/
protected function getRandomConnection()
{
if ($this->pool) {
return $this->pool[array_rand($this->pool)];
}
}
/**
* Permanently associates the connection instance to a new slot.
* The connection is added to the connections pool if not yet included.
*
* @param SingleConnectionInterface $connection Connection instance.
* @param int $slot Target slot index.
*/
protected function move(SingleConnectionInterface $connection, $slot)
{
@@ -304,53 +365,76 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
}
/**
* Returns the underlying command hash strategy used to hash
* commands by their keys.
* Handles -ERR responses returned by Redis.
*
* @return CommandHashStrategyInterface
*/
public function getCommandHashStrategy()
{
return $this->strategy;
}
/**
* {@inheritdoc}
*/
public function count()
{
return count($this->pool);
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new \ArrayIterator(array_values($this->pool));
}
/**
* Handles -ERR replies from Redis.
*
* @param CommandInterface $command Command that generated the -ERR reply.
* @param ResponseErrorInterface $error Redis error reply object.
* @param CommandInterface $command Command that generated the -ERR response.
* @param ResponseErrorInterface $error Redis error response object.
* @return mixed
*/
protected function handleServerError(CommandInterface $command, ResponseErrorInterface $error)
protected function onErrorResponse(CommandInterface $command, ResponseErrorInterface $error)
{
list($type, $details) = explode(' ', $error->getMessage(), 2);
$details = explode(' ', $error->getMessage(), 2);
switch ($type) {
switch ($details[0]) {
case 'MOVED':
return $this->onMovedResponse($command, $details[1]);
case 'ASK':
return $this->onMoveRequest($command, $type, $details);
return $this->onAskResponse($command, $details[1]);
default:
return $error;
}
}
/**
* Handles -MOVED responses by executing again the command against the node
* indicated by the Redis response.
*
* @param CommandInterface $command Command that generated the -MOVED response.
* @param string $details Parameters of the -MOVED response.
* @return mixed
*/
protected function onMovedResponse(CommandInterface $command, $details)
{
list($slot, $connectionID) = explode(' ', $details, 2);
if (!$connection = $this->getConnectionById($connectionID)) {
$connection = $this->createConnection($connectionID);
}
if ($this->askClusterNodes) {
$this->askClusterNodes($connection);
}
$this->move($connection, $slot);
$response = $this->executeCommand($command);
return $response;
}
/**
* Handles -ASK responses by executing again the command against the node
* indicated by the Redis response.
*
* @param CommandInterface $command Command that generated the -ASK response.
* @param string $details Parameters of the -ASK response.
* @return mixed
*/
protected function onAskResponse(CommandInterface $command, $details)
{
list($slot, $connectionID) = explode(' ', $details, 2);
if (!$connection = $this->getConnectionById($connectionID)) {
$connection = $this->createConnection($connectionID);
}
$connection->executeCommand(RawCommand::create('ASKING'));
$response = $connection->executeCommand($command);
return $response;
}
/**
* {@inheritdoc}
*/
@@ -373,12 +457,74 @@ class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \C
public function executeCommand(CommandInterface $command)
{
$connection = $this->getConnection($command);
$reply = $connection->executeCommand($command);
$response = $connection->executeCommand($command);
if ($reply instanceof ResponseErrorInterface) {
return $this->handleServerError($command, $reply);
if ($response instanceof ResponseErrorInterface) {
return $this->onErrorResponse($command, $response);
}
return $reply;
return $response;
}
/**
* {@inheritdoc}
*/
public function count()
{
return count($this->pool);
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new ArrayIterator(array_values($this->pool));
}
/**
* Returns the underlying hash strategy used to hash commands by their keys.
*
* @return CommandHashStrategyInterface
*/
public function getCommandHashStrategy()
{
return $this->strategy;
}
/**
* Enables automatic fetching of the current slots map from one of the nodes
* using the CLUSTER SLOTS command. This option is disabled by default but
* asking the current slots map to Redis upon -MOVED responses may reduce
* overhead by eliminating the trial-and-error nature of the node guessing
* procedure, mostly when targeting many keys that would end up in a lot of
* redirections.
*
* The slots map can still be manually fetched using the askClusterNodes()
* method whether or not this option is enabled.
*
* @param bool $value Enable or disable the use of CLUSTER SLOTS.
*/
public function enableClusterNodes($value)
{
$this->askClusterNodes = (bool) $value;
}
/**
* Sets a default array of connection parameters to be applied when creating
* new connection instances on the fly when they are not part of the initial
* pool supplied upon cluster initialization.
*
* These parameters are not applied to connections added to the pool using
* the add() method.
*
* @param array $parameters Array of connection parameters.
*/
public function setDefaultParameters(array $parameters)
{
$this->defaultParameters = array_merge(
$this->defaultParameters,
$parameters ?: array()
);
}
}
+15 -12
View File
@@ -23,6 +23,7 @@ use Predis\Iterator\MultiBulkResponseSimple;
* - scheme: it can be either 'tcp' or 'unix'.
* - host: hostname or IP address of the server.
* - port: TCP port of the server.
* - path: path of a UNIX domain socket when scheme is 'unix'.
* - timeout: timeout to perform the connection.
* - read_write_timeout: timeout of read / write operations.
* - async_connect: performs the connection asynchronously.
@@ -72,19 +73,21 @@ class StreamConnection extends AbstractConnection
/**
* Initializes a TCP stream resource.
*
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @return resource
*/
private function tcpStreamInitializer(ConnectionParametersInterface $parameters)
protected function tcpStreamInitializer(ConnectionParametersInterface $parameters)
{
$uri = "tcp://{$parameters->host}:{$parameters->port}/";
$uri = "tcp://{$parameters->host}:{$parameters->port}";
$flags = STREAM_CLIENT_CONNECT;
if (isset($parameters->async_connect) && $parameters->async_connect) {
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
}
if (isset($parameters->persistent) && $parameters->persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
$uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
}
$resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
@@ -101,7 +104,7 @@ class StreamConnection extends AbstractConnection
stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
}
if (isset($parameters->tcp_nodelay) && version_compare(PHP_VERSION, '5.4.0') >= 0) {
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
$socket = socket_import_stream($resource);
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
}
@@ -112,7 +115,7 @@ class StreamConnection extends AbstractConnection
/**
* Initializes a UNIX stream resource.
*
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @return resource
*/
private function unixStreamInitializer(ConnectionParametersInterface $parameters)
@@ -180,7 +183,7 @@ class StreamConnection extends AbstractConnection
$socket = $this->getResource();
while (($length = strlen($buffer)) > 0) {
$written = fwrite($socket, $buffer);
$written = @fwrite($socket, $buffer);
if ($length === $written) {
return;
@@ -209,7 +212,7 @@ class StreamConnection extends AbstractConnection
$payload = substr($chunk, 1, -2);
switch ($prefix) {
case '+': // inline
case '+':
switch ($payload) {
case 'OK':
return true;
@@ -221,7 +224,7 @@ class StreamConnection extends AbstractConnection
return $payload;
}
case '$': // bulk
case '$':
$size = (int) $payload;
if ($size === -1) {
return null;
@@ -243,7 +246,7 @@ class StreamConnection extends AbstractConnection
return substr($bulkData, 0, -2);
case '*': // multi bulk
case '*':
$count = (int) $payload;
if ($count === -1) {
@@ -261,10 +264,10 @@ class StreamConnection extends AbstractConnection
return $multibulk;
case ':': // integer
case ':':
return (int) $payload;
case '-': // error
case '-':
return new ResponseError($payload);
default:
@@ -285,7 +288,7 @@ class StreamConnection extends AbstractConnection
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandId}\r\n";
for ($i = 0; $i < $reqlen - 1; $i++) {
for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
$argument = $arguments[$i];
$arglen = strlen($argument);
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
+5 -6
View File
@@ -14,7 +14,6 @@ namespace Predis\Connection;
use Predis\NotSupportedException;
use Predis\ResponseError;
use Predis\Command\CommandInterface;
use Predis\Connection\ConnectionException;
use Predis\Protocol\ProtocolException;
/**
@@ -102,7 +101,7 @@ class WebdisConnection implements SingleConnectionInterface
/**
* Initializes cURL.
*
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @return resource
*/
private function initializeCurl(ConnectionParametersInterface $parameters)
@@ -128,7 +127,7 @@ class WebdisConnection implements SingleConnectionInterface
/**
* Initializes phpiredis' protocol reader.
*
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @return resource
*/
private function initializeReader(ConnectionParametersInterface $parameters)
@@ -168,8 +167,8 @@ class WebdisConnection implements SingleConnectionInterface
/**
* Feeds phpredis' reader resource with the data read from the network.
*
* @param resource $resource Reader resource.
* @param string $buffer Buffer with the reply read from the network.
* @param resource $resource Reader resource.
* @param string $buffer Buffer with the reply read from the network.
* @return int
*/
protected function feedReader($resource, $buffer)
@@ -206,7 +205,7 @@ class WebdisConnection implements SingleConnectionInterface
/**
* Checks if the specified command is supported by this connection class.
*
* @param CommandInterface $command The instance of a Redis command.
* @param CommandInterface $command The instance of a Redis command.
* @return string
*/
protected function getCommandId(CommandInterface $command)
+1 -1
View File
@@ -22,7 +22,7 @@ interface ExecutableContextInterface
/**
* Starts the execution of the context.
*
* @param mixed $callable Optional callback for execution.
* @param mixed $callable Optional callback for execution.
* @return array
*/
public function execute($callable = null);
+2 -2
View File
@@ -43,7 +43,7 @@ class Helpers
* Normalizes the arguments array passed to a Redis command.
*
* @deprecated Deprecated since v0.8.3 - moved in Predis\Command\AbstractCommand::normalizeArguments()
* @param array $arguments Arguments for a command.
* @param array $arguments Arguments for a command.
* @return array
*/
public static function filterArrayArguments(Array $arguments)
@@ -59,7 +59,7 @@ class Helpers
* Normalizes the arguments array passed to a variadic Redis command.
*
* @deprecated Deprecated since v0.8.3 - moved in Predis\Command\AbstractCommand::normalizeVariadic()
* @param array $arguments Arguments for a command.
* @param array $arguments Arguments for a command.
* @return array
*/
public static function filterVariadicValues(Array $arguments)
+1 -1
View File
@@ -96,5 +96,5 @@ abstract class MultiBulkResponse implements \Iterator, \Countable, ResponseObjec
/**
* {@inheritdoc}
*/
protected abstract function getValue();
abstract protected function getValue();
}
@@ -24,7 +24,7 @@ class MultiBulkResponseSimple extends MultiBulkResponse
/**
* @param SingleConnectionInterface $connection Connection to Redis.
* @param int $size Number of elements of the multibulk reply.
* @param int $size Number of elements of the multibulk reply.
*/
public function __construct(SingleConnectionInterface $connection, $size)
{
@@ -49,8 +49,8 @@ class MultiBulkResponseSimple extends MultiBulkResponse
* read from the connection by consuming the rest of the multibulk reply,
* or simply by dropping the connection.
*
* @param Boolean $drop True to synchronize the client by dropping the connection.
* False to synchronize the client by consuming the multibulk reply.
* @param bool $drop True to synchronize the client by dropping the connection.
* False to synchronize the client by consuming the multibulk reply.
*/
public function sync($drop = false)
{
+1 -1
View File
@@ -117,7 +117,7 @@ class MonitorContext implements \Iterator
/**
* Checks if the the context is still in a valid state to continue.
*
* @return Boolean
* @return bool
*/
public function valid()
{
+4 -3
View File
@@ -25,7 +25,7 @@ class ClientCluster extends AbstractOption
/**
* Checks if the specified value is a valid instance of ClusterConnectionInterface.
*
* @param ClusterConnectionInterface $cluster Instance of a connection cluster.
* @param ClusterConnectionInterface $cluster Instance of a connection cluster.
* @return ClusterConnectionInterface
*/
protected function checkInstance($cluster)
@@ -54,8 +54,8 @@ class ClientCluster extends AbstractOption
/**
* Returns an initializer for the specified FQN or type.
*
* @param string $fqnOrType Type of cluster or FQN of a class implementing ClusterConnectionInterface.
* @param ClientOptionsInterface $options Instance of the client options.
* @param string $fqnOrType Type of cluster or FQN of a class implementing ClusterConnectionInterface.
* @param ClientOptionsInterface $options Instance of the client options.
* @return \Closure
*/
protected function getInitializer(ClientOptionsInterface $options, $fqnOrType)
@@ -79,6 +79,7 @@ class ClientCluster extends AbstractOption
if (is_string($fqnOrType) && !class_exists($fqnOrType)) {
throw new \InvalidArgumentException("Class $fqnOrType does not exist");
}
return function () use ($fqnOrType) {
return new $fqnOrType();
};
@@ -50,7 +50,7 @@ class ClientConnectionFactory extends AbstractOption
return $factory;
}
if (@class_exists($value)) {
if (is_string($value) && @class_exists($value)) {
$factory = new $value();
if (!$factory instanceof ConnectionFactoryInterface) {
+5 -5
View File
@@ -51,7 +51,7 @@ class ClientOptions implements ClientOptionsInterface
/**
* Initializes client options handlers.
*
* @param array $options List of client options values.
* @param array $options List of client options values.
* @return array
*/
protected function initialize(Array $options)
@@ -75,8 +75,8 @@ class ClientOptions implements ClientOptionsInterface
/**
* Checks if the specified option is set.
*
* @param string $option Name of the option.
* @return Boolean
* @param string $option Name of the option.
* @return bool
*/
public function __isset($option)
{
@@ -86,7 +86,7 @@ class ClientOptions implements ClientOptionsInterface
/**
* Returns the value of the specified option.
*
* @param string $option Name of the option.
* @param string $option Name of the option.
* @return mixed
*/
public function __get($option)
@@ -107,7 +107,7 @@ class ClientOptions implements ClientOptionsInterface
/**
* Returns the default value for the specified option.
*
* @param string|OptionInterface $option Name or instance of the option.
* @param string|OptionInterface $option Name or instance of the option.
* @return mixed
*/
public function getDefault($option)
+1 -1
View File
@@ -24,7 +24,7 @@ class ClientReplication extends AbstractOption
/**
* Checks if the specified value is a valid instance of ReplicationConnectionInterface.
*
* @param ReplicationConnectionInterface $connection Instance of a replication connection.
* @param ReplicationConnectionInterface $connection Instance of a replication connection.
* @return ReplicationConnectionInterface
*/
protected function checkInstance($connection)
+2 -2
View File
@@ -33,8 +33,8 @@ class CustomOption implements OptionInterface
/**
* Checks if the specified value in the options array is a callable object.
*
* @param array $options Array of options
* @param string $key Target option.
* @param array $options Array of options
* @param string $key Target option.
*/
private function ensureCallable($options, $key)
{
+7 -5
View File
@@ -21,7 +21,8 @@ interface OptionInterface
/**
* Filters (and optionally converts) the passed value.
*
* @param mixed $value Input value.
* @param ClientOptionsInterface $options Options container.
* @param mixed $value Input value.
* @return mixed
*/
public function filter(ClientOptionsInterface $options, $value);
@@ -29,16 +30,17 @@ interface OptionInterface
/**
* Returns a default value for the option.
*
* @param mixed $value Input value.
* @param ClientOptionsInterface $options Options container.
* @return mixed
*/
public function getDefault(ClientOptionsInterface $options);
/**
* Filters a value and, if no value is specified, returns
* the default one defined by the option.
* Filters a value and, if no value is specified, returns the default one
* defined by the option.
*
* @param mixed $value Input value.
* @param ClientOptionsInterface $options Options container.
* @param mixed $value Input value.
* @return mixed
*/
public function __invoke(ClientOptionsInterface $options, $value);
+4 -4
View File
@@ -100,8 +100,8 @@ class MultiExecExecutor implements PipelineExecutorInterface
/**
* Consumes an iterator response returned by EXEC.
*
* @param SplQueue $commands Pipelined commands
* @param Iterator $responses Responses returned by EXEC.
* @param SplQueue $commands Pipelined commands
* @param Iterator $responses Responses returned by EXEC.
* @return array
*/
protected function consumeIteratorResponse(SplQueue $commands, Iterator $responses)
@@ -129,8 +129,8 @@ class MultiExecExecutor implements PipelineExecutorInterface
/**
* Consumes an array response returned by EXEC.
*
* @param SplQueue $commands Pipelined commands
* @param Array $responses Responses returned by EXEC.
* @param SplQueue $commands Pipelined commands
* @param Array $responses Responses returned by EXEC.
* @return array
*/
protected function consumeArrayResponse(SplQueue $commands, Array &$responses)
+13 -10
View File
@@ -34,7 +34,7 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
private $running = false;
/**
* @param ClientInterface $client Client instance used by the context.
* @param ClientInterface $client Client instance used by the context.
* @param PipelineExecutorInterface $executor Pipeline executor instace.
*/
public function __construct(ClientInterface $client, PipelineExecutorInterface $executor = null)
@@ -48,7 +48,7 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
* Returns a pipeline executor depending on the kind of the underlying
* connection and the passed options.
*
* @param ClientInterface $client Client instance used by the context.
* @param ClientInterface $client Client instance used by the context.
* @return PipelineExecutorInterface
*/
protected function createExecutor(ClientInterface $client)
@@ -65,9 +65,9 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
/**
* Queues a command into the pipeline buffer.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @return PipelineContext
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @return $this
*/
public function __call($method, $arguments)
{
@@ -90,17 +90,20 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
/**
* Queues a command instance into the pipeline buffer.
*
* @param CommandInterface $command Command to queue in the buffer.
* @param CommandInterface $command Command to queue in the buffer.
* @return $this
*/
public function executeCommand(CommandInterface $command)
{
$this->recordCommand($command);
return $this;
}
/**
* Flushes the buffer that holds the queued commands.
*
* @param Boolean $send Specifies if the commands in the buffer should be sent to Redis.
* @param bool $send Specifies if the commands in the buffer should be sent to Redis.
* @return PipelineContext
*/
public function flushPipeline($send = true)
@@ -119,8 +122,8 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
/**
* Marks the running status of the pipeline.
*
* @param Boolean $bool True if the pipeline is running.
* False if the pipeline is not running.
* @param bool $bool True if the pipeline is running.
* False if the pipeline is not running.
*/
private function setRunning($bool)
{
@@ -134,7 +137,7 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
/**
* Handles the actual execution of the whole pipeline.
*
* @param mixed $callable Optional callback for execution.
* @param mixed $callable Optional callback for execution.
* @return array
*/
public function execute($callable = null)
@@ -25,8 +25,8 @@ interface PipelineExecutorInterface
/**
* Writes a list of commands to the network and reads back their replies.
*
* @param ConnectionInterface $connection Connection to Redis.
* @param SplQueue $commands Commands queued for execution.
* @param ConnectionInterface $connection Connection to Redis.
* @param SplQueue $commands Commands queued for execution.
* @return array
*/
public function execute(ConnectionInterface $connection, SplQueue $commands);
+6 -5
View File
@@ -56,9 +56,9 @@ class StandardExecutor implements PipelineExecutorInterface
/**
* Handles a response object.
*
* @param ConnectionInterface $connection
* @param CommandInterface $command
* @param ResponseObjectInterface $response
* @param ConnectionInterface $connection
* @param CommandInterface $command
* @param ResponseObjectInterface $response
* @return mixed
*/
protected function onResponseObject(ConnectionInterface $connection, CommandInterface $command, ResponseObjectInterface $response)
@@ -77,8 +77,9 @@ class StandardExecutor implements PipelineExecutorInterface
/**
* Handles -ERR responses returned by Redis.
*
* @param ConnectionInterface $connection The connection that returned the error.
* @param ResponseErrorInterface $response The error response instance.
* @param ConnectionInterface $connection The connection that returned the error.
* @param ResponseErrorInterface $response The error response instance.
* @return ResponseErrorInterface
*/
protected function onResponseError(ConnectionInterface $connection, ResponseErrorInterface $response)
{
+7 -6
View File
@@ -41,7 +41,7 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
*
* @return array
*/
protected abstract function getSupportedCommands();
abstract protected function getSupportedCommands();
/**
* Returns the default server profile.
@@ -78,7 +78,8 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
'2.4' => 'Predis\Profile\ServerVersion24',
'2.6' => 'Predis\Profile\ServerVersion26',
'2.8' => 'Predis\Profile\ServerVersion28',
'default' => 'Predis\Profile\ServerVersion26',
'3.0' => 'Predis\Profile\ServerVersion30',
'default' => 'Predis\Profile\ServerVersion28',
'dev' => 'Predis\Profile\ServerVersionNext',
);
}
@@ -86,7 +87,7 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
/**
* Registers a new server profile.
*
* @param string $alias Profile version or alias.
* @param string $alias Profile version or alias.
* @param string $profileClass FQN of a class implementing Predis\Profile\ServerProfileInterface.
*/
public static function define($alias, $profileClass)
@@ -107,7 +108,7 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
/**
* Returns the specified server profile.
*
* @param string $version Profile version or alias.
* @param string $version Profile version or alias.
* @return ServerProfileInterface
*/
public static function get($version)
@@ -151,7 +152,7 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
* Returns the FQN of the class that represent the specified command ID
* registered in the current server profile.
*
* @param string $command Command ID.
* @param string $command Command ID.
* @return string
*/
public function getCommandClass($command)
@@ -186,7 +187,7 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
/**
* Defines a new commands in the server profile.
*
* @param string $alias Command ID.
* @param string $alias Command ID.
* @param string $command FQN of a class implementing Predis\Command\CommandInterface.
*/
public function defineCommand($alias, $command)
@@ -32,15 +32,15 @@ interface ServerProfileInterface
/**
* Checks if the profile supports the specified command.
*
* @param string $command Command ID.
* @return Boolean
* @param string $command Command ID.
* @return bool
*/
public function supportsCommand($command);
/**
* Checks if the profile supports the specified list of commands.
*
* @param array $commands List of command IDs.
* @param array $commands List of command IDs.
* @return string
*/
public function supportsCommands(Array $commands);
@@ -48,8 +48,8 @@ interface ServerProfileInterface
/**
* Creates a new command instance.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @return CommandInterface
*/
public function createCommand($method, $arguments = array());
-1
View File
@@ -121,7 +121,6 @@ class ServerVersion20 extends ServerProfile
'shutdown' => 'Predis\Command\ServerShutdown',
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
-2
View File
@@ -121,7 +121,6 @@ class ServerVersion22 extends ServerProfile
'shutdown' => 'Predis\Command\ServerShutdown',
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
@@ -170,7 +169,6 @@ class ServerVersion22 extends ServerProfile
/* remote server control commands */
'config' => 'Predis\Command\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
-3
View File
@@ -121,7 +121,6 @@ class ServerVersion24 extends ServerProfile
'shutdown' => 'Predis\Command\ServerShutdown',
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
@@ -170,7 +169,6 @@ class ServerVersion24 extends ServerProfile
/* remote server control commands */
'config' => 'Predis\Command\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
@@ -200,7 +198,6 @@ class ServerVersion24 extends ServerProfile
'object' => 'Predis\Command\ServerObject',
'slowlog' => 'Predis\Command\ServerSlowlog',
/* ---------------- Redis 2.4 ---------------- */
/* remote server control commands */
+1 -6
View File
@@ -111,7 +111,7 @@ class ServerVersion26 extends ServerProfile
'quit' => 'Predis\Command\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Command\ServerInfo',
'info' => 'Predis\Command\ServerInfoV26x',
'slaveof' => 'Predis\Command\ServerSlaveOf',
'monitor' => 'Predis\Command\ServerMonitor',
'dbsize' => 'Predis\Command\ServerDatabaseSize',
@@ -123,7 +123,6 @@ class ServerVersion26 extends ServerProfile
'shutdown' => 'Predis\Command\ServerShutdown',
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
@@ -172,7 +171,6 @@ class ServerVersion26 extends ServerProfile
/* remote server control commands */
'config' => 'Predis\Command\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
@@ -202,13 +200,11 @@ class ServerVersion26 extends ServerProfile
'object' => 'Predis\Command\ServerObject',
'slowlog' => 'Predis\Command\ServerSlowlog',
/* ---------------- Redis 2.4 ---------------- */
/* remote server control commands */
'client' => 'Predis\Command\ServerClient',
/* ---------------- Redis 2.6 ---------------- */
/* commands operating on the key space */
@@ -231,7 +227,6 @@ class ServerVersion26 extends ServerProfile
'script' => 'Predis\Command\ServerScript',
/* remote server control commands */
'info' => 'Predis\Command\ServerInfoV26x',
'time' => 'Predis\Command\ServerTime',
);
}
+12 -7
View File
@@ -111,7 +111,7 @@ class ServerVersion28 extends ServerProfile
'quit' => 'Predis\Command\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Command\ServerInfo',
'info' => 'Predis\Command\ServerInfoV26x',
'slaveof' => 'Predis\Command\ServerSlaveOf',
'monitor' => 'Predis\Command\ServerMonitor',
'dbsize' => 'Predis\Command\ServerDatabaseSize',
@@ -123,7 +123,6 @@ class ServerVersion28 extends ServerProfile
'shutdown' => 'Predis\Command\ServerShutdown',
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
@@ -172,7 +171,6 @@ class ServerVersion28 extends ServerProfile
/* remote server control commands */
'config' => 'Predis\Command\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
@@ -202,13 +200,11 @@ class ServerVersion28 extends ServerProfile
'object' => 'Predis\Command\ServerObject',
'slowlog' => 'Predis\Command\ServerSlowlog',
/* ---------------- Redis 2.4 ---------------- */
/* remote server control commands */
'client' => 'Predis\Command\ServerClient',
/* ---------------- Redis 2.6 ---------------- */
/* commands operating on the key space */
@@ -231,10 +227,8 @@ class ServerVersion28 extends ServerProfile
'script' => 'Predis\Command\ServerScript',
/* remote server control commands */
'info' => 'Predis\Command\ServerInfoV26x',
'time' => 'Predis\Command\ServerTime',
/* ---------------- Redis 2.8 ---------------- */
/* commands operating on the key space */
@@ -245,9 +239,20 @@ class ServerVersion28 extends ServerProfile
/* commands operating on sorted sets */
'zscan' => 'Predis\Command\ZSetScan',
'zlexcount' => 'Predis\Command\ZSetLexCount',
'zrangebylex' => 'Predis\Command\ZSetRangeByLex',
'zremrangebylex' => 'Predis\Command\ZSetRemoveRangeByLex',
/* commands operating on hashes */
'hscan' => 'Predis\Command\HashScan',
/* commands operating on HyperLogLog */
'pfadd' => 'Predis\Command\HyperLogLogAdd',
'pfcount' => 'Predis\Command\HyperLogLogCount',
'pfmerge' => 'Predis\Command\HyperLogLogMerge',
/* remote server control commands */
'command' => 'Predis\Command\ServerCommand',
);
}
}
+261
View File
@@ -0,0 +1,261 @@
<?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\Profile;
/**
* Server profile for Redis v3.0.x.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersion30 extends ServerProfile
{
/**
* {@inheritdoc}
*/
public function getVersion()
{
return '3.0';
}
/**
* {@inheritdoc}
*/
public function getSupportedCommands()
{
return array(
/* ---------------- Redis 1.2 ---------------- */
/* commands operating on the key space */
'exists' => 'Predis\Command\KeyExists',
'del' => 'Predis\Command\KeyDelete',
'type' => 'Predis\Command\KeyType',
'keys' => 'Predis\Command\KeyKeys',
'randomkey' => 'Predis\Command\KeyRandom',
'rename' => 'Predis\Command\KeyRename',
'renamenx' => 'Predis\Command\KeyRenamePreserve',
'expire' => 'Predis\Command\KeyExpire',
'expireat' => 'Predis\Command\KeyExpireAt',
'ttl' => 'Predis\Command\KeyTimeToLive',
'move' => 'Predis\Command\KeyMove',
'sort' => 'Predis\Command\KeySort',
'dump' => 'Predis\Command\KeyDump',
'restore' => 'Predis\Command\KeyRestore',
/* commands operating on string values */
'set' => 'Predis\Command\StringSet',
'setnx' => 'Predis\Command\StringSetPreserve',
'mset' => 'Predis\Command\StringSetMultiple',
'msetnx' => 'Predis\Command\StringSetMultiplePreserve',
'get' => 'Predis\Command\StringGet',
'mget' => 'Predis\Command\StringGetMultiple',
'getset' => 'Predis\Command\StringGetSet',
'incr' => 'Predis\Command\StringIncrement',
'incrby' => 'Predis\Command\StringIncrementBy',
'decr' => 'Predis\Command\StringDecrement',
'decrby' => 'Predis\Command\StringDecrementBy',
/* commands operating on lists */
'rpush' => 'Predis\Command\ListPushTail',
'lpush' => 'Predis\Command\ListPushHead',
'llen' => 'Predis\Command\ListLength',
'lrange' => 'Predis\Command\ListRange',
'ltrim' => 'Predis\Command\ListTrim',
'lindex' => 'Predis\Command\ListIndex',
'lset' => 'Predis\Command\ListSet',
'lrem' => 'Predis\Command\ListRemove',
'lpop' => 'Predis\Command\ListPopFirst',
'rpop' => 'Predis\Command\ListPopLast',
'rpoplpush' => 'Predis\Command\ListPopLastPushHead',
/* commands operating on sets */
'sadd' => 'Predis\Command\SetAdd',
'srem' => 'Predis\Command\SetRemove',
'spop' => 'Predis\Command\SetPop',
'smove' => 'Predis\Command\SetMove',
'scard' => 'Predis\Command\SetCardinality',
'sismember' => 'Predis\Command\SetIsMember',
'sinter' => 'Predis\Command\SetIntersection',
'sinterstore' => 'Predis\Command\SetIntersectionStore',
'sunion' => 'Predis\Command\SetUnion',
'sunionstore' => 'Predis\Command\SetUnionStore',
'sdiff' => 'Predis\Command\SetDifference',
'sdiffstore' => 'Predis\Command\SetDifferenceStore',
'smembers' => 'Predis\Command\SetMembers',
'srandmember' => 'Predis\Command\SetRandomMember',
/* commands operating on sorted sets */
'zadd' => 'Predis\Command\ZSetAdd',
'zincrby' => 'Predis\Command\ZSetIncrementBy',
'zrem' => 'Predis\Command\ZSetRemove',
'zrange' => 'Predis\Command\ZSetRange',
'zrevrange' => 'Predis\Command\ZSetReverseRange',
'zrangebyscore' => 'Predis\Command\ZSetRangeByScore',
'zcard' => 'Predis\Command\ZSetCardinality',
'zscore' => 'Predis\Command\ZSetScore',
'zremrangebyscore' => 'Predis\Command\ZSetRemoveRangeByScore',
/* connection related commands */
'ping' => 'Predis\Command\ConnectionPing',
'auth' => 'Predis\Command\ConnectionAuth',
'select' => 'Predis\Command\ConnectionSelect',
'echo' => 'Predis\Command\ConnectionEcho',
'quit' => 'Predis\Command\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Command\ServerInfoV26x',
'slaveof' => 'Predis\Command\ServerSlaveOf',
'monitor' => 'Predis\Command\ServerMonitor',
'dbsize' => 'Predis\Command\ServerDatabaseSize',
'flushdb' => 'Predis\Command\ServerFlushDatabase',
'flushall' => 'Predis\Command\ServerFlushAll',
'save' => 'Predis\Command\ServerSave',
'bgsave' => 'Predis\Command\ServerBackgroundSave',
'lastsave' => 'Predis\Command\ServerLastSave',
'shutdown' => 'Predis\Command\ServerShutdown',
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
'setex' => 'Predis\Command\StringSetExpire',
'append' => 'Predis\Command\StringAppend',
'substr' => 'Predis\Command\StringSubstr',
/* commands operating on lists */
'blpop' => 'Predis\Command\ListPopFirstBlocking',
'brpop' => 'Predis\Command\ListPopLastBlocking',
/* commands operating on sorted sets */
'zunionstore' => 'Predis\Command\ZSetUnionStore',
'zinterstore' => 'Predis\Command\ZSetIntersectionStore',
'zcount' => 'Predis\Command\ZSetCount',
'zrank' => 'Predis\Command\ZSetRank',
'zrevrank' => 'Predis\Command\ZSetReverseRank',
'zremrangebyrank' => 'Predis\Command\ZSetRemoveRangeByRank',
/* commands operating on hashes */
'hset' => 'Predis\Command\HashSet',
'hsetnx' => 'Predis\Command\HashSetPreserve',
'hmset' => 'Predis\Command\HashSetMultiple',
'hincrby' => 'Predis\Command\HashIncrementBy',
'hget' => 'Predis\Command\HashGet',
'hmget' => 'Predis\Command\HashGetMultiple',
'hdel' => 'Predis\Command\HashDelete',
'hexists' => 'Predis\Command\HashExists',
'hlen' => 'Predis\Command\HashLength',
'hkeys' => 'Predis\Command\HashKeys',
'hvals' => 'Predis\Command\HashValues',
'hgetall' => 'Predis\Command\HashGetAll',
/* transactions */
'multi' => 'Predis\Command\TransactionMulti',
'exec' => 'Predis\Command\TransactionExec',
'discard' => 'Predis\Command\TransactionDiscard',
/* publish - subscribe */
'subscribe' => 'Predis\Command\PubSubSubscribe',
'unsubscribe' => 'Predis\Command\PubSubUnsubscribe',
'psubscribe' => 'Predis\Command\PubSubSubscribeByPattern',
'punsubscribe' => 'Predis\Command\PubSubUnsubscribeByPattern',
'publish' => 'Predis\Command\PubSubPublish',
/* remote server control commands */
'config' => 'Predis\Command\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
'persist' => 'Predis\Command\KeyPersist',
/* commands operating on string values */
'strlen' => 'Predis\Command\StringStrlen',
'setrange' => 'Predis\Command\StringSetRange',
'getrange' => 'Predis\Command\StringGetRange',
'setbit' => 'Predis\Command\StringSetBit',
'getbit' => 'Predis\Command\StringGetBit',
/* commands operating on lists */
'rpushx' => 'Predis\Command\ListPushTailX',
'lpushx' => 'Predis\Command\ListPushHeadX',
'linsert' => 'Predis\Command\ListInsert',
'brpoplpush' => 'Predis\Command\ListPopLastPushHeadBlocking',
/* commands operating on sorted sets */
'zrevrangebyscore' => 'Predis\Command\ZSetReverseRangeByScore',
/* transactions */
'watch' => 'Predis\Command\TransactionWatch',
'unwatch' => 'Predis\Command\TransactionUnwatch',
/* remote server control commands */
'object' => 'Predis\Command\ServerObject',
'slowlog' => 'Predis\Command\ServerSlowlog',
/* ---------------- Redis 2.4 ---------------- */
/* remote server control commands */
'client' => 'Predis\Command\ServerClient',
/* ---------------- Redis 2.6 ---------------- */
/* commands operating on the key space */
'pttl' => 'Predis\Command\KeyPreciseTimeToLive',
'pexpire' => 'Predis\Command\KeyPreciseExpire',
'pexpireat' => 'Predis\Command\KeyPreciseExpireAt',
/* commands operating on string values */
'psetex' => 'Predis\Command\StringPreciseSetExpire',
'incrbyfloat' => 'Predis\Command\StringIncrementByFloat',
'bitop' => 'Predis\Command\StringBitOp',
'bitcount' => 'Predis\Command\StringBitCount',
/* commands operating on hashes */
'hincrbyfloat' => 'Predis\Command\HashIncrementByFloat',
/* scripting */
'eval' => 'Predis\Command\ServerEval',
'evalsha' => 'Predis\Command\ServerEvalSHA',
'script' => 'Predis\Command\ServerScript',
/* remote server control commands */
'time' => 'Predis\Command\ServerTime',
/* ---------------- Redis 2.8 ---------------- */
/* commands operating on the key space */
'scan' => 'Predis\Command\KeyScan',
/* commands operating on sets */
'sscan' => 'Predis\Command\SetScan',
/* commands operating on sorted sets */
'zscan' => 'Predis\Command\ZSetScan',
'zlexcount' => 'Predis\Command\ZSetLexCount',
'zrangebylex' => 'Predis\Command\ZSetRangeByLex',
'zremrangebylex' => 'Predis\Command\ZSetRemoveRangeByLex',
/* commands operating on hashes */
'hscan' => 'Predis\Command\HashScan',
/* commands operating on HyperLogLog */
'pfadd' => 'Predis\Command\HyperLogLogAdd',
'pfcount' => 'Predis\Command\HyperLogLogCount',
'pfmerge' => 'Predis\Command\HyperLogLogMerge',
/* remote server control commands */
'command' => 'Predis\Command\ServerCommand',
/* ---------------- Redis 3.0 ---------------- */
);
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace Predis\Profile;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersionNext extends ServerVersion28
class ServerVersionNext extends ServerVersion30
{
/**
* {@inheritdoc}
@@ -23,7 +23,7 @@ interface CommandSerializerInterface
/**
* Serializes a Redis command.
*
* @param CommandInterface $command Redis command.
* @param CommandInterface $command Redis command.
* @return string
*/
public function serialize(CommandInterface $command);
+2 -2
View File
@@ -26,7 +26,7 @@ interface ProtocolInterface extends ResponseReaderInterface
* Writes a Redis command on the specified connection.
*
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param CommandInterface $command Redis command.
* @param CommandInterface $command Redis command.
*/
public function write(ComposableConnectionInterface $connection, CommandInterface $command);
@@ -34,7 +34,7 @@ interface ProtocolInterface extends ResponseReaderInterface
* Sets the options for the protocol processor.
*
* @param string $option Name of the option.
* @param mixed $value Value of the option.
* @param mixed $value Value of the option.
*/
public function setOption($option, $value);
}
@@ -24,9 +24,9 @@ interface ResponseHandlerInterface
* Parses a type of reply returned by Redis and reads more data from the
* connection if needed.
*
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $payload Initial payload of the reply.
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $payload Initial payload of the reply.
* @return mixed
*/
function handle(ComposableConnectionInterface $connection, $payload);
public function handle(ComposableConnectionInterface $connection, $payload);
}
@@ -24,7 +24,7 @@ interface ResponseReaderInterface
/**
* Reads replies from a connection to Redis and deserializes them.
*
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param ComposableConnectionInterface $connection Connection to Redis.
* @return mixed
*/
public function read(ComposableConnectionInterface $connection);
@@ -28,8 +28,8 @@ class ResponseBulkHandler implements ResponseHandlerInterface
/**
* Handles a bulk reply returned by Redis.
*
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $lengthString Bytes size of the bulk reply.
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $lengthString Bytes size of the bulk reply.
* @return string
*/
public function handle(ComposableConnectionInterface $connection, $lengthString)
@@ -28,8 +28,8 @@ class ResponseIntegerHandler implements ResponseHandlerInterface
/**
* Handles an integer reply returned by Redis.
*
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $number String representation of an integer.
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $number String representation of an integer.
* @return int
*/
public function handle(ComposableConnectionInterface $connection, $number)
@@ -28,8 +28,8 @@ class ResponseMultiBulkHandler implements ResponseHandlerInterface
/**
* Handles a multi-bulk reply returned by Redis.
*
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $lengthString Number of items in the multi-bulk reply.
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $lengthString Number of items in the multi-bulk reply.
* @return array
*/
public function handle(ComposableConnectionInterface $connection, $lengthString)
@@ -29,8 +29,8 @@ class ResponseMultiBulkStreamHandler implements ResponseHandlerInterface
/**
* Handles a multi-bulk reply returned by Redis in a streamable fashion.
*
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $lengthString Number of items in the multi-bulk reply.
* @param ComposableConnectionInterface $connection Connection to Redis.
* @param string $lengthString Number of items in the multi-bulk reply.
* @return MultiBulkResponseSimple
*/
public function handle(ComposableConnectionInterface $connection, $lengthString)
@@ -36,7 +36,7 @@ class TextCommandSerializer implements CommandSerializerInterface
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandId}\r\n";
for ($i = 0; $i < $reqlen - 1; $i++) {
for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
$argument = $arguments[$i];
$arglen = strlen($argument);
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
+6 -5
View File
@@ -72,7 +72,7 @@ class TextProtocol implements ProtocolInterface
$payload = substr($chunk, 1);
switch ($prefix) {
case '+': // inline
case '+':
switch ($payload) {
case 'OK':
return true;
@@ -84,14 +84,15 @@ class TextProtocol implements ProtocolInterface
return $payload;
}
case '$': // bulk
case '$':
$size = (int) $payload;
if ($size === -1) {
return null;
}
return substr($connection->readBytes($size + 2), 0, -2);
case '*': // multi bulk
case '*':
$count = (int) $payload;
if ($count === -1) {
@@ -109,10 +110,10 @@ class TextProtocol implements ProtocolInterface
return $multibulk;
case ':': // integer
case ':':
return (int) $payload;
case '-': // error
case '-':
return new ResponseError($payload);
default:
@@ -55,7 +55,7 @@ class TextResponseReader implements ResponseReaderInterface
* Sets a response handler for a certain prefix that identifies a type of
* reply that can be returned by Redis.
*
* @param string $prefix Identifier for a type of reply.
* @param string $prefix Identifier for a type of reply.
* @param ResponseHandlerInterface $handler Response handler for the reply.
*/
public function setHandler($prefix, ResponseHandlerInterface $handler)
@@ -67,7 +67,7 @@ class TextResponseReader implements ResponseReaderInterface
* Returns the response handler associated to a certain type of reply that
* can be returned by Redis.
*
* @param string $prefix Identifier for a type of reply.
* @param string $prefix Identifier for a type of reply.
* @return ResponseHandlerInterface
*/
public function getHandler($prefix)
@@ -104,7 +104,7 @@ class TextResponseReader implements ResponseReaderInterface
* reply from a connection to Redis.
*
* @param ComposableConnectionInterface $connection Connection to Redis that generated the error.
* @param string $message Error message.
* @param string $message Error message.
*/
private function protocolError(ComposableConnectionInterface $connection, $message)
{

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