Compare commits

..

111 Commits

Author SHA1 Message Date
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
638 changed files with 20521 additions and 26963 deletions
-17
View File
@@ -1,17 +0,0 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
[*.php]
trim_trailing_whitespace = true
max_line_length = 120
[*.{md,yml}]
indent_size = 2
-10
View File
@@ -1,10 +0,0 @@
* text=auto
/tests export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.php_cs export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
/phpunit.xml.travisci export-ignore
-2
View File
@@ -1,7 +1,5 @@
*.tgz
*.phar
.php-version
.php_cs.cache
phpunit.xml
package.xml
composer.lock
-34
View File
@@ -1,34 +0,0 @@
<?php
$PREDIS_HEADER = <<<EOS
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.
EOS;
Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($PREDIS_HEADER);
return Symfony\CS\Config\Config::create()
->setUsingCache(true)
->level(Symfony\CS\FixerInterface::SYMFONY_LEVEL)
->fixers(array(
// Symfony
'-unalign_equals',
'-unalign_double_arrow',
// Contribs
'header_comment',
'ordered_use',
'phpdoc_order',
'long_array_syntax',
))
->finder(
Symfony\CS\Finder\DefaultFinder::create()
->in(__DIR__.'/bin')
->in(__DIR__.'/src')
->in(__DIR__.'/tests')
->in(__DIR__.'/examples')
);
+3 -3
View File
@@ -1,17 +1,15 @@
language: php
sudo: false
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
branches:
except:
- v0.5
- v0.6
- v0.6-PHP_5.2
- php5.2_backport
- documentation
services: redis-server
before_script:
@@ -20,4 +18,6 @@ before_script:
script:
- vendor/bin/phpunit -c phpunit.xml.travisci
matrix:
allow_failures:
- php: hhvm
fast_finish: true
+53
View File
@@ -0,0 +1,53 @@
# Namespaces, interfaces and classes renamed in Predis v0.8 #
____________________________________________
Some namespaces, interfaces and classes in Predis v0.8 have been renamed to follow a common rule inspired
by the naming conventions adopted by the Symfony2 project. This is a list of all the changes:
### Renamed namespaces ###
- `Predis\Network` => `Predis\Connection`
- `Predis\Profiles` => `Predis\Profile`
- `Predis\Iterators` => `Predis\Iterator`
- `Predis\Options` => `Predis\Option`
- `Predis\Commands` => `Predis\Command`
- `Predis\Commands\Processors` => `Predis\Command\Processor`
### Renamed interfaces ###
- `Predis\IReplyObject` => `Predis\ResponseObjectInterface`
- `Predis\IRedisServerError` => `Predis\ResponseErrorInterface`
- `Predis\Options\IOption` => `Predis\Option\OptionInterface`
- `Predis\Options\IClientOptions` => `Predis\Option\ClientOptionsInterface`
- `Predis\Profile\IServerProfile` => `Predis\Profile\ServerProfileInterface`
- `Predis\Pipeline\IPipelineExecutor` => `Predis\Pipeline\PipelineExecutorInterface`
- `Predis\Distribution\INodeKeyGenerator` => `Predis\Distribution\HashGeneratorInterface`
- `Predis\Distribution\IDistributionStrategy` => `Predis\Distribution\DistributionStrategyInterface`
- `Predis\Protocol\IProtocolProcessor` => `Predis\Protocol\ProtocolInterface`
- `Predis\Protocol\IResponseReader` => `Predis\Protocol\ResponseReaderInterface`
- `Predis\Protocol\IResponseHandler` => `Predis\Protocol\ResponseHandlerInterface`
- `Predis\Protocol\ICommandSerializer` => `Predis\Protocol\CommandSerializerInterface`
- `Predis\Protocol\IComposableProtocolProcessor` => `Predis\Protocol\ComposableProtocolInterface`
- `Predis\Network\IConnection` => `Predis\Connection\ConnectionInterface`
- `Predis\Network\IConnectionSingle` => `Predis\Connection\SingleConnectionInterface`
- `Predis\Network\IConnectionComposable` => `Predis\Connection\ComposableConnectionInterface`
- `Predis\Network\IConnectionCluster` => `Predis\Connection\ClusterConnectionInterface`
- `Predis\Network\IConnectionReplication` => `Predis\Connection\ReplicationConnectionInterface`
- `Predis\Commands\ICommand` => `Predis\Command\CommandInterface`
- `Predis\Commands\IPrefixable` => `Predis\Command\PrefixableCommandInterface`
- `Predis\Command\Processor\ICommandProcessor` => `Predis\Command\Processor\CommandProcessorInterface`
- `Predis\Command\Processor\ICommandProcessorChain` => `Predis\Command\Processor\CommandProcessorChainInterface`
- `Predis\Command\Processor\IProcessingSupport` => `Predis\Command\Processor\CommandProcessingInterface`
### Renamed classes ###
- `Predis\Commands\Command` => `Predis\Command\AbstractCommand`
- `Predis\Network\ConnectionBase` => `Predis\Connection\AbstractConnection`
### Classes or interfaces moved to different namespaces ###
- `Predis\MonitorContext` => `Predis\Monitor\MonitorContext`
- `Predis\ConnectionParameters` => `Predis\Connection\ConnectionParameters`
- `Predis\ConnectionParametersInterface` => `Predis\Connection\ConnectionParametersInterface`
- `Predis\ConnectionFactory` => `Predis\Connection\ConnectionFactory`
- `Predis\ConnectionFactoryInterface` => `Predis\Connection\ConnectionFactoryInterface`
+1 -312
View File
@@ -1,318 +1,7 @@
v1.1.0 (2016-06-02)
================================================================================
- The default server profile for the client now targets Redis 3.2.
- Responses to the following commands are not casted into booleans anymore, the
original integer value is returned: `SETNX`, `MSETNX`, `SMOVE`, `SISMEMBER`,
`HSET`, `HSETNX`, `HEXISTS`, `PFADD`, `EXISTS`, `MOVE`, `PERSIST`, `EXPIRE`,
`EXPIREAT`, `RENAMENX`. This change does not have a significant impact unless
when using strict comparisons (=== and !==) the returned value.
- Non-boolean string values passed to the `persistent` connection parameter can
be used to create different persistent connections. Note that this feature was
already present in Predis but required both `persistent` and `path` to be set
as illustrated by [#139](https://github.com/nrk/predis/pull/139). This change
is needed to prevent confusion with how `path` is used to select a database
when using the `redis` scheme.
- The client throws exceptions when Redis returns any kind of error response to
initialization commands (the ones being automatically sent when a connection
is established, such as `SELECT` and `AUTH` when database and password are set
in connection parameters) regardless of the value of the exception option.
- Using `unix:///path/to/socket` in URI strings to specify a UNIX domain socket
file is now deprecated in favor of the format `unix:/path/to/socket` (note the
lack of the double slash after the scheme) and will not be supported starting
with the next major release.
- Implemented full support for redis-sentinel.
- Implemented the ability to specify default connection parameters for aggregate
connections with the new `parameters` client option. These parameters augment
the usual user-supplied connection parameters (but do not take the precedence
over them) when creating new connections and they are mostly useful when the
client is using aggregate connections such as redis-cluster and redis-sentinel
as these backends can create new connections on the fly based on responses and
redirections from Redis.
- Redis servers protected by SSL-encrypted connections can be accessed by using
the `tls` or `rediss` scheme in connection parameters along with SSL-specific
options in the `ssl` parameter (see http://php.net/manual/context.ssl.php).
- `Predis\Client` implements `IteratorAggregate` making it possible to iterate
over traversable aggregate connections and get a new client instance for each
Redis node.
- Iterating over an instance of `Predis\Connection\Aggregate\RedisCluster` will
return all the connections mapped in the slots map instead of just the ones in
the pool. This change makes it possible, when the slots map is retrieved from
Redis, to iterate over all of the master nodes in the cluster. When the use of
`CLUSTER SLOTS` is disabled via the `useClusterSlots()` method, the iteration
returns only the connections with slots ranges associated in their parameters
or the ones initialized by `-MOVED` responses in order to make the behaviour
of the iteration consistent between the two modes of operation.
- Various improvements to `Predis\Connection\Aggregate\MasterSlaveReplication`
(the "basic" replication backend, not the new one based on redis-sentinel):
- When the client is not able to send a read-only command to a slave because
the current connection fails or the slave is resyncing (`-LOADING` response
returned by Redis), the backend discards the failed connection and performs
a new attempt on the next slave. When no other slave is available the master
server is used for read-only commands as last resort.
- It is possible to discover the current replication configuration on the fly
by invoking the `discover()` method which internally relies on the output of
the command `INFO REPLICATION` executed against the master server or one of
the slaves. The backend can also be configured to do this automatically when
it fails to reach one of the servers.
- Implemented the `switchToMaster()` and `switchToSlave()` methods to make it
easier to force a switch to the master server or a random slave when needed.
v1.0.4 (2016-05-30)
================================================================================
- Added new profile for Redis 3.2 with its new commands: `HSTRLEN`, `BITFIELD`,
`GEOADD`, `GEOHASH`, `GEOPOS`, `GEODIST`, `GEORADIUS`, `GEORADIUSBYMEMBER`.
The default server profile for Predis is still the one for Redis 3.0 you must
set the `profile` client option to `3.2` when initializing the client in order
to be able to use them when connecting to Redis 3.2.
- Various improvements in the handling of redis-cluster:
- If the connection to a specific node fails when executing a command, the
client tries to connect to another node in order to refresh the slots map
and perform a new attempt to execute the command.
- Connections to nodes can be preassigned to non-contiguous slot ranges via
the `slots` parameter using a comma separator. This is how it looks like
in practice: `tcp://127.0.0.1:6379?slots=0-5460,5500-5600,11000`.
- __FIX__: broken values returned by `Predis\Collection\Iterator\HashKey` when
iterating hash keys containing integer fields (PR #330, ISSUE #331).
- __FIX__: prevent failures when `Predis\Connection\StreamConnection` serializes
commands with holes in their arguments (e.g. `[0 => 'key:0', 2 => 'key:2']`).
The same fix has been applied to `Predis\Protocol\Text\RequestSerializer`.
(ISSUE #316).
v1.0.3 (2015-07-30)
================================================================================
- __FIX__: the previous release introduced a severe regression on HHVM that made
the library unable to connect to Redis when using IPv4 addresses. Code running
on the standard PHP interpreter is not affected.
v1.0.2 (2015-07-30)
================================================================================
- IPv6 is now fully supported.
- Added `redis` as an accepted scheme for connection parameters. When using this
scheme, the rules used to parse URI strings match the provisional registration
[published by IANA](http://www.iana.org/assignments/uri-schemes/prov/redis).
- Added new or missing commands: `HSTRLEN` (>= 3.2), `ZREVRANGEBYLEX` (>= 2.8)
and `MIGRATE` (>= 2.6).
- Implemented support for the `ZADD` modifiers `NX|XX`, `CH`, `INCR` (Redis >=
3.0.2) using the simplified signature where scores and members are passed as
a named array.
- __FIX__: `Predis\Configuration\Options` must not trigger the autoloader when
option values are strings (ISSUE #257).
- __FIX__: `BITPOS` was not defined in the key-prefix processor (ISSUE #265) and
in the replication strategy.
v1.0.1 (2015-01-02)
================================================================================
- Added `BITPOS` to the server profile for Redis 2.8.
- Connection timeout for read/write operations can now be set for UNIX sockets
where the underlying connection uses PHP's stream.
- __FIX__: broken values returned by `Predis\Collection\Iterator\SortedSetKey`
when iterating sorted set containing integer members (ISSUE #216).
- __FIX__: applied a minor workaround for a bug in old versions of PHP < 5.3.9
affecting inheritance.
- __FIX__: prevent E_NOTICE warnings when using INFO [section] returns an empty
response due to an unsupported specific set of information requested to Redis.
v1.0.0 (2014-08-01)
================================================================================
- Switched to PSR-4 for autoloading.
- The default server profile for Redis is `3.0`.
- Removed server profile for Redis 1.2.
- Added `SENTINEL` to the profile for Redis 2.6 and `PUBSUB` to the profile for
Redis 2.8.
- `Predis\Client` can now send raw commands using `Predis\Client::executeRaw()`.
- Status responses are returned as instances of `Predis\Response\Status`, for
example +OK is not returned as boolean TRUE anymore which is a breaking change
for those using strict comparisons. Status responses can be casted to string
values carrying the original payload, so one can do `$response == 'OK'` which
is also more akin to how Redis replies to clients.
- Commands `ZRANGE`, `ZRANGEBYSCORE`, `ZREVRANGE` and `ZREVRANGEBYSCORE` using
`WITHSCORE` return a named array of member => score instead of using an array
of [member, score] elements. Insertion order is preserved anyway due to how
PHP works internally.
- The command `ZSCAN` returns a named array of member => score instead of using
an array of [member, score] elements. Insertion order is preserved anyway due
to how PHP works internally.
- The rules for redis-cluster are now leveraged for empty key tags when using
client-side sharding, which means that when one or the first occurrence of {}
is found in a key it will most likely produce a different hash than previous
versions of Predis thus leading to a different partitioning in these cases.
- Invoking `Predis\Client::connect()` when the underlying connection has been
already established does not throw any exception anymore, now the connection
simply does not attempt to perform any operation.
- Added the `aggregate` client option, useful to fully customize how the client
should aggregate multiple connections when an array of connection parameters
is passed to `Predis\Client::__construct()`.
- Dropped support for streamable multibulk responses. Actually we still ship the
iterator response classes just in case anyone would want to build custom stuff
at a level lower than the client abstraction (our standard and composable text
protocol processors still handle them and can be used as an example).
- Simplified the implementation of connection parameters by removing method used
to cast to int / bool / float certain parameters supplied by users. Casting
values, if deemed necessary, should be done by the consumer or you can just
subclass `Predis\Connection\Parameters` and override the `filter()` method.
- Changed a couple of options for our transaction abstraction:
- `exceptions`: overrides the value of the client option with the same name.
Please note that it does not affect all the transaction control commands
such as `MULTI`, `EXEC`, `DISCARD`, `WATCH` and `UNWATCH`.
- `on_retry`: this option has been removed.
- Removed pipeline executors, now command pipelines can be easily customized by
extending the standard `Predis\Pipeline\Pipeline` class. Accepted options when
creating a pipeline using `Predis\Client::pipeline()` are:
- `atomic`: returns a pipeline wrapped in a MULTI / EXEC transaction
(class: `Predis\Pipeline\Atomic`).
- `fire-and-forget`: returns a pipeline that does not read back responses
(class: `Predis\Pipeline\FireAndForget`).
- Renamed the two base abstract command classes:
- `Predis\Command\AbstractCommand` is now `Predis\Command\Command`
- `Predis\Command\ScriptedCommand` is now `Predis\Command\ScriptCommand`
- Dropped `Predis\Command\Command::__toString()` (see issue #151).
- The key prefixing logic has been moved from command classes to the key prefix
processor. Developers can define or override handlers used to prefix keys, but
they can also define the needed logic in their command classes by implementing
`Predis\Command\PrefixableCommandInterface` just like before.
- `Predis\PubSub\DispatcherLoop` now takes a `Predis\PubSub\Consumer` instance
as the sole argument of its constructor instead of `Predis\ClientInterface`.
- All of the interfaces and classes related to translated Redis response types
have been moved in the new `Predis\Response` namespace and most of them have
been renamed to make their fully-qualified name less redundant. Now the base
response interface is `Predis\Response\ResponseInterface`.
- Renamed interface `Predis\Command\Processor\CommandProcessorInterface` to a
shorter `Predis\Command\Processor\ProcessorInterface`. Also removed interface
for chain processors since it is basically useless.
- Renamed `Predis\ExecutableContextInterface` to `Predis\ClientContextInterface`
and augmented it with a couple of required methods since this interface is no
more comparable to a basic client as it could be misleading.
- The `Predis\Option` namespace is now known as `Predis\Configuration` and have
a fully-reworked `Options` class with the ability to lazily initialize values
using objects that responds to `__invoke()` (not all the kinds of callables)
even for custom options defined by the user.
- Renamed `Predis\Connection\ConnectionInterface::writeCommand()` into
`writeRequest()` for consistency with its counterpart, `readResponse()`.
- Renamed `Predis\Connection\SingleConnectionInterface::pushInitCommand()` into
`addConnectCommand()` which is more obvious.
- Renamed the connection class based on both ext-phpiredis and ext-socket into
`Predis\Connection\PhpiredisSocketConnection`. The one based on PHP's streams
is still named `Predis\Connection\PhpiredisStreamConnection`.
- Renamed the connection factory class to `Predis\Connection\Factory`. Now its
constructor does not require anymore a profile instance to create `AUTH` and
`SELECT` commands when parameters contain both `password` and `database`. Raw
commands will be used instead.
- Renamed the connection parameters class to `Predis\Connection\Parameters`. Now
its constructor accepts only named arrays, but instances can still be created
using both URIs or arrays using the static method `Parameters::create()`.
- The profile factory code has been extracted from the abstract Redis profile
class and now lives in `Predis\Profile\Factory`.
- The `Predis\Connection` namespace has been completely reorganized by renaming
a few classes and interfaces and adding some sub-namespaces.
- Most classes and interfaces in the `Predis\Protocol` namespace have been moved
or renamed while rationalizing the whole API for external protocol processors.
v0.8.7 (2014-08-01)
================================================================================
- 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
- Redis 2.8 is not 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
+2 -10
View File
@@ -18,14 +18,6 @@ least to some degree).
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 SSL-encrypted connections? ###
Yes. Encrypted connections are mostly useful when connecting to Redis instances exposed by various
cloud hosting providers without the need to configure an SSL proxy, but you should also take into
account the general performances degradation especially during the connect() operation when the TLS
handshake must be performed to secure the connection. Persistent SSL-encrypted connections may help
in that respect, but they are supported only when running on PHP >= 7.0.0.
### Does Predis support transparent (de)serialization of values? ###
No and it will not ever do that by default. The reason behind this decision is that serialization is
@@ -141,7 +133,7 @@ library for Redis) with a thin layer exposing its features to PHP. You can then
different connection classes:
- `Predis\Connection\PhpiredisStreamConnection` (using native PHP streams).
- `Predis\Connection\PhpiredisSocketConnection` (requires `ext-socket`).
- `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:
@@ -150,7 +142,7 @@ lines of code:
$client = new Predis\Client('tcp://127.0.0.1', array(
'connections' => array(
'tcp' => 'Predis\Connection\PhpiredisStreamConnection',
'unix' => 'Predis\Connection\PhpiredisSocketConnection',
'unix' => 'Predis\Connection\PhpiredisConnection',
),
));
```
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2016 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
+132 -295
View File
@@ -1,55 +1,56 @@
# Predis #
[![Software license][ico-license]](LICENSE)
[![Latest stable][ico-version-stable]][link-packagist]
[![Latest development][ico-version-dev]][link-packagist]
[![Monthly installs][ico-downloads-monthly]][link-downloads]
[![Build status][ico-travis]][link-travis]
[![HHVM support][ico-hhvm]][link-hhvm]
[![Gitter room][ico-gitter]][link-gitter]
[![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)
Flexible and feature-complete [Redis](http://redis.io) client for PHP >= 5.3 and HHVM >= 2.3.0.
Predis is a flexible and feature-complete [Redis](http://redis.io) client library for PHP >= 5.3.
Predis does not require any additional C extension by default, but it can be optionally paired with
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of the serialization and parsing
of the [Redis RESP Protocol](http://redis.io/topics/protocol). For an __experimental__ asynchronous
implementation of the client you can refer to [Predis\Async](https://github.com/nrk/predis-async).
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).
More details about this project can be found on the [frequently asked questions](FAQ.md).
Predis can be used with [HHVM](http://www.hhvm.com) >= 2.4.0, but there are no guarantees you will
not run into unexpected issues (especially with the JIT compiler 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 ##
- Support for different versions of Redis (from __2.0__ to __3.2__) using profiles.
- Support for clustering using client-side sharding and pluggable keyspace distributors.
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel).
- Transparent key prefixing of keys using a customizable prefix strategy.
- Command pipelining on both single nodes and clusters (client-side sharding only).
- Abstraction for Redis transactions (Redis >= 2.0) and CAS operations (Redis >= 2.2).
- Abstraction for Lua scripting (Redis >= 2.6) and automatic switching between `EVALSHA` or `EVAL`.
- Wide range of Redis versions supported (from __1.2__ to __2.8__ and unstable) using profiles.
- Clustering via client-side sharding using consistent hashing or custom distributors.
- Smart support for [redis-cluster](http://redis.io/topics/cluster-spec) (Redis >= 3.0).
- Support for master-slave replication configurations (write on master, read from slaves).
- Transparent key prefixing for all Redis commands.
- Command pipelining (works on both single 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 are established lazily by the client upon the first command and can be persisted.
- Connections can be established via TCP/IP (also TLS/SSL-encrypted) or UNIX domain sockets.
- Support for [Webdis](http://webd.is) (requires both `ext-curl` and `ext-phpiredis`).
- 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 custom commands and profiles and override the default ones.
- Flexible system for defining and registering custom sets of supported commands or profiles.
## How to _install_ and use Predis ##
## How to use Predis ##
This library can be found on [Packagist](http://packagist.org/packages/predis/predis) for an easier
management of projects dependencies using [Composer](http://packagist.org/about-composer) or on our
[own PEAR channel](http://pear.nrk.io) for a more traditional installation using PEAR. Ultimately,
compressed archives of each release are [available on GitHub](https://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-4 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md).
Autoloading is handled automatically when dependencies are managed through Composer, but it is also
possible to leverage its own autoloader in projects or scripts lacking any autoload 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 using Composer, but you can also leverage its own autoloader if you are going to use it in a
project or script without any PSR-0 compliant autoloading facility:
```php
// Prepend a base path if Predis is not available in your "include_path".
@@ -58,15 +59,17 @@ require 'Predis/Autoloader.php';
Predis\Autoloader::register();
```
It is also possible to create a [phar](http://www.php.net/manual/en/intro.phar.php) archive directly
from the repository by launching the `bin/create-phar` script. The generated phar already contains a
stub defining its own autoloader, so you just need to `require()` it to start using the library.
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 also possible to generate one single PHP file that holds every class like older
versions of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
### Connecting to Redis ###
When creating a client instance without passing any connection parameter, Predis assumes `127.0.0.1`
and `6379` as default host and port. The default timeout for the `connect()` operation is 5 seconds:
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 and uses a connection timeout of 5 seconds:
```php
$client = new Predis\Client();
@@ -74,57 +77,25 @@ $client->set('foo', 'bar');
$value = $client->get('foo');
```
Connection parameters can be supplied either in the form of URI strings or named arrays. The latter
is the preferred way to supply parameters, but URI strings can be useful when parameters are read
from non-structured or partially-structured sources:
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
// Parameters passed using a named array:
// Named array of connection parameters:
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '10.0.0.1',
'port' => 6379,
]);
// Same set of parameters, passed using an URI string:
// Same set of parameters, but using an URI string:
$client = new Predis\Client('tcp://10.0.0.1:6379');
```
It is also possible to connect to local instances of Redis using UNIX domain sockets, in this case
the parameters must use the `unix` scheme and specify a path for the socket file:
```php
$client = new Predis\Client(['scheme' => 'unix', 'path' => '/path/to/redis.sock']);
$client = new Predis\Client('unix:/path/to/redis.sock');
```
The client can leverage TLS/SSL encryption to connect to secured remote Redis instances without the
need to configure an SSL proxy like stunnel. This can be useful when connecting to nodes running on
various cloud hosting providers. Encryption can be enabled with using the `tls` scheme and an array
of suitable [options](http://php.net/manual/context.ssl.php) passed via the `ssl` parameter:
```php
// Named array of connection parameters:
$client = new Predis\Client([
'scheme' => 'tls',
'ssl' => ['cafile' => 'private.pem', 'verify_peer' => true],
]
// Same set of parameters, but using an URI string:
$client = new Predis\Client('tls://127.0.0.1?ssl[cafile]=private.pem&ssl[verify_peer]=1');
```
The connection schemes [`redis`](http://www.iana.org/assignments/uri-schemes/prov/redis) (alias of
`tcp`) and [`rediss`](http://www.iana.org/assignments/uri-schemes/prov/rediss) (alias of `tls`) are
also supported, with the difference that URI strings containing these schemes are parsed following
the rules described on their respective IANA provisional registration documents.
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.
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:
When an array of connections parameters is provided, Predis automatically works in clustering mode
using client-side sharding. Both named arrays and URI strings can be mixed for providing each node
configuration:
```php
$client = new Predis\Client([
@@ -133,148 +104,34 @@ $client = new Predis\Client([
]);
```
See the [aggregate connections](#aggregate-connections) section of this document for more details.
Connections to Redis are lazy meaning that the client connects to a server only if and when needed.
While it is recommended to let the client do its own stuff under the hood, there may be times when
it is still desired to have control of when the connection is opened or closed: this can easily be
achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect
of these methods on aggregate connections may differ depending on each specific implementation.
The actual list of supported connection parameters can vary depending on each connection backend so
it is recommended to refer to their specific documentation for details.
### Client configuration ###
Many aspects and behaviors of the client can be configured by passing specific client options to the
second argument of `Predis\Client::__construct()`:
Various aspects of the client can be easily configured 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:
```php
$client = new Predis\Client($parameters, ['profile' => '2.8', 'prefix' => 'sample:']);
```
Options are managed using a mini DI-alike container and their values can be lazily initialized only
when needed. The client options supported by default in Predis are:
- `profile`: specifies the profile to use to match a specific version of Redis.
- `prefix`: prefix string automatically applied to keys found in commands.
- `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`: list of connection backends or a connection factory instance.
- `cluster`: specifies a cluster backend (`predis`, `redis` or callable object).
- `replication`: specifies a replication backend (`TRUE`, `sentinel` or callable object).
- `aggregate`: overrides `cluster` and `replication` to provide a custom connections aggregator.
- `parameters`: list of default connection parameters for aggregate connections.
- `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 also provide custom options with values or callable objects (for lazy initialization) that
are stored in the options container for later use through the library.
Users can provide custom option values, they are stored in the options container and can be accessed
later through the library.
### Aggregate connections ###
Aggregate connections are the foundation upon which Predis implements clustering and replication and
they are used to group multiple connections to single Redis nodes and hide the specific logic needed
to handle them properly depending on the context. Aggregate connections usually require an array of
connection parameters when creating a new client instance.
#### Cluster ####
By default, when no specific client options are set and an array of connection parameters is passed
to the client's constructor, Predis configures itself to work in clustering mode using a traditional
client-side sharding approach to create a cluster of independent nodes and distribute the keyspace
among them. This approach needs some form of external health monitoring of nodes and requires manual
operations to rebalance the keyspace when changing its configuration by adding or removing nodes:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$client = new Predis\Client($parameters);
```
Along with Redis 3.0, a new supervised and coordinated type of clustering was introduced in the form
of [redis-cluster](http://redis.io/topics/cluster-tutorial). This kind of approach uses a different
algorithm to distribute the keyspaces, with Redis nodes coordinating themselves by communicating via
a gossip protocol to handle health status, rebalancing, nodes discovery and request redirection. In
order to connect to a cluster managed by redis-cluster, the client requires a list of its nodes (not
necessarily complete since it will automatically discover new nodes if necessary) and the `cluster`
client options set to `redis`:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['cluster' => 'redis'];
$client = new Predis\Client($parameters, $options);
```
#### Replication ####
The client can be configured to operate in a single master / multiple slaves setup to provide better
service availability. When using replication, Predis recognizes read-only commands and sends them to
a random slave in order to provide some sort of load-balancing and switches to the master as soon as
it detects a command that performs any kind of operation that would end up modifying the keyspace or
the value of a key. Instead of raising a connection error when a slave fails, the client attempts to
fall back to a different slave among the ones provided in the configuration.
The basic configuration needed to use the client in replication mode requires one Redis server to be
identified as the master (this can be done via connection parameters using the `alias` parameter set
to `master`) and one or more servers acting as slaves:
```php
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['replication' => true];
$client = new Predis\Client($parameters, $options);
```
The above configuration has a static list of servers and relies entirely on the client's logic, but
it is possible to rely on [`redis-sentinel`](http://redis.io/topics/sentinel) for a more robust HA
environment with sentinel servers acting as a source of authority for clients for service discovery.
The minimum configuration required by the client to work with redis-sentinel is a list of connection
parameters pointing to a bunch of sentinel instances, the `replication` option set to `sentinel` and
the `service` option set to the name of the service:
```php
$sentinels = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['replication' => 'sentinel', 'service' => 'mymaster'];
$client = new Predis\Client($sentinels, $options);
```
If the master and slave nodes are configured to require an authentication from clients, a password
must be provided via the global `parameters` client option. This option can also be used to specify
a different database index. The client options array would then look like this:
```php
$options = [
'replication' => 'sentinel',
'service' => 'mymaster',
'parameters' => [
'password' => $secretpassword,
'database' => 10,
],
];
```
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 cannot
tell when a Lua script is safe to be executed on slaves. While this is indeed 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', 'tcp://10.0.0.3'];
$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`](examples/) directory contains a few scripts that demonstrate how the client can be
configured and used to leverage replication in both basic and complex scenarios.
Predis is able to aggregate multiple connections which is the base for clustering and replication.
By default the client implements clustering 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 single-master and multiple-slaves setups by executing read operations on slaves
and switching to the master for write operations. The replication behaviour is fully configurable.
### Command pipelines ###
@@ -285,7 +142,7 @@ The client can execute the pipeline inside a callable block or return a pipeline
ability to chain commands thanks to its fluent interface:
```php
// Executes a pipeline inside the given callable block:
// 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));
@@ -293,7 +150,7 @@ $responses = $client->pipeline(function ($pipe) {
}
});
// Returns a pipeline that can be chained thanks to its fluent interface:
// Returns a pipeline instance with fluent interface:
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();
```
@@ -304,31 +161,70 @@ The client provides an abstraction for Redis transactions based on `MULTI` and `
interface to command pipelines:
```php
// Executes a transaction inside the given callable block:
// Executes a transaction inside a given callable block:
$responses = $client->transaction(function ($tx) {
$tx->set('foo', 'bar');
$tx->get('foo');
});
// Returns a transaction that can be chained thanks to its fluent interface:
// Returns a transaction instance with fluent interface:
$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute();
```
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/transaction_using_cas.php).
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 commands ###
### Customizable connection backends ###
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 old 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 default server profiles used by the client:
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:
```php
// Define a new command by extending Predis\Command\Command:
class BrandNewRedisCommand extends Predis\Command\Command
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => [
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
'unix' => 'Predis\Connection\PhpiredisConnection', // ext-socket
],
]);
```
Developers can create their own connection classes to add support for 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.
### Adding support for new 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()
{
@@ -343,29 +239,20 @@ $client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
$response = $client->newcmd();
```
There is also a method to send raw commands without filtering their arguments or parsing responses.
Users must provide the list of arguments for the command as an array, following the signatures as
defined by the [Redis documentation for commands](http://redis.io/commands):
### Scriptable commands ###
A scriptable command is just an abstraction for [Lua scripting](http://redis.io/commands/eval) that
aims to simplify the usage of scripting with Redis >= 2.6. Scriptable 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 a Lua script that gets transmitted to Redis for remote execution. Internally, scriptable
commands use by default [EVALSHA](http://redis.io/commands/evalsha) and identify a Lua script by its
SHA1 hash to save bandwidth but [EVAL](http://redis.io/commands/eval) is automatically preferred as
a fall back when needed:
```php
$response = $client->executeRaw(['SET', 'foo', 'bar']);
```
### Script commands ###
While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using
directly [`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha),
Predis offers script commands as an higher level abstraction built upon them 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 script command by extending Predis\Command\ScriptCommand:
class ListPushRandomValue extends Predis\Command\ScriptCommand
// Define a new scriptable command by extending Predis\Command\ScriptedCommand:
class ListPushRandomValue extends Predis\Command\ScriptedCommand
{
public function getKeysCount()
{
@@ -383,7 +270,7 @@ LUA;
}
}
// Inject the script command in the current profile:
// Inject your scriptable command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
@@ -391,42 +278,6 @@ $response = $client->lpushrand('random_values', $seed = mt_rand());
```
### Customizable connection backends ###
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 and UNIX domain sockets:
```php
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => [
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP stream resources
'unix' => 'Predis\Connection\PhpiredisSocketConnection', // ext-socket resources
],
]);
```
Developers can create their own connection classes to support whole new network backends, extend
existing classes or provide completely different implementations. Connection classes must implement
`Predis\Connection\NodeConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
```php
class MyConnectionClass implements Predis\Connection\NodeConnectionInterface
{
// 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.
## Development ##
@@ -445,9 +296,9 @@ 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
behaviour 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 3.2
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)
@@ -476,17 +327,3 @@ found [on its project page](http://travis-ci.org/nrk/predis).
### License ###
The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
[ico-license]: https://img.shields.io/github/license/nrk/predis.svg?style=flat-square
[ico-version-stable]: https://img.shields.io/packagist/v/predis/predis.svg?style=flat-square
[ico-version-dev]: https://img.shields.io/packagist/vpre/predis/predis.svg?style=flat-square
[ico-downloads-monthly]: https://img.shields.io/packagist/dm/predis/predis.svg?style=flat-square
[ico-travis]: https://img.shields.io/travis/nrk/predis.svg?style=flat-square
[ico-hhvm]: https://img.shields.io/hhvm/predis/predis.svg?style=flat-square
[ico-gitter]: https://img.shields.io/gitter/room/nrk/predis.svg?style=flat-square
[link-packagist]: https://packagist.org/packages/predis/predis
[link-travis]: https://travis-ci.org/nrk/predis
[link-downloads]: https://packagist.org/packages/predis/predis/stats
[link-hhvm]: http://hhvm.h4cc.de/package/predis/predis
[link-gitter]: https://gitter.im/nrk/predis
+1 -1
View File
@@ -1 +1 @@
1.1.0
0.8.6
+1 -1
View File
@@ -9,6 +9,6 @@
* file that was distributed with this source code.
*/
require __DIR__.'/src/Autoloader.php';
require __DIR__.'/lib/Predis/Autoloader.php';
Predis\Autoloader::register();
+5 -9
View File
@@ -17,7 +17,7 @@
// For example, to generate a test case for SET (which is represented by the
// Predis\Command\StringSet class):
//
// $ ./bin/generate-command-test --class=StringSet
// $ ./bin/generate-command-test.php --class=StringSet
//
// Here is a list of optional arguments:
//
@@ -40,7 +40,7 @@ class CommandTestCaseGenerator
{
private $options;
public function __construct(array $options)
public function __construct(Array $options)
{
if (!isset($options['class'])) {
throw new RuntimeException("Missing 'class' option.");
@@ -61,7 +61,7 @@ class CommandTestCaseGenerator
$options = array(
'overwrite' => false,
'tests' => __DIR__.'/../tests/Predis',
'tests' => __DIR__.'/../tests',
);
foreach ($getops as $option => $value) {
@@ -93,9 +93,9 @@ class CommandTestCaseGenerator
}
$options['fqn'] = "Predis\\Command\\{$options['class']}";
$options['path'] = "Command/{$options['class']}.php";
$options['path'] = "Predis/Command/{$options['class']}.php";
$source = __DIR__.'/../src/'.$options['path'];
$source = __DIR__.'/../lib/'.$options['path'];
if (!file_exists($source)) {
throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
}
@@ -134,11 +134,7 @@ class CommandTestCaseGenerator
throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
}
/*
* @var CommandInterface
*/
$instance = $reflection->newInstance();
$buffer = $this->getTestCaseBuffer($instance);
return $buffer;
+2 -14
View File
@@ -46,12 +46,12 @@ function parseAuthor($string)
if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
if (count($regs) == 4) {
list($_,$name,$user,$email) = $regs;
list($orig,$name,$user,$email) = $regs;
$author['name'] = $name;
$author['user'] = $user;
$author['email'] = $email;
} elseif (count($regs) == 3) {
list($_,$name,$email) = $regs;
list($orig,$name,$email) = $regs;
$author['name'] = $name;
$author['email'] = $email;
}
@@ -78,8 +78,6 @@ function parseVersion($string)
'max' => $regs[2],
);
}
return null;
}
function addRolePath($pkg, $path, $role)
@@ -186,15 +184,6 @@ XML;
return $pkg;
}
function rewritePackageInstallAs($pkg)
{
foreach ($pkg->phprelease->filelist->install as $file) {
if (preg_match('/^src\//', $file['name'])) {
$file['as'] = "Predis/{$file['as']}";
}
}
}
function savePackageXml($xml)
{
$dom = new DOMDocument("1.0");
@@ -226,7 +215,6 @@ executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
modifyPhpunitXml($file);
$pkg = generatePackageXml('package.ini');
rewritePackageInstallAs($pkg);
savePackageXml($pkg);
buildPackage();
+2 -2
View File
@@ -19,7 +19,7 @@
$options = array(
'name' => 'predis',
'project_path' => __DIR__ . '/../src',
'project_path' => __DIR__ . '/../lib/',
'compression' => Phar::NONE,
'append_version' => true,
);
@@ -52,7 +52,7 @@ function getPharStub($options)
Phar::mapPhar('predis.phar');
spl_autoload_register(function (\$class) {
if (strpos(\$class, 'Predis\\\\') === 0) {
\$file = 'phar://predis.phar/'.strtr(substr(\$class, 7), '\\\', '/').'.php';
\$file = 'phar://predis.phar/'.strtr(\$class, '\\\', '/').'.php';
if (file_exists(\$file)) {
require \$file;
return true;
+10 -9
View File
@@ -19,7 +19,7 @@
// files, but namespaces and classes definitions must follow a precise order
// when dealing with subclassing and inheritance.
//
// The current implementation is pretty naïve, but it should do for now.
// The current implementation is pretty naïve, but it should do for now.
// -------------------------------------------------------------------------- //
class CommandLine
@@ -27,16 +27,16 @@ class CommandLine
public static function getOptions()
{
$parameters = array(
's:' => 'source:',
'o:' => 'output:',
'e:' => 'exclude:',
'E:' => 'exclude-classes:',
's:' => 'source:',
'o:' => 'output:',
'e:' => 'exclude:',
'E:' => 'exclude-classes:',
);
$getops = getopt(implode(array_keys($parameters)), $parameters);
$options = array(
'source' => __DIR__ . "/../src",
'source' => __DIR__ . "/../lib/",
'output' => PredisFile::NS_ROOT . '.php',
'exclude' => array(),
);
@@ -80,10 +80,11 @@ class PredisFile
$this->namespaces = array();
}
public static function from($libraryPath, array $exclude = array())
public static function from($libraryPath, Array $exclude = array())
{
$nsroot = self::NS_ROOT;
$predisFile = new PredisFile();
$libIterator = new RecursiveDirectoryIterator($libraryPath);
$libIterator = new RecursiveDirectoryIterator("$libraryPath$nsroot");
foreach (new RecursiveIteratorIterator($libIterator) as $classFile)
{
@@ -91,7 +92,7 @@ class PredisFile
continue;
}
$namespace = self::NS_ROOT.strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
$namespace = strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
if (in_array(sprintf('%s\\%s', $namespace, $classFile->getBasename('.php')), $exclude)) {
continue;
+4 -7
View File
@@ -1,13 +1,10 @@
{
"name": "predis/predis",
"type": "library",
"description": "Flexible and feature-complete Redis client for PHP and HHVM",
"description": "Flexible and feature-complete PHP client library for Redis",
"keywords": ["nosql", "redis", "predis"],
"homepage": "http://github.com/nrk/predis",
"license": "MIT",
"support": {
"issues": "https://github.com/nrk/predis/issues"
},
"authors": [
{
"name": "Daniele Alessandri",
@@ -16,16 +13,16 @@
}
],
"require": {
"php": ">=5.3.9"
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
"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"
},
"autoload": {
"psr-4": {"Predis\\": "src/"}
"psr-0": {"Predis": "lib/"}
}
}
@@ -9,18 +9,17 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// Developers can implement Predis\Distribution\DistributorInterface to create
// their own distributors used by the client to distribute keys among a cluster
// of servers.
// Developers can customize the distribution strategy used by the client
// to distribute keys among a cluster of servers simply by creating a class
// that implements Predis\Distribution\DistributionStrategyInterface.
use Predis\Cluster\Distributor\DistributorInterface;
use Predis\Connection\PredisCluster;
use Predis\Cluster\Distribution\DistributionStrategyInterface;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\Cluster\PredisStrategy;
use Predis\Connection\Aggregate\PredisCluster;
class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
class NaiveDistributionStrategy implements DistributionStrategyInterface, HashGeneratorInterface
{
private $nodes;
private $nodesCount;
@@ -34,7 +33,7 @@ class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
public function add($node, $weight = null)
{
$this->nodes[] = $node;
++$this->nodesCount;
$this->nodesCount++;
}
public function remove($node)
@@ -46,34 +45,13 @@ class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
$this->nodesCount = count($this->nodes);
}
public function getSlot($hash)
public function get($key)
{
return $this->nodesCount > 1 ? abs($hash % $this->nodesCount) : 0;
}
public function getBySlot($slot)
{
return isset($this->nodes[$slot]) ? $this->nodes[$slot] : null;
}
public function getByHash($hash)
{
if (!$this->nodesCount) {
throw new RuntimeException('No connections.');
if (0 === $count = $this->nodesCount) {
throw new RuntimeException('No connections');
}
$slot = $this->getSlot($hash);
$node = $this->getBySlot($slot);
return $node;
}
public function get($value)
{
$hash = $this->hash($value);
$node = $this->getByHash($hash);
return $node;
return $this->nodes[$count > 1 ? abs($key % $count) : 0];
}
public function hash($value)
@@ -89,9 +67,8 @@ class NaiveDistributor implements DistributorInterface, HashGeneratorInterface
$options = array(
'cluster' => function () {
$distributor = new NaiveDistributor();
$strategy = new PredisStrategy($distributor);
$cluster = new PredisCluster($strategy);
$distributor = new NaiveDistributionStrategy();
$cluster = new PredisCluster($distributor);
return $cluster;
},
@@ -99,7 +76,7 @@ $options = array(
$client = new Predis\Client($multiple_servers, $options);
for ($i = 0; $i < 100; ++$i) {
for ($i = 0; $i < 100; $i++) {
$client->set("key:$i", str_pad($i, 4, '0', 0));
$client->get("key:$i");
}
@@ -107,11 +84,6 @@ 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']
);
@@ -9,29 +9,28 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// This is a basic example on how to use the Predis\DispatcherLoop class.
//
// To see this example in action you can just use redis-cli and publish some
// messages to the 'events' and 'control' channel, e.g.:
/*
This is a basic example on how to use the Predis\DispatcherLoop class.
// ./redis-cli
// PUBLISH events first
// PUBLISH events second
// PUBLISH events third
// PUBLISH control terminate_dispatcher
To see this example in action you can just use redis-cli and publish some
messages to the 'events' and 'control' channel, e.g.:
./redis-cli
PUBLISH events first
PUBLISH events second
PUBLISH events third
PUBLISH control terminate_dispatcher
*/
// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Return an initialized PubSub consumer instance from the client.
$pubsub = $client->pubSubLoop();
// Create a Predis\DispatcherLoop instance and attach a bunch of callbacks.
$dispatcher = new Predis\PubSub\DispatcherLoop($client);
// Create a dispatcher loop instance and attach a bunch of callbacks.
$dispatcher = new Predis\PubSub\DispatcherLoop($pubsub);
// Demonstrate how to use a callable class as a callback for the dispatcher loop.
// Demonstrate how to use a callable class as a callback for Predis\DispatcherLoop.
class EventsListener implements Countable
{
private $events;
@@ -72,8 +71,8 @@ $dispatcher->attachCallback('control', function ($payload) use ($dispatcher) {
$dispatcher->run();
// Display our achievements!
echo "We received {$events->count()} messages!", PHP_EOL;
echo "We received {$events->count()} messages!\n";
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
+37
View File
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// Predis ships with a KeyPrefixProcessor class that is used to transparently
// prefix each key before sending commands to Redis, even for complex commands
// such as SORT, ZUNIONSTORE and ZINTERSTORE. Key prefixes are useful to create
// user-level namespaces for you keyspace, thus eliminating the need for separate
// logical databases.
$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
var_dump($client->mget('foo', 'lol'));
/*
array(2) {
[0]=> string(3) "bar"
[1]=> string(3) "wut"
}
*/
var_dump($client->keys('*'));
/*
array(2) {
[0]=> string(7) "nrk:foo"
[1]=> string(7) "nrk:lol"
}
*/
@@ -9,16 +9,16 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// Predis supports master / slave replication scenarios where write operations
// are performed on the master server and read operations are executed against
// one of the slaves. The behavior of commands or EVAL scripts can be customized
// at will. As soon as a write operation is performed the client switches to the
// master server for all the subsequent requests (either reads and writes).
// Predis supports master / slave replication scenarios where write operations are
// performed on the master server and read operations are executed against one of
// the slaves. The behaviour of commands or EVAL scripts can be customized at will.
// As soon as a write operation is performed, all the subsequent requests (reads
// or writes) will be served by the master server.
//
// This example must be executed using the second Redis server configured as the
// slave of the first one (see the "SLAVEOF" command).
// This example must be executed with the second Redis server acting as the slave
// of the first one using the SLAVEOF command.
//
$parameters = array(
@@ -33,20 +33,20 @@ $client = new Predis\Client($parameters, $options);
// Read operation.
$exists = $client->exists('foo') ? 'yes' : 'no';
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL;
echo "Does 'foo' exist on {$current->alias}? $exists.\n";
// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL;
echo "Now 'foo' has been set to 'bar' on {$current->alias}!\n";
// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL;
echo "We just fetched 'foo' from {$current->alias} and its value is '$bar'.\n";
/* OUTPUT:
Does 'foo' exist on slave? yes.
Now 'foo' has been set to 'bar' on master!
We fetched 'foo' from master and its value is 'bar'.
We just fetched 'foo' from master and its value is 'bar'.
*/
@@ -9,37 +9,36 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// Predis allows to set Lua scripts as read-only operations for replication.
// This works for both EVAL and EVALSHA and also for the client-side abstraction
// built upon them (Predis\Command\ScriptCommand). This example shows a slightly
// more complex configuration that injects a new script command in the server
// profile used by the new client instance and marks it marks it as a read-only
// operation for replication so that it will be executed on slaves.
// Predis allows to set Lua scripts as read-only operations in the context of
// replication. This works for both EVAL and EVALSHA and also for the client-side
// abstraction built upon them (Predis\Command\ScriptedCommand). This example
// shows a slightly more complex configuration that injects a new scripted command
// in the server profile used by the new client instance and marks it marks it as
// a read-only operation for replication so that it will be executed on slaves.
use Predis\Command\ScriptCommand;
use Predis\Connection\Aggregate\MasterSlaveReplication;
use Predis\Command\ScriptedCommand;
use Predis\Connection\MasterSlaveReplication;
use Predis\Profile\ServerProfile;
use Predis\Replication\ReplicationStrategy;
// ------------------------------------------------------------------------- //
// Define a new script command that returns all the fields of a variable number
// of hashes with a single roundtrip.
// Define a new scripted command that returns all the fields
// of a variable number of hashes with a single roundtrip.
class HashMultipleGetAll extends ScriptCommand
{
const BODY = <<<LUA
class HashMultipleGetAll extends ScriptedCommand {
const BODY = <<<EOS
local hashes = {}
for _, key in pairs(KEYS) do
table.insert(hashes, key)
table.insert(hashes, redis.call('hgetall', key))
end
return hashes
LUA;
EOS;
public function getScript()
{
public function getScript() {
return self::BODY;
}
}
@@ -58,7 +57,7 @@ $options = array(
return $profile;
},
'replication' => function () {
'replication' => function ($options) {
$strategy = new ReplicationStrategy();
$strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
@@ -81,5 +80,5 @@ $hashes = $client->hmgetall('metavars', 'servers');
$replication = $client->getConnection();
$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave');
echo 'Is still on slave? ', $stillOnSlave ? 'YES!' : 'NO!', PHP_EOL;
echo "Is still on slave? ", $stillOnSlave ? 'YES' : 'NO', "!\n";
var_export($hashes);
@@ -9,10 +9,10 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// This is a basic example on how to use the Predis\Monitor\Consumer class. You
// can use redis-cli to send commands to the same Redis instance your client is
// This is a basic example on how to use the Predis\MonitorContext class.
// You can use redis-cli to send commands to the same Redis instance your client is
// connected to, and then type "ECHO QUIT_MONITOR" in redis-cli when you want to
// exit the monitor loop and terminate this script in a graceful way.
@@ -25,20 +25,20 @@ $timestamp = new DateTime();
foreach (($monitor = $client->monitor()) as $event) {
$timestamp->setTimestamp((int) $event->timestamp);
// If we notice a ECHO command with the message QUIT_MONITOR, we stop the
// monitor consumer and then break the loop.
// If we notice a ECHO command with the message QUIT_MONITOR, we close the
// monitor context and then break the loop.
if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') {
echo 'Exiting the monitor loop...', PHP_EOL;
$monitor->stop();
echo "Exiting the monitor loop...\n";
$monitor->closeContext();
break;
}
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}", PHP_EOL;
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}\n";
if (isset($event->arguments)) {
echo " Arguments: {$event->arguments}", PHP_EOL;
echo " Arguments: {$event->arguments}\n";
}
}
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
+56
View File
@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// Operations such as LRANGE, ZRANGE and others can potentially generate replies
// containing a huge number of items. In some corner cases, such replies might
// end up exhausting the maximum allowed memory allocated for a PHP process.
// Multibulk iterators can be handy because they allow you to stream multibulk
// replies using plain old PHP iterators, making it possible to iterate them with
// a classic `foreach` loop and avoiding to consume an excessive amount of memory.
//
// PS: please note that multibulk iterators are supported only by the standard
// connection backend class (Predis\Connection\StreamConnection) and not the
// phpiredis-based one (Predis\Connection\PhpiredisConnection).
// Create a client and force the connection to use iterable multibulk responses.
$client = new Predis\Client($single_server + array('iterable_multibulk' => true));
// Prepare an hash with some fields and their respective values.
$client->hmset('metavars', array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut'));
// By default multibulk iterators iterate over the reply as a list of items...
foreach ($client->hgetall('metavars') as $index => $item) {
echo "[$index] $item\n";
}
/* OUTPUT:
[0] foo
[1] bar
[2] hoge
[3] piyo
[4] lol
[5] wut
*/
// ... but certain multibulk replies are better represented as lists of tuples.
foreach ($client->hgetall('metavars')->asTuple() as $index => $kv) {
list($key, $value) = $kv;
echo "[$index] $key => $value\n";
}
/* OUTPUT:
[0] foo => bar
[1] hoge => piyo
[2] lol => wut
*/
+38
View File
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// redis can set keys and their relative values in one go
// using MSET, then the same values can be retrieved with
// a single command using MGET.
$mkv = array(
'usr:0001' => 'First user',
'usr:0002' => 'Second user',
'usr:0003' => 'Third user'
);
$client = new Predis\Client($single_server);
$client->mset($mkv);
$retval = $client->mget(array_keys($mkv));
print_r($retval);
/* OUTPUT:
Array
(
[0] => First user
[1] => Second user
[2] => Third user
)
*/
@@ -9,15 +9,15 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// When you have a whole set of consecutive commands to send to a redis server,
// you can use a pipeline to dramatically improve performances. Pipelines can
// greatly reduce the effects of network round-trips.
// When you have a whole set of consecutive commands to send to
// a redis server, you can use a pipeline to improve performances.
$client = new Predis\Client($single_server);
$responses = $client->pipeline(function ($pipe) {
$replies = $client->pipeline(function ($pipe) {
$pipe->ping();
$pipe->flushdb();
$pipe->incrby('counter', 10);
$pipe->incrby('counter', 30);
@@ -26,20 +26,22 @@ $responses = $client->pipeline(function ($pipe) {
$pipe->mget('does_not_exist', 'counter');
});
var_export($responses);
print_r($replies);
/* OUTPUT:
array (
0 => Predis\Response\Status::__set_state(array(
'payload' => 'OK',
)),
1 => 10,
2 => 40,
3 => true,
4 => '40',
5 => array (
0 => NULL,
1 => '40',
),
Array
(
[0] => 1
[1] => 1
[2] => 10
[3] => 40
[4] => 1
[5] => 40
[6] => Array
(
[0] =>
[1] => 40
)
)
*/
@@ -9,15 +9,15 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// Starting from Redis 2.0 clients can subscribe and listen for events published
// on certain channels using a Publish/Subscribe (PUB/SUB) approach.
// Redis 2.0 features new commands that allow clients to subscribe for
// events published on certain channels (PUBSUB).
// Create a client and disable r/w timeout on the socket
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
// Initialize a new pubsub consumer.
// Initialize a new pubsub context
$pubsub = $client->pubSubLoop();
// Subscribe to your channels
@@ -30,30 +30,30 @@ $pubsub->subscribe('control_channel', 'notifications');
foreach ($pubsub as $message) {
switch ($message->kind) {
case 'subscribe':
echo "Subscribed to {$message->channel}", PHP_EOL;
echo "Subscribed to {$message->channel}\n";
break;
case 'message':
if ($message->channel == 'control_channel') {
if ($message->payload == 'quit_loop') {
echo 'Aborting pubsub loop...', PHP_EOL;
echo "Aborting pubsub loop...\n";
$pubsub->unsubscribe();
} else {
echo "Received an unrecognized command: {$message->payload}.", PHP_EOL;
echo "Received an unrecognized command: {$message->payload}.\n";
}
} else {
echo "Received the following message from {$message->channel}:",
PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL;
echo "Received the following message from {$message->channel}:\n",
" {$message->payload}\n\n";
}
break;
}
}
// Always unset the pubsub consumer instance when you are done! The
// Always unset the pubsub context instance when you are done! The
// class destructor will take care of cleanups and prevent protocol
// desynchronizations between the client and the server.
unset($pubsub);
// Say goodbye :-)
$version = redis_version($client->info());
echo "Goodbye from Redis $version!", PHP_EOL;
$info = $client->info();
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
@@ -9,22 +9,19 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
use Predis\Collection\Iterator;
// Starting from Redis 2.8, clients can iterate incrementally over collections
// without blocking the server like it happens when a command such as KEYS is
// executed on a Redis instance storing millions of keys. These commands are:
//
// - SCAN (iterates over the keyspace)
// - SSCAN (iterates over members of a set)
// - ZSCAN (iterates over members and ranks of a sorted set)
// - HSCAN (iterates over fields and values of an hash).
// Predis provides a specialized abstraction for each command based on standard
// SPL iterators making it possible to easily consume SCAN-based iterations in
// your PHP code.
// Redis 2.8 features new commands allowing clients to incrementally
// iterate over collections without blocking the server like it happens
// when a command such as KEYS is executed on a Redis instance storing
// millions of keys. These commands are SCAN (iterates over the keyspace),
// SSCAN (iterates over members of a set), ZSCAN (iterates over members
// and ranks of a sorted set) and HSCAN (iterates over fields and values
// of an hash). Predis provides a specialized abstraction for each command
// based on SPL iterators making it possible to easily consume SCAN-based
// iterations in your PHP code.
//
// See http://redis.io/commands/scan for more details.
//
@@ -34,7 +31,7 @@ $client = new Predis\Client($single_server, array('profile' => '2.8'));
// Prepare some keys for our example
$client->del('predis:set', 'predis:zset', 'predis:hash');
for ($i = 0; $i < 5; ++$i) {
for ($i = 0; $i < 5; $i++) {
$client->sadd('predis:set', "member:$i");
$client->zadd('predis:zset', -$i, "member:$i");
$client->hset('predis:hash', "field:$i", "value:$i");
@@ -9,19 +9,18 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// This example will not work with versions of Redis < 2.6.
//
// Additionally to the EVAL command defined in the current development profile,
// the Predis\Command\ScriptCommand class can be used to build an higher level
// abstraction for "scriptable" commands so that they will appear just like any
// other command on the client-side. This is a quick example used to implement
// INCREX.
// Additionally to the EVAL command defined in the current development profile, the new
// Predis\Command\ScriptedCommand base class can be used to build an higher abstraction
// for our "scripted" commands so that they will appear just like any other command on
// the client-side. This is a quick example used to implement INCREX.
use Predis\Command\ScriptCommand;
use Predis\Command\ScriptedCommand;
class IncrementExistingKeysBy extends ScriptCommand
class IncrementExistingKeysBy extends ScriptedCommand
{
public function getKeysCount()
{
@@ -32,7 +31,8 @@ class IncrementExistingKeysBy extends ScriptCommand
public function getScript()
{
return <<<LUA
return
<<<LUA
local cmd, insert = redis.call, table.insert
local increment, results = ARGV[1], { }
@@ -49,14 +49,9 @@ LUA;
}
}
$client = new Predis\Client($single_server, array(
'profile' => function ($options) {
$profile = $options->getDefault('profile');
$profile->defineCommand('increxby', 'IncrementExistingKeysBy');
$client = new Predis\Client($single_server);
return $profile;
},
));
$client->getProfile()->defineCommand('increxby', 'IncrementExistingKeysBy');
$client->mset('foo', 10, 'foobar', 100);
+39
View File
@@ -0,0 +1,39 @@
<?php
require 'SharedConfigurations.php';
// This example demonstrates how to leverage Predis to save PHP sessions on Redis.
//
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as the
// the TTL for keys holding session data on Redis, but this value can be overridden
// when creating the session handler instance with the `gc_maxlifetime` option.
//
// Note that this class needs PHP >= 5.4 but can be used on PHP 5.3 if a polyfill for
// SessionHandlerInterface (see http://www.php.net/class.sessionhandlerinterface.php)
// is provided either by you or an external package like `symfony/http-foundation`.
if (!interface_exists('SessionHandlerInterface')) {
die("ATTENTION: the session handler implemented by Predis needs PHP >= 5.4.0 or a polyfill ".
"for \SessionHandlerInterface either provided by you or an external package.\n");
}
// Instantiate a new client just like you would normally do. We'll prefix our session keys here.
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
// Set `gc_maxlifetime` so that a session will be expired after 5 seconds since last access.
$handler = new Predis\Session\SessionHandler($client, array('gc_maxlifetime' => 5));
// Register our session handler (it uses `session_set_save_handler()` internally).
$handler->register();
// Set a fixed session ID just for the sake of our example.
session_id('example_session_id');
session_start();
if (isset($_SESSION['foo'])) {
echo "Session has `foo` set to {$_SESSION['foo']}\n";
} else {
$_SESSION['foo'] = $value = mt_rand();
echo "Empty session, `foo` has been set with $value\n";
}
+33
View File
@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/../autoload.php';
$single_server = array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15
);
$multiple_servers = array(
array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
'alias' => 'first',
),
array(
'host' => '127.0.0.1',
'port' => 6380,
'database' => 15,
'alias' => 'second',
),
);
@@ -9,12 +9,7 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// This is an example of how you can easily extend an existing connection class
// and trace the execution of commands for debugging purposes. This can be quite
// useful as a starting poing to understand how your application interacts with
// Redis.
require 'SharedConfigurations.php';
use Predis\Command\CommandInterface;
use Predis\Connection\StreamConnection;
@@ -33,10 +28,10 @@ class SimpleDebuggableConnection extends StreamConnection
private function storeDebug(CommandInterface $command, $direction)
{
$firtsArg = $command->getArgument(0);
$firtsArg = $command->getArgument(0);
$timestamp = round(microtime(true) - $this->tstart, 4);
$debug = $command->getId();
$debug = $command->getId();
$debug .= isset($firtsArg) ? " $firtsArg " : ' ';
$debug .= "$direction $this";
$debug .= " [{$timestamp}s]";
@@ -44,19 +39,19 @@ class SimpleDebuggableConnection extends StreamConnection
$this->debugBuffer[] = $debug;
}
public function writeRequest(CommandInterface $command)
public function writeCommand(CommandInterface $command)
{
parent::writeRequest($command);
parent::writeCommand($command);
$this->storeDebug($command, '->');
}
public function readResponse(CommandInterface $command)
{
$response = parent::readResponse($command);
$reply = parent::readResponse($command);
$this->storeDebug($command, '<-');
return $response;
return $reply;
}
public function getDebugBuffer()
@@ -76,17 +71,18 @@ $client->set('foo', 'bar');
$client->get('foo');
$client->info();
var_export($client->getConnection()->getDebugBuffer());
print_r($client->getConnection()->getDebugBuffer());
/* OUTPUT:
array (
0 => 'SELECT 15 -> 127.0.0.1:6379 [0.0008s]',
1 => 'SELECT 15 <- 127.0.0.1:6379 [0.001s]',
2 => 'SET foo -> 127.0.0.1:6379 [0.001s]',
3 => 'SET foo <- 127.0.0.1:6379 [0.0011s]',
4 => 'GET foo -> 127.0.0.1:6379 [0.0013s]',
5 => 'GET foo <- 127.0.0.1:6379 [0.0015s]',
6 => 'INFO -> 127.0.0.1:6379 [0.0019s]',
7 => 'INFO <- 127.0.0.1:6379 [0.0022s]',
Array
(
[0] => SELECT 15 -> 127.0.0.1:6379 [0.0008s]
[1] => SELECT 15 <- 127.0.0.1:6379 [0.0012s]
[2] => SET foo -> 127.0.0.1:6379 [0.0014s]
[3] => SET foo <- 127.0.0.1:6379 [0.0014s]
[4] => GET foo -> 127.0.0.1:6379 [0.0016s]
[5] => GET foo <- 127.0.0.1:6379 [0.0018s]
[6] => INFO -> 127.0.0.1:6379 [0.002s]
[7] => INFO <- 127.0.0.1:6379 [0.0025s]
)
*/
+25
View File
@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require 'SharedConfigurations.php';
// simple set and get scenario
$client = new Predis\Client($single_server);
$client->set('library', 'predis');
$retval = $client->get('library');
var_dump($retval);
/* OUTPUT
string(6) "predis"
*/
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
require 'SharedConfigurations.php';
// This is an implementation of an atomic client-side ZPOP using the support for
// check-and-set (CAS) operations with MULTI/EXEC transactions, as described in
@@ -19,16 +19,15 @@ require __DIR__.'/shared.php';
//
// ./redis-cli
// SELECT 15
// ZADD zset 1 a 2 b 3 c
//
// Then execute this script four times and see its output.
//
// ZADD zset 1 a
// ZADD zset 2 b
// ZADD zset 3 c
function zpop($client, $key)
{
$element = null;
$options = array(
'cas' => true, // Initialize with support for CAS operations
'cas' => true, // Initialize with support for CAS operations
'watch' => $key, // Key that needs to be WATCHed to detect changes
'retry' => 3, // Number of retries on aborted transactions, after
// which the client bails out with an exception.
@@ -49,4 +48,4 @@ function zpop($client, $key)
$client = new Predis\Client($single_server);
$zpopped = zpop($client, 'zset');
echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL;
echo isset($zpopped) ? "ZPOPed $zpopped" : "Nothing to ZPOP!", "\n";
-57
View File
@@ -1,57 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
$client = new Predis\Client($single_server);
// Plain old SET and GET example...
$client->set('library', 'predis');
$response = $client->get('library');
var_export($response); echo PHP_EOL;
/* OUTPUT: 'predis' */
// Redis has the MSET and MGET commands to set or get multiple keys in one go,
// cases like this Predis accepts arguments for variadic commands both as a list
// of arguments or an array containing all of the keys and/or values.
$mkv = array(
'uid:0001' => '1st user',
'uid:0002' => '2nd user',
'uid:0003' => '3rd user',
);
$client->mset($mkv);
$response = $client->mget(array_keys($mkv));
var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
0 => '1st user',
1 => '2nd user',
2 => '3rd user',
) */
// Predis can also send "raw" commands to Redis. The difference between sending
// commands to Redis the usual way and the "raw" way is that in the latter case
// their arguments are not filtered nor responses coming from Redis are parsed.
$response = $client->executeRaw(array(
'MGET', 'uid:0001', 'uid:0002', 'uid:0003',
));
var_export($response); echo PHP_EOL;
/* OUTPUT:
array (
0 => '1st user',
1 => '2nd user',
2 => '3rd user',
) */
-36
View File
@@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// Predis can prefix keys found in commands arguments before sending commands to
// Redis, even for complex commands such as SORT, ZUNIONSTORE and ZINTERSTORE.
// Prefixing keys can be useful to create user-level namespaces for you keyspace
// thus reducing the need for separate logical databases in certain scenarios.
$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
var_export($client->mget('foo', 'lol'));
/*
array (
0 => 'bar',
1 => 'wut',
)
*/
var_export($client->keys('*'));
/*
array (
0 => 'nrk:foo',
1 => 'nrk:lol',
)
*/
-58
View File
@@ -1,58 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// Predis supports redis-sentinel to provide high availability in master / slave
// scenarios. The only but relevant difference with a basic replication scenario
// is that sentinel servers can manage the master server and its slaves based on
// their state, which means that they are able to provide an authoritative and
// updated configuration to clients thus avoiding static configurations for the
// replication servers and their roles.
// Instead of connection parameters pointing to redis nodes, we provide a list
// of instances of redis-sentinel. Users should always provide a timeout value
// low enough to not hinder operations just in case a sentinel is unreachable
// but Predis uses a default value of 100 milliseconds for sentinel parameters
// without an explicit timeout value.
//
// NOTE: in real-world scenarios sentinels should be running on different hosts!
$sentinels = array(
'tcp://127.0.0.1:5380?timeout=0.100',
'tcp://127.0.0.1:5381?timeout=0.100',
'tcp://127.0.0.1:5382?timeout=0.100',
);
$client = new Predis\Client($sentinels, array(
'replication' => 'sentinel',
'service' => 'mymaster',
));
// Read operation.
$exists = $client->exists('foo') ? 'yes' : 'no';
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Does 'foo' exist on {$current->alias}? $exists.", PHP_EOL;
// Write operation.
$client->set('foo', 'bar');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "Now 'foo' has been set to 'bar' on {$current->alias}!", PHP_EOL;
// Read operation.
$bar = $client->get('foo');
$current = $client->getConnection()->getCurrent()->getParameters();
echo "We fetched 'foo' from {$current->alias} and its value is '$bar'.", PHP_EOL;
/* OUTPUT:
Does 'foo' exist on slave-127.0.0.1:6381? yes.
Now 'foo' has been set to 'bar' on master!
We fetched 'foo' from master and its value is 'bar'.
*/
-52
View File
@@ -1,52 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/shared.php';
// This example demonstrates how to use Predis to save PHP sessions on Redis.
//
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as
// the TTL for keys holding session data but this value can be overridden when
// creating the session handler instance using the `gc_maxlifetime` option.
//
// NOTE: this class requires PHP >= 5.4 but can be used on PHP 5.3 if a polyfill
// for SessionHandlerInterface is provided either by you or an external package
// like `symfony/http-foundation`.
//
// See http://www.php.net/class.sessionhandlerinterface.php for more details.
//
if (!interface_exists('SessionHandlerInterface')) {
die('ATTENTION: the session handler implemented by Predis requires PHP >= 5.4.0 '.
"or a polyfill for SessionHandlerInterface provided by an external package.\n");
}
// Instantiate a new client just like you would normally do. Using a prefix for
// keys will effectively prefix all session keys with the specified string.
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
// Set `gc_maxlifetime` to specify a time-to-live of 5 seconds for session keys.
$handler = new Predis\Session\Handler($client, array('gc_maxlifetime' => 5));
// Register the session handler.
$handler->register();
// We just set a fixed session ID only for the sake of our example.
session_id('example_session_id');
session_start();
if (isset($_SESSION['foo'])) {
echo "Session has `foo` set to {$_SESSION['foo']}", PHP_EOL;
} else {
$_SESSION['foo'] = $value = mt_rand();
echo "Empty session, `foo` has been set with $value", PHP_EOL;
}
-44
View File
@@ -1,44 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/../autoload.php';
function redis_version($info)
{
if (isset($info['Server']['redis_version'])) {
return $info['Server']['redis_version'];
} elseif (isset($info['redis_version'])) {
return $info['redis_version'];
} else {
return 'unknown version';
}
}
$single_server = array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
);
$multiple_servers = array(
array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
'alias' => 'first',
),
array(
'host' => '127.0.0.1',
'port' => 6380,
'database' => 15,
'alias' => 'second',
),
);
@@ -12,7 +12,7 @@
namespace Predis;
/**
* Implements a lightweight PSR-0 compliant autoloader for Predis.
* Implements a lightweight PSR-0 compliant autoloader.
*
* @author Eric Naeseth <eric@thumbtack.com>
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -29,7 +29,7 @@ class Autoloader
public function __construct($baseDirectory = __DIR__)
{
$this->directory = $baseDirectory;
$this->prefix = __NAMESPACE__.'\\';
$this->prefix = __NAMESPACE__ . '\\';
$this->prefixLength = strlen($this->prefix);
}
@@ -40,7 +40,7 @@ class Autoloader
*/
public static function register($prepend = false)
{
spl_autoload_register(array(new self(), 'autoload'), true, $prepend);
spl_autoload_register(array(new self, 'autoload'), true, $prepend);
}
/**
@@ -55,7 +55,7 @@ class Autoloader
$filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
if (is_file($filepath)) {
require $filepath;
require($filepath);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Command\CommandInterface;
/**
* Defines the interface of a basic client object or abstraction that
* can send commands to Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface BasicClientInterface
{
/**
* Executes the specified Redis command.
*
* @param CommandInterface $command A Redis command.
* @return mixed
*/
public function executeCommand(CommandInterface $command);
}
+465
View File
@@ -0,0 +1,465 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptedCommand;
use Predis\Connection\AggregatedConnectionInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Connection\ConnectionFactoryInterface;
use Predis\Monitor\MonitorContext;
use Predis\Option\ClientOptions;
use Predis\Option\ClientOptionsInterface;
use Predis\Pipeline\PipelineContext;
use Predis\PubSub\PubSubContext;
use Predis\Transaction\MultiExecContext;
/**
* Main class that exposes the most high-level interface to interact with Redis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class Client implements ClientInterface
{
const VERSION = '0.8.6';
private $options;
private $profile;
private $connection;
/**
* 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.
*/
public function __construct($parameters = null, $options = null)
{
$this->options = $this->filterOptions($options);
$this->profile = $this->options->profile;
$this->connection = $this->initializeConnection($parameters);
}
/**
* Creates an instance of Predis\Option\ClientOptions from various types of
* arguments (string, array, Predis\Profile\ServerProfile) or returns the
* passed object if it is an instance of Predis\Option\ClientOptions.
*
* @param mixed $options Client options.
* @return ClientOptions
*/
protected function filterOptions($options)
{
if (!isset($options)) {
return new ClientOptions();
}
if (is_array($options)) {
return new ClientOptions($options);
}
if ($options instanceof ClientOptionsInterface) {
return $options;
}
throw new \InvalidArgumentException("Invalid type for client options");
}
/**
* Initializes one or multiple connection (cluster) objects from various
* types of arguments (string, array) or returns the passed object if it
* implements Predis\Connection\ConnectionInterface.
*
* @param mixed $parameters Connection parameters or instance.
* @return ConnectionInterface
*/
protected function initializeConnection($parameters)
{
if ($parameters instanceof ConnectionInterface) {
return $parameters;
}
if (is_array($parameters) && isset($parameters[0])) {
$options = $this->options;
$replication = isset($options->replication) && $options->replication;
$connection = $options->{$replication ? 'replication' : 'cluster'};
return $options->connections->createAggregated($connection, $parameters);
}
if (is_callable($parameters)) {
$connection = call_user_func($parameters, $this->options);
if (!$connection instanceof ConnectionInterface) {
throw new \InvalidArgumentException(
'Callable parameters must return instances of Predis\Connection\ConnectionInterface'
);
}
return $connection;
}
return $this->options->connections->create($parameters);
}
/**
* {@inheritdoc}
*/
public function getProfile()
{
return $this->profile;
}
/**
* {@inheritdoc}
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns the connection factory object used by the client.
*
* @return ConnectionFactoryInterface
*/
public function getConnectionFactory()
{
return $this->options->connections;
}
/**
* Returns a new instance of a client for the specified connection when the
* 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)
{
if (!$connection = $this->getConnectionById($connectionID)) {
throw new \InvalidArgumentException("Invalid connection ID: '$connectionID'");
}
return new static($connection, $this->options);
}
/**
* Opens the connection to the server.
*/
public function connect()
{
$this->connection->connect();
}
/**
* Disconnects from the server.
*/
public function disconnect()
{
$this->connection->disconnect();
}
/**
* Disconnects from the server.
*
* This method is an alias of disconnect().
*/
public function quit()
{
$this->disconnect();
}
/**
* Checks if the underlying connection is connected to Redis.
*
* @return bool True means that the connection is open.
* False means that the connection is closed.
*/
public function isConnected()
{
return $this->connection->isConnected();
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->connection;
}
/**
* Retrieves a single connection out of an aggregated connections instance.
*
* @param string $connectionId Index or alias of the single connection.
* @return Connection\SingleConnectionInterface
*/
public function getConnectionById($connectionId)
{
if (!$this->connection instanceof AggregatedConnectionInterface) {
throw new NotSupportedException('Retrieving connections by ID is supported only when using aggregated connections');
}
return $this->connection->getConnectionById($connectionId);
}
/**
* Creates a Redis command with the specified arguments and sends a request
* to the server.
*
* @param string $commandID Command ID.
* @param array $arguments Arguments for the command.
* @return mixed
*/
public function __call($commandID, $arguments)
{
$command = $this->createCommand($commandID, $arguments);
$response = $this->executeCommand($command);
return $response;
}
/**
* {@inheritdoc}
*/
public function createCommand($commandID, $arguments = array())
{
return $this->profile->createCommand($commandID, $arguments);
}
/**
* {@inheritdoc}
*/
public function executeCommand(CommandInterface $command)
{
$response = $this->connection->executeCommand($command);
if ($response instanceof ResponseObjectInterface) {
if ($response instanceof ResponseErrorInterface) {
$response = $this->onResponseError($command, $response);
}
return $response;
}
return $command->parseResponse($response);
}
/**
* Handles -ERR responses returned by Redis.
*
* @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)
{
if ($command instanceof ScriptedCommand && $response->getErrorType() === 'NOSCRIPT') {
$eval = $this->createCommand('eval');
$eval->setRawArguments($command->getEvalArguments());
$response = $this->executeCommand($eval);
if (!$response instanceof ResponseObjectInterface) {
$response = $command->parseResponse($response);
}
return $response;
}
if ($this->options->exceptions) {
throw new ServerException($response->getMessage());
}
return $response;
}
/**
* Calls the specified initializer method on $this with 0, 1 or 2 arguments.
*
* TODO: Invert $argv and $initializer.
*
* @param array $argv Arguments for the initializer.
* @param string $initializer The initializer method.
* @return mixed
*/
private function sharedInitializer($argv, $initializer)
{
switch (count($argv)) {
case 0:
return $this->$initializer();
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:
return $this->$initializer($this, $argv);
}
}
/**
* Creates a new pipeline context and returns it, or returns the results of
* a pipeline executed inside the optionally provided callable object.
*
* @param mixed ... Options for the context, a callable object, or both.
* @return PipelineContext|array
*/
public function pipeline(/* arguments */)
{
return $this->sharedInitializer(func_get_args(), 'initPipeline');
}
/**
* Pipeline context initializer.
*
* @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)
{
$executor = isset($options['executor']) ? $options['executor'] : null;
if (is_callable($executor)) {
$executor = call_user_func($executor, $this, $options);
}
$pipeline = new PipelineContext($this, $executor);
$replies = $this->pipelineExecute($pipeline, $callable);
return $replies;
}
/**
* Executes a pipeline context when a callable object is passed.
*
* @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)
{
return isset($callable) ? $pipeline->execute($callable) : $pipeline;
}
/**
* Creates a new transaction context and returns it, or returns the results of
* a transaction executed inside the optionally provided callable object.
*
* @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 */)
{
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.
* @return MultiExecContext|array
*/
protected function initMultiExec(Array $options = null, $callable = null)
{
$transaction = new MultiExecContext($this, $options ?: array());
return isset($callable) ? $transaction->execute($callable) : $transaction;
}
/**
* Creates a new Publish / Subscribe context and returns it, or executes it
* inside the optionally provided callable object.
*
* @deprecated This method will change in the next major release to support
* the new PUBSUB command introduced in Redis 2.8. Please use
* Client::pubSubLoop() to create Predis\PubSub\PubSubContext
* instances from now on.
*
* @param mixed ... Options for the context, a callable object, or both.
* @return PubSubContext|array
*/
public function pubSub(/* arguments */)
{
return call_user_func_array(array($this, 'pubSubLoop'), func_get_args());
}
/**
* Creates a new Publish / Subscribe context and returns it, or executes it
* inside the optionally provided callable object.
*
* @param mixed ... Options for the context, a callable object, or both.
* @return PubSubContext|array
*/
public function pubSubLoop(/* arguments */)
{
return $this->sharedInitializer(func_get_args(), 'initPubSub');
}
/**
* Publish / Subscribe context initializer.
*
* @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)
{
$pubsub = new PubSubContext($this, $options);
if (!isset($callable)) {
return $pubsub;
}
foreach ($pubsub as $message) {
if (call_user_func($callable, $pubsub, $message) === false) {
$pubsub->closeContext();
}
}
}
/**
* Returns a new monitor context.
*
* @return MonitorContext
*/
public function monitor()
{
return new MonitorContext($this);
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis;
use Predis\Connection\ConnectionInterface;
use Predis\Option\ClientOptionsInterface;
use Predis\Profile\ServerProfileInterface;
/**
* Interface defining the most important parts needed to create an
* high-level Redis client object that can interact with other
* building blocks of Predis.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface ClientInterface extends BasicClientInterface
{
/**
* Returns the server profile used by the client.
*
* @return ServerProfileInterface
*/
public function getProfile();
/**
* Returns the client options specified upon initialization.
*
* @return ClientOptionsInterface
*/
public function getOptions();
/**
* Opens the connection to the server.
*/
public function connect();
/**
* Disconnects from the server.
*/
public function disconnect();
/**
* Returns the underlying connection instance.
*
* @return ConnectionInterface
*/
public function getConnection();
/**
* Creates a new instance of the specified Redis command.
*
* @param string $method The name of a Redis command.
* @param array $arguments The arguments for the command.
* @return Command\CommandInterface
*/
public function createCommand($method, $arguments = array());
}
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Command\CommandInterface;
/**
* Interface for classes defining the strategy used to calculate an hash
* out of keys extracted from supported commands.
*
* This is mostly useful to support clustering via client-side sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface CommandHashStrategyInterface
{
/**
* Returns the hash for the given command using the specified algorithm, or null
* if the command cannot be hashed.
*
* @param CommandInterface $command Command to be hashed.
* @return int
*/
public function getHash(CommandInterface $command);
/**
* Returns the hash for the given key using the specified algorithm.
*
* @param string $key Key to be hashed.
* @return string
*/
public function getKeyHash($key);
}
@@ -9,17 +9,17 @@
* file that was distributed with this source code.
*/
namespace Predis\Cluster\Distributor;
namespace Predis\Cluster\Distribution;
use Predis\Cluster\Hash\HashGeneratorInterface;
/**
* A distributor implements the logic to automatically distribute keys among
* several nodes for client-side sharding.
* A distributor implements the logic to automatically distribute
* keys among several nodes for client-side sharding.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface DistributorInterface
interface DistributionStrategyInterface
{
/**
* Adds a node to the distributor with an optional weight.
@@ -37,41 +37,12 @@ interface DistributorInterface
public function remove($node);
/**
* Returns the corresponding slot of a node from the distributor using the
* computed hash of a key.
*
* @param mixed $hash
* Gets a node from the distributor using the computed hash of a key.
*
* @param mixed $key
* @return mixed
*/
public function getSlot($hash);
/**
* Returns a node from the distributor using its assigned slot ID.
*
* @param mixed $slot
*
* @return mixed|null
*/
public function getBySlot($slot);
/**
* Returns a node from the distributor using the computed hash of a key.
*
* @param mixed $hash
*
* @return mixed
*/
public function getByHash($hash);
/**
* Returns a node from the distributor mapping to the specified value.
*
* @param string $value
*
* @return mixed
*/
public function get($value);
public function get($key);
/**
* Returns the underlying hash generator instance.
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Cluster\Distributor;
namespace Predis\Cluster\Distribution;
/**
* Exception class that identifies empty rings.
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Cluster\Distributor;
namespace Predis\Cluster\Distribution;
use Predis\Cluster\Hash\HashGeneratorInterface;
@@ -21,10 +21,10 @@ use Predis\Cluster\Hash\HashGeneratorInterface;
* @author Daniele Alessandri <suppakilla@gmail.com>
* @author Lorenzo Castelli <lcastelli@gmail.com>
*/
class HashRing implements DistributorInterface, HashGeneratorInterface
class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
{
const DEFAULT_REPLICAS = 128;
const DEFAULT_WEIGHT = 100;
const DEFAULT_WEIGHT = 100;
private $ring;
private $ringKeys;
@@ -35,7 +35,7 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
/**
* @param int $replicas Number of replicas in the ring.
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
*/
public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
{
@@ -53,11 +53,7 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
{
// In case of collisions in the hashes of the nodes, the node added
// last wins, thus the order in which nodes are added is significant.
$this->nodes[] = array(
'object' => $node,
'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT,
);
$this->nodes[] = array('object' => $node, 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT);
$this->reset();
}
@@ -74,7 +70,6 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
if ($this->nodes[$i]['object'] === $node) {
array_splice($this->nodes, $i, 1);
$this->reset();
break;
}
}
@@ -128,7 +123,7 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
}
if (!$this->nodes) {
throw new EmptyRingException('Cannot initialize an empty hashring.');
throw new EmptyRingException('Cannot initialize empty hashring');
}
$this->ring = array();
@@ -160,7 +155,7 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
$nodeHash = $this->getNodeHash($nodeObject);
$replicas = (int) round($weightRatio * $totalNodes * $replicas);
for ($i = 0; $i < $replicas; ++$i) {
for ($i = 0; $i < $replicas; $i++) {
$key = crc32("$nodeHash:$i");
$ring[$key] = $nodeObject;
}
@@ -171,7 +166,7 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
*/
protected function getNodeHash($nodeObject)
{
if (!isset($this->nodeHashCallback)) {
if ($this->nodeHashCallback === null) {
return (string) $nodeObject;
}
@@ -179,7 +174,10 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
}
/**
* {@inheritdoc}
* Calculates the hash for the specified value.
*
* @param string $value Input value.
* @return int
*/
public function hash($value)
{
@@ -189,41 +187,31 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
/**
* {@inheritdoc}
*/
public function getByHash($hash)
public function get($key)
{
return $this->ring[$this->getSlot($hash)];
return $this->ring[$this->getNodeKey($key)];
}
/**
* {@inheritdoc}
* Calculates the corrisponding key of a node distributed in the hashring.
*
* @param int $key Computed hash of a key.
* @return int
*/
public function getBySlot($slot)
private function getNodeKey($key)
{
$this->initialize();
if (isset($this->ring[$slot])) {
return $this->ring[$slot];
}
}
/**
* {@inheritdoc}
*/
public function getSlot($hash)
{
$this->initialize();
$ringKeys = $this->ringKeys;
$upper = $this->ringKeysCount - 1;
$lower = 0;
while ($lower <= $upper) {
$index = ($lower + $upper) >> 1;
$item = $ringKeys[$index];
$item = $ringKeys[$index];
if ($item > $hash) {
if ($item > $key) {
$upper = $index - 1;
} elseif ($item < $hash) {
} elseif ($item < $key) {
$lower = $index + 1;
} else {
return $item;
@@ -233,24 +221,12 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
}
/**
* {@inheritdoc}
*/
public function get($value)
{
$hash = $this->hash($value);
$node = $this->getByHash($hash);
return $node;
}
/**
* 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)
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Cluster\Distributor;
namespace Predis\Cluster\Distribution;
/**
* This class implements an hashring-based distributor that uses the same
@@ -19,12 +19,12 @@ namespace Predis\Cluster\Distributor;
* @author Daniele Alessandri <suppakilla@gmail.com>
* @author Lorenzo Castelli <lcastelli@gmail.com>
*/
class KetamaRing extends HashRing
class KetamaPureRing extends HashRing
{
const DEFAULT_REPLICAS = 160;
/**
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
*/
public function __construct($nodeHashCallback = null)
{
@@ -40,7 +40,7 @@ class KetamaRing extends HashRing
$nodeHash = $this->getNodeHash($nodeObject);
$replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
for ($i = 0; $i < $replicas; ++$i) {
for ($i = 0; $i < $replicas; $i++) {
$unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
foreach ($unpackedDigest as $key) {
@@ -64,7 +64,7 @@ class KetamaRing extends HashRing
*/
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
{
// Binary search for the first item in ringkeys with a value greater
// Binary search for the first item in _ringkeys with a value greater
// or equal to the key. If no such item exists, return the first item.
return $lower < $ringKeysCount ? $lower : 0;
}
@@ -12,11 +12,11 @@
namespace Predis\Cluster\Hash;
/**
* Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
* This class implements the CRC-CCITT-16 algorithm used by redis-cluster.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class CRC16 implements HashGeneratorInterface
class CRC16HashGenerator implements HashGeneratorInterface
{
private static $CCITT_16 = array(
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
@@ -63,7 +63,7 @@ class CRC16 implements HashGeneratorInterface
$CCITT_16 = self::$CCITT_16;
$strlen = strlen($value);
for ($i = 0; $i < $strlen; ++$i) {
for ($i = 0; $i < $strlen; $i++) {
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
}
@@ -12,18 +12,17 @@
namespace Predis\Cluster\Hash;
/**
* An hash generator implements the logic used to calculate the hash of a key to
* distribute operations among Redis nodes.
* A generator of node keys implements the logic used to calculate the hash of
* a key to distribute the respective operations among nodes.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface HashGeneratorInterface
{
/**
* Generates an hash from a string to be used for distribution.
*
* @param string $value String value.
* Generates an hash that is used by the distributor algorithm
*
* @param string $value Value used to generate the hash.
* @return int
*/
public function hash($value);
@@ -0,0 +1,398 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Cluster\Hash\HashGeneratorInterface;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptedCommand;
/**
* Default class used by Predis for client-side sharding to calculate
* hashes out of keys of supported commands.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class PredisClusterHashStrategy implements CommandHashStrategyInterface
{
private $commands;
private $hashGenerator;
/**
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
*/
public function __construct(HashGeneratorInterface $hashGenerator)
{
$this->commands = $this->getDefaultCommands();
$this->hashGenerator = $hashGenerator;
}
/**
* Returns the default map of supported commands with their handlers.
*
* @return array
*/
protected function getDefaultCommands()
{
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
return array(
/* commands operating on the key space */
'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' => $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' => $getKeyFromFirstArgument,
'SETRANGE' => $getKeyFromFirstArgument,
'STRLEN' => $getKeyFromFirstArgument,
'SUBSTR' => $getKeyFromFirstArgument,
'BITOP' => array($this, 'getKeyFromBitOp'),
'BITCOUNT' => $getKeyFromFirstArgument,
/* commands operating on lists */
'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' => $getKeyFromFirstArgument,
'LPUSHX' => $getKeyFromFirstArgument,
'RPUSH' => $getKeyFromFirstArgument,
'RPUSHX' => $getKeyFromFirstArgument,
'LRANGE' => $getKeyFromFirstArgument,
'LREM' => $getKeyFromFirstArgument,
'LSET' => $getKeyFromFirstArgument,
'LTRIM' => $getKeyFromFirstArgument,
/* commands operating on sets */
'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' => $getKeyFromFirstArgument,
'ZCARD' => $getKeyFromFirstArgument,
'ZCOUNT' => $getKeyFromFirstArgument,
'ZINCRBY' => $getKeyFromFirstArgument,
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'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' => $getKeyFromFirstArgument,
'ZLEXCOUNT' => $getKeyFromFirstArgument,
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
/* commands operating on hashes */
'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'),
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
);
}
/**
* Returns the list of IDs for the supported commands.
*
* @return array
*/
public function getSupportedCommands()
{
return array_keys($this->commands);
}
/**
* Sets an handler for the specified command ID.
*
* The signature of the callback must have a single parameter
* of type Predis\Command\CommandInterface.
*
* When the callback argument is omitted or NULL, the previously
* associated handler for the specified command ID is removed.
*
* @param string $commandId The ID of the command to be handled.
* @param mixed $callback A valid callable object or NULL.
*/
public function setCommandHandler($commandId, $callback = null)
{
$commandId = strtoupper($commandId);
if (!isset($callback)) {
unset($this->commands[$commandId]);
return;
}
if (!is_callable($callback)) {
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
}
$this->commands[$commandId] = $callback;
}
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
{
return $command->getArgument(0);
}
/**
* Extracts the key from a command with multiple keys only when all keys
* in the arguments array produce the same hash.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromAllArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameHashForKeys($arguments)) {
return $arguments[0];
}
}
/**
* Extracts the key from a command with multiple keys only when all keys
* in the arguments array produce the same hash.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
$keys = array();
for ($i = 0; $i < count($arguments); $i += 2) {
$keys[] = $arguments[$i];
}
if ($this->checkSameHashForKeys($keys)) {
return $arguments[0];
}
}
/**
* Extracts the key from BLPOP and BRPOP commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameHashForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
return $arguments[0];
}
}
/**
* Extracts the key from BITOP command.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBitOp(CommandInterface $command)
{
$arguments = $command->getArguments();
if ($this->checkSameHashForKeys(array_slice($arguments, 1, count($arguments)))) {
return $arguments[1];
}
}
/**
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
$keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
if ($this->checkSameHashForKeys($keys)) {
return $arguments[0];
}
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
if ($command instanceof ScriptedCommand) {
$keys = $command->getKeys();
} else {
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
}
if ($keys && $this->checkSameHashForKeys($keys)) {
return $keys[0];
}
}
/**
* {@inheritdoc}
*/
public function getHash(CommandInterface $command)
{
$hash = $command->getHash();
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
$key = call_user_func($this->commands[$cmdID], $command);
if (isset($key)) {
$hash = $this->getKeyHash($key);
$command->setHash($hash);
}
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function getKeyHash($key)
{
$key = $this->extractKeyTag($key);
$hash = $this->hashGenerator->hash($key);
return $hash;
}
/**
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys Array of keys.
* @return bool
*/
protected function checkSameHashForKeys(Array $keys)
{
if (!$count = count($keys)) {
return false;
}
$currentKey = $this->extractKeyTag($keys[0]);
for ($i = 1; $i < $count; $i++) {
$nextKey = $this->extractKeyTag($keys[$i]);
if ($currentKey !== $nextKey) {
return false;
}
$currentKey = $nextKey;
}
return true;
}
/**
* Returns only the hashable part of a key (delimited by "{...}"), or the
* whole key if a key tag is not found in the string.
*
* @param string $key A key.
* @return string
*/
protected function extractKeyTag($key)
{
if (false !== $start = strpos($key, '{')) {
if (false !== $end = strpos($key, '}', $start)) {
$key = substr($key, ++$start, $end - $start);
}
}
return $key;
}
}
@@ -0,0 +1,323 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Cluster\Hash\CRC16HashGenerator;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptedCommand;
/**
* Default class used by Predis to calculate hashes out of keys of
* commands supported by redis-cluster.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RedisClusterHashStrategy implements CommandHashStrategyInterface
{
private $commands;
private $hashGenerator;
/**
*
*/
public function __construct()
{
$this->commands = $this->getDefaultCommands();
$this->hashGenerator = new CRC16HashGenerator();
}
/**
* Returns the default map of supported commands with their handlers.
*
* @return array
*/
protected function getDefaultCommands()
{
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
return array(
/* commands operating on the key space */
'EXISTS' => $getKeyFromFirstArgument,
'DEL' => $getKeyFromAllArguments,
'TYPE' => $getKeyFromFirstArgument,
'EXPIRE' => $getKeyFromFirstArgument,
'EXPIREAT' => $getKeyFromFirstArgument,
'PERSIST' => $getKeyFromFirstArgument,
'PEXPIRE' => $getKeyFromFirstArgument,
'PEXPIREAT' => $getKeyFromFirstArgument,
'TTL' => $getKeyFromFirstArgument,
'PTTL' => $getKeyFromFirstArgument,
'SORT' => $getKeyFromFirstArgument, // TODO
/* commands operating on string values */
'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' => $getKeyFromFirstArgument,
'SETRANGE' => $getKeyFromFirstArgument,
'STRLEN' => $getKeyFromFirstArgument,
'SUBSTR' => $getKeyFromFirstArgument,
'BITCOUNT' => $getKeyFromFirstArgument,
/* commands operating on lists */
'LINSERT' => $getKeyFromFirstArgument,
'LINDEX' => $getKeyFromFirstArgument,
'LLEN' => $getKeyFromFirstArgument,
'LPOP' => $getKeyFromFirstArgument,
'RPOP' => $getKeyFromFirstArgument,
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
'LPUSH' => $getKeyFromFirstArgument,
'LPUSHX' => $getKeyFromFirstArgument,
'RPUSH' => $getKeyFromFirstArgument,
'RPUSHX' => $getKeyFromFirstArgument,
'LRANGE' => $getKeyFromFirstArgument,
'LREM' => $getKeyFromFirstArgument,
'LSET' => $getKeyFromFirstArgument,
'LTRIM' => $getKeyFromFirstArgument,
/* commands operating on sets */
'SADD' => $getKeyFromFirstArgument,
'SCARD' => $getKeyFromFirstArgument,
'SISMEMBER' => $getKeyFromFirstArgument,
'SMEMBERS' => $getKeyFromFirstArgument,
'SSCAN' => $getKeyFromFirstArgument,
'SPOP' => $getKeyFromFirstArgument,
'SRANDMEMBER' => $getKeyFromFirstArgument,
'SREM' => $getKeyFromFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $getKeyFromFirstArgument,
'ZCARD' => $getKeyFromFirstArgument,
'ZCOUNT' => $getKeyFromFirstArgument,
'ZINCRBY' => $getKeyFromFirstArgument,
'ZRANGE' => $getKeyFromFirstArgument,
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZRANK' => $getKeyFromFirstArgument,
'ZREM' => $getKeyFromFirstArgument,
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANGE' => $getKeyFromFirstArgument,
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANK' => $getKeyFromFirstArgument,
'ZSCORE' => $getKeyFromFirstArgument,
'ZSCAN' => $getKeyFromFirstArgument,
'ZLEXCOUNT' => $getKeyFromFirstArgument,
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
/* commands operating on hashes */
'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'),
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
);
}
/**
* Returns the list of IDs for the supported commands.
*
* @return array
*/
public function getSupportedCommands()
{
return array_keys($this->commands);
}
/**
* Sets an handler for the specified command ID.
*
* The signature of the callback must have a single parameter
* of type Predis\Command\CommandInterface.
*
* When the callback argument is omitted or NULL, the previously
* associated handler for the specified command ID is removed.
*
* @param string $commandId The ID of the command to be handled.
* @param mixed $callback A valid callable object or NULL.
*/
public function setCommandHandler($commandId, $callback = null)
{
$commandId = strtoupper($commandId);
if (!isset($callback)) {
unset($this->commands[$commandId]);
return;
}
if (!is_callable($callback)) {
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
}
$this->commands[$commandId] = $callback;
}
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
{
return $command->getArgument(0);
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromAllArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 1) {
return $arguments[0];
}
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from BLPOP and BRPOP commands ensuring that only one key
* is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
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);
}
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function getKeyHash($key)
{
$key = $this->extractKeyTag($key);
$hash = $this->hashGenerator->hash($key);
return $hash;
}
/**
* Returns only the hashable part of a key (delimited by "{...}"), or the
* whole key if a key tag is not found in the string.
*
* @param string $key A key.
* @return string
*/
protected function extractKeyTag($key)
{
if (false !== $start = strpos($key, '{')) {
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
$key = substr($key, $start, $end - $start);
}
}
return $key;
}
}
@@ -11,23 +11,24 @@
namespace Predis\Collection\Iterator;
use Iterator;
use Predis\ClientInterface;
use Predis\NotSupportedException;
/**
* Provides the base implementation for a fully-rewindable PHP iterator that can
* incrementally iterate over cursor-based collections stored on Redis using the
* commands in the `SCAN` family.
* Provides the base implementation for a fully-rewindable PHP iterator
* that can incrementally iterate over cursor-based collections stored
* on Redis using commands in the `SCAN` family.
*
* Given their incremental nature with multiple fetches, these kind of iterators
* offer limited guarantees about the returned elements because the collection
* can change several times during the iteration process.
* Given their incremental nature with multiple fetches, these kind of
* iterators offer limited guarantees about the returned elements because
* the collection can change several times during the iteration process.
*
* @see http://redis.io/commands/scan
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class CursorBasedIterator implements \Iterator
abstract class CursorBasedIterator implements Iterator
{
protected $client;
protected $match;
@@ -43,7 +44,7 @@ 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 Hint used by Redis to compute the number of results per 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)
{
@@ -55,18 +56,17 @@ abstract class CursorBasedIterator implements \Iterator
}
/**
* Ensures that the client supports the specified Redis command required to
* fetch elements from the server to perform the iteration.
* Ensures that the client instance supports the specified Redis
* command required to fetch elements from the server to perform
* the iteration.
*
* @param ClientInterface $client Client connected to Redis.
* @param string $commandID Command ID.
*
* @throws NotSupportedException
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
if (!$client->getProfile()->supportsCommand($commandID)) {
throw new NotSupportedException("The current profile does not support '$commandID'.");
throw new NotSupportedException("The specified server profile does not support the `$commandID` command.");
}
}
@@ -104,16 +104,16 @@ abstract class CursorBasedIterator implements \Iterator
}
/**
* Fetches a new set of elements from the remote collection, effectively
* advancing the iteration process.
* Fetches a new set of elements from the remote collection,
* effectively advancing the iteration process.
*
* @return array
*/
abstract protected function executeCommand();
/**
* Populates the local buffer of elements fetched from the server during
* the iteration.
* Populates the local buffer of elements fetched from the
* server during the iteration.
*/
protected function fetch()
{
@@ -132,7 +132,7 @@ abstract class CursorBasedIterator implements \Iterator
*/
protected function extractNext()
{
++$this->position;
$this->position++;
$this->current = array_shift($this->elements);
}
@@ -14,11 +14,11 @@ namespace Predis\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of fields and values of an hash by leveraging the
* HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
* Abstracts the iteration of fields and values of an hash
* by leveraging the HSCAN command (Redis >= 2.8) wrapped
* in a fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class HashKey extends CursorBasedIterator
@@ -50,11 +50,7 @@ class HashKey extends CursorBasedIterator
*/
protected function extractNext()
{
if ($kv = each($this->elements)) {
$this->position = $kv[0];
$this->current = $kv[1];
unset($this->elements[$this->position]);
}
$this->position = key($this->elements);
$this->current = array_shift($this->elements);
}
}
@@ -14,11 +14,11 @@ namespace Predis\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of the keyspace on a Redis instance by leveraging the
* SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
* Abstracts the iteration of the keyspace on a Redis instance
* by leveraging the SCAN command (Redis >= 2.8) wrapped in a
* fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class Keyspace extends CursorBasedIterator
@@ -11,6 +11,8 @@
namespace Predis\Collection\Iterator;
use InvalidArgumentException;
use Iterator;
use Predis\ClientInterface;
use Predis\NotSupportedException;
@@ -25,10 +27,9 @@ use Predis\NotSupportedException;
* times (trimmed, deleted, overwritten) during the iteration process.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/lrange
*/
class ListKey implements \Iterator
class ListKey implements Iterator
{
protected $client;
protected $count;
@@ -44,15 +45,13 @@ 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.
*
* @throws \InvalidArgumentException
*/
public function __construct(ClientInterface $client, $key, $count = 10)
{
$this->requiredCommand($client, 'LRANGE');
if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
throw new \InvalidArgumentException('The $count argument must be a positive integer.');
throw new InvalidArgumentException('The $count argument must be a positive integer.');
}
$this->client = $client;
@@ -63,18 +62,17 @@ class ListKey implements \Iterator
}
/**
* Ensures that the client instance supports the specified Redis command
* required to fetch elements from the server to perform the iteration.
* Ensures that the client instance supports the specified Redis
* command required to fetch elements from the server to perform
* the iteration.
*
* @param ClientInterface $client Client connected to Redis.
* @param string $commandID Command ID.
*
* @throws NotSupportedException
*/
protected function requiredCommand(ClientInterface $client, $commandID)
{
if (!$client->getProfile()->supportsCommand($commandID)) {
throw new NotSupportedException("The current profile does not support '$commandID'.");
throw new NotSupportedException("The specified server profile does not support the `$commandID` command.");
}
}
@@ -91,8 +89,8 @@ class ListKey implements \Iterator
}
/**
* Fetches a new set of elements from the remote collection, effectively
* advancing the iteration process.
* Fetches a new set of elements from the remote collection,
* effectively advancing the iteration process.
*
* @return array
*/
@@ -102,8 +100,8 @@ class ListKey implements \Iterator
}
/**
* Populates the local buffer of elements fetched from the server during the
* iteration.
* Populates the local buffer of elements fetched from the
* server during the iteration.
*/
protected function fetch()
{
@@ -121,7 +119,7 @@ class ListKey implements \Iterator
*/
protected function extractNext()
{
++$this->position;
$this->position++;
$this->current = array_shift($this->elements);
}
@@ -14,11 +14,11 @@ namespace Predis\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of members stored in a set by leveraging the SSCAN
* command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
* Abstracts the iteration of members stored in a set by
* leveraging the SSCAN command (Redis >= 2.8) wrapped in
* a fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class SetKey extends CursorBasedIterator
@@ -14,11 +14,11 @@ namespace Predis\Collection\Iterator;
use Predis\ClientInterface;
/**
* Abstracts the iteration of members stored in a sorted set by leveraging the
* ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
* Abstracts the iteration of members stored in a sorted set
* by leveraging the ZSCAN command (Redis >= 2.8) wrapped in
* a fully-rewindable PHP iterator.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*
* @link http://redis.io/commands/scan
*/
class SortedSetKey extends CursorBasedIterator
@@ -50,11 +50,9 @@ class SortedSetKey extends CursorBasedIterator
*/
protected function extractNext()
{
if ($kv = each($this->elements)) {
$this->position = $kv[0];
$this->current = $kv[1];
$element = array_shift($this->elements);
unset($this->elements[$this->position]);
}
$this->position = $element[0];
$this->current = $element[1];
}
}
@@ -16,19 +16,18 @@ namespace Predis\Command;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
abstract class Command implements CommandInterface
abstract class AbstractCommand implements CommandInterface
{
private $slot;
private $hash;
private $arguments = array();
/**
* 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)
protected function filterArguments(Array $arguments)
{
return $arguments;
}
@@ -36,19 +35,19 @@ abstract class Command implements CommandInterface
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
public function setArguments(Array $arguments)
{
$this->arguments = $this->filterArguments($arguments);
unset($this->slot);
unset($this->hash);
}
/**
* {@inheritdoc}
*/
public function setRawArguments(array $arguments)
public function setRawArguments(Array $arguments)
{
$this->arguments = $arguments;
unset($this->slot);
unset($this->hash);
}
/**
@@ -72,18 +71,18 @@ abstract class Command implements CommandInterface
/**
* {@inheritdoc}
*/
public function setSlot($slot)
public function setHash($hash)
{
$this->slot = $slot;
$this->hash = $hash;
}
/**
* {@inheritdoc}
*/
public function getSlot()
public function getHash()
{
if (isset($this->slot)) {
return $this->slot;
if (isset($this->hash)) {
return $this->hash;
}
}
@@ -95,14 +94,45 @@ abstract class Command implements CommandInterface
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()
);
}
/**
* 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)
public static function normalizeArguments(Array $arguments)
{
if (count($arguments) === 1 && is_array($arguments[0])) {
return $arguments[0];
@@ -114,11 +144,10 @@ abstract class Command 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)
public static function normalizeVariadic(Array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
return array_merge(array($arguments[0]), $arguments[1]);
@@ -13,46 +13,44 @@ namespace Predis\Command;
/**
* Defines an abstraction representing a Redis command.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
interface CommandInterface
{
/**
* Returns the ID of the Redis command. By convention, command identifiers
* must always be uppercase.
* Gets the ID of a Redis command.
*
* @return string
*/
public function getId();
/**
* Assign the specified slot to the command for clustering distribution.
* Set the hash for the command.
*
* @param int $slot Slot ID.
* @param int $hash Calculated hash.
*/
public function setSlot($slot);
public function setHash($hash);
/**
* Returns the assigned slot of the command for clustering distribution.
* Returns the hash of the command.
*
* @return int|null
* @return int
*/
public function getSlot();
public function getHash();
/**
* Sets the arguments for the command.
*
* @param array $arguments List of arguments.
*/
public function setArguments(array $arguments);
public function setArguments(Array $arguments);
/**
* Sets the raw arguments for the command without processing them.
*
* @param array $arguments List of arguments.
*/
public function setRawArguments(array $arguments);
public function setRawArguments(Array $arguments);
/**
* Gets the arguments of the command.
@@ -64,17 +62,15 @@ interface CommandInterface
/**
* Gets the argument of the command at the specified index.
*
* @param int $index Index of the desired argument.
*
* @return mixed|null
* @param int $index Index of the desired argument.
* @return mixed
*/
public function getArgument($index);
/**
* Parses a raw response and returns a PHP object.
*
* @param string $data Binary string containing the whole response.
* Parses a reply buffer and returns a PHP object.
*
* @param string $data Binary string containing the whole reply.
* @return mixed
*/
public function parseResponse($data);
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/auth
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionAuth extends Command
class ConnectionAuth extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/echo
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionEcho extends Command
class ConnectionEcho extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/ping
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionPing extends Command
class ConnectionPing extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class ConnectionPing extends Command
{
return 'PING';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return $data === 'PONG' ? true : false;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/quit
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionQuit extends Command
class ConnectionQuit extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/select
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ConnectionSelect extends Command
class ConnectionSelect extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hdel
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashDelete extends Command
class HashDelete extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -29,7 +28,7 @@ class HashDelete extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
protected function filterArguments(Array $arguments)
{
return self::normalizeVariadic($arguments);
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hexists
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashExists extends Command
class HashExists extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class HashExists extends Command
{
return 'HEXISTS';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hget
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGet extends Command
class HashGet extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hgetall
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGetAll extends Command
class HashGetAll extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -33,7 +32,7 @@ class HashGetAll extends Command
{
$result = array();
for ($i = 0; $i < count($data); ++$i) {
for ($i = 0; $i < count($data); $i++) {
$result[$data[$i]] = $data[++$i];
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hmget
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashGetMultiple extends Command
class HashGetMultiple extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -29,7 +28,7 @@ class HashGetMultiple extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
protected function filterArguments(Array $arguments)
{
return self::normalizeVariadic($arguments);
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hincrby
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashIncrementBy extends Command
class HashIncrementBy extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hincrbyfloat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashIncrementByFloat extends Command
class HashIncrementByFloat extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hkeys
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashKeys extends Command
class HashKeys extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hlen
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashLength extends Command
class HashLength extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hscan
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashScan extends Command
class HashScan extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -29,7 +28,7 @@ class HashScan extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
protected function filterArguments(Array $arguments)
{
if (count($arguments) === 3 && is_array($arguments[2])) {
$options = $this->prepareOptions(array_pop($arguments));
@@ -42,8 +41,7 @@ class HashScan extends Command
/**
* 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)
@@ -70,10 +68,11 @@ class HashScan extends Command
public function parseResponse($data)
{
if (is_array($data)) {
$data[0] = (int) $data[0];
$fields = $data[1];
$result = array();
for ($i = 0; $i < count($fields); ++$i) {
for ($i = 0; $i < count($fields); $i++) {
$result[$fields[$i]] = $fields[++$i];
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hset
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSet extends Command
class HashSet extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class HashSet extends Command
{
return 'HSET';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hmset
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSetMultiple extends Command
class HashSetMultiple extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -29,7 +28,7 @@ class HashSetMultiple extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
protected function filterArguments(Array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$flattenedKVs = array($arguments[0]);
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hsetnx
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashSetPreserve extends Command
class HashSetPreserve extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class HashSetPreserve extends Command
{
return 'HSETNX';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/hvals
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HashValues extends Command
class HashValues extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/pfadd
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogAdd extends Command
class HyperLogLogAdd extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -33,4 +32,12 @@ class HyperLogLogAdd extends Command
{
return self::normalizeVariadic($arguments);
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/pfcount
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogCount extends Command
class HyperLogLogCount extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -33,4 +32,12 @@ class HyperLogLogCount extends Command
{
return self::normalizeArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/pfmerge
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogMerge extends Command
class HyperLogLogMerge extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -33,4 +32,12 @@ class HyperLogLogMerge extends Command
{
return self::normalizeArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/del
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDelete extends Command
class KeyDelete extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -29,8 +28,16 @@ class KeyDelete extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
protected function filterArguments(Array $arguments)
{
return self::normalizeArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/dump
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyDump extends Command
class KeyDump extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/exists
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExists extends Command
class KeyExists extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class KeyExists extends Command
{
return 'EXISTS';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/expire
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExpire extends Command
class KeyExpire extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class KeyExpire extends Command
{
return 'EXPIRE';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/expireat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyExpireAt extends Command
class KeyExpireAt extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class KeyExpireAt extends Command
{
return 'EXPIREAT';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/keys
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyKeys extends Command
class KeyKeys extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -12,17 +12,17 @@
namespace Predis\Command;
/**
* @link http://redis.io/commands/geodist
*
* @link http://redis.io/commands/keys
* @author Daniele Alessandri <suppakilla@gmail.com>
* @deprecated
*/
class GeospatialGeoDist extends Command
class KeyKeysV12x extends KeyKeys
{
/**
* {@inheritdoc}
*/
public function getId()
public function parseResponse($data)
{
return 'GEODIST';
return explode(' ', $data);
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/move
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyMove extends Command
class KeyMove extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class KeyMove extends Command
{
return 'MOVE';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/persist
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPersist extends Command
class KeyPersist extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class KeyPersist extends Command
{
return 'PERSIST';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,7 +13,6 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/pexpire
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPreciseExpire extends KeyExpire
@@ -13,7 +13,6 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/pexpireat
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPreciseExpireAt extends KeyExpireAt
@@ -13,7 +13,6 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/pttl
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyPreciseTimeToLive extends KeyTimeToLive
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/randomkey
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRandom extends Command
class KeyRandom extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/rename
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRename extends Command
class KeyRename extends AbstractCommand implements PrefixableCommandInterface
{
/**
* {@inheritdoc}
@@ -25,4 +24,12 @@ class KeyRename extends Command
{
return 'RENAME';
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
@@ -13,7 +13,6 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/renamenx
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRenamePreserve extends KeyRename
@@ -25,4 +24,12 @@ class KeyRenamePreserve extends KeyRename
{
return 'RENAMENX';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/restore
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyRestore extends Command
class KeyRestore extends PrefixableCommand
{
/**
* {@inheritdoc}
@@ -13,10 +13,9 @@ namespace Predis\Command;
/**
* @link http://redis.io/commands/scan
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class KeyScan extends Command
class KeyScan extends AbstractCommand
{
/**
* {@inheritdoc}
@@ -29,7 +28,7 @@ class KeyScan extends Command
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
protected function filterArguments(Array $arguments)
{
if (count($arguments) === 2 && is_array($arguments[1])) {
$options = $this->prepareOptions(array_pop($arguments));
@@ -42,8 +41,7 @@ class KeyScan extends Command
/**
* 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)
@@ -63,4 +61,16 @@ class KeyScan extends Command
return $normalized;
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
if (is_array($data)) {
$data[0] = (int) $data[0];
}
return $data;
}
}

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