Compare commits

...

73 Commits

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

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

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

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

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

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

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

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

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

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

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

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

Future improvements (currently not needed) include:

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

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

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

Meh
2014-05-30 11:41:20 +02:00
Daniele Alessandri c2ae1c612e Merge remote-tracking branch 'github/pr/159' into v0.8 2014-05-28 12:58:03 +02:00
Daniele Alessandri a85f89e2c7 Merge remote-tracking branch 'github/pr/170' into v0.8 2014-05-28 12:48:53 +02:00
thbourlove dbb802f0f1 use phpunit 4.x instead of 3.x 2014-05-26 22:57:40 +08:00
thbourlove 64b79511bd isntall requirement with a better way 2014-05-21 17:43:32 +08:00
thbourlove 5a9e747b01 add phpunit as dev requirement 2014-05-21 17:21:16 +08:00
Alexey Kupershtokh cb0fbd01b4 Enable INCRBYFLOAT in cluster mode 2014-03-28 22:12:43 +07:00
Daniele Alessandri 83df2ef11c Update CHANGELOG and README. 2014-03-25 15:02:22 +01:00
Daniele Alessandri c94e5d3a10 Merge remote-tracking branch 'thbourlove/v0.8' into v0.8 2014-03-25 14:51:22 +01:00
Daniele Alessandri c90fbfc0ff Failures for PHP 5.6 on Travis CI are not allowed anymore. 2014-03-25 14:31:04 +01:00
Daniele Alessandri 21d6f0e7c6 Update CHANGELOG. 2014-03-25 14:29:05 +01:00
Daniele Alessandri f4a950b275 Merge remote-tracking branch 'thbourlove/server_info' into v0.8 2014-03-25 14:26:10 +01:00
thbourlove 5ec51c899f original array format 2014-03-15 01:16:38 +08:00
thbourlove e1d700dd0c fix #154 2014-03-15 00:59:55 +08:00
Daniele Alessandri 809f76a7b3 Fix implementation for hash tags extraction from keys.
We now fully comply with the specifications defined by Redis.
2014-02-11 19:10:09 +01:00
Daniele Alessandri ab6cbe7e10 Add PHP 5.6 to the build matix of Travis-CI.
Builds are allowed to fail for now as PHP 5.6 is still in alpha stage.
2014-02-11 16:34:40 +01:00
Daniele Alessandri 80bcb9f475 Add HHVM to the build matrix of Travis-CI for v0.8.
Travis-CI recently provisioned virtual machines with HHVM 2.4.0 which
fixed the issues preventing Predis v0.8 from working correctly.
2014-02-11 16:29:44 +01:00
Daniele Alessandri c8e671a42a [HHVM] Work around a bug in class_exists() with non-string values. 2014-02-11 16:24:09 +01:00
Daniele Alessandri 838e6af403 [tests] Adapt for testing with HHVM. 2014-02-11 15:58:38 +01:00
Daniele Alessandri ba2ffb612a Add support for key hash tags in redis-cluster (Redis 3.0.0b1).
Multi-keys operations are not allowed even when keys generate the same
hash but this will probably be supported in later betas of Redis which
means we will basically end up reusing the whole strategy used for
client-side sharding.
2014-02-11 15:33:07 +01:00
Daniele Alessandri fdaf73987b Update CHANGELOG. 2014-02-11 12:44:00 +01:00
Daniele Alessandri 50f8922df2 Update README about HHVM compatibility. 2014-02-11 12:44:00 +01:00
Daniele Alessandri 60733eb5c9 Make Predis v0.8 compatible with HHVM 2.4.0.
Since we cannot rely on the PHP version to detect the availability of
socket_import_stream(), we switched to function_exists(). As an added
bonus, using function_exists() is twice faster.
2014-02-11 12:44:00 +01:00
Daniele Alessandri a13dd4e180 Back to development. 2014-02-11 12:44:00 +01:00
thbourlove 1678e486b1 use 2.8 as the default server version 2014-01-25 10:55:47 +08:00
61 changed files with 2701 additions and 558 deletions
+11 -1
View File
@@ -3,6 +3,8 @@ php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
branches:
except:
- v0.5
@@ -10,4 +12,12 @@ branches:
- php5.2_backport
- documentation
services: redis-server
script: phpunit -c phpunit.xml.travisci
before_script:
- composer self-update
- composer install --no-interaction --prefer-source --dev
script:
- vendor/bin/phpunit -c phpunit.xml.travisci
matrix:
allow_failures:
- php: hhvm
fast_finish: true
+65
View File
@@ -1,3 +1,68 @@
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
break compatibility with previous releases.
- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for
handling the HyperLogLog data structure introduced in Redis 2.8.9.
- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for
Redis 2.8 for handling lexicographic operations on members of sorted sets.
- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1).
- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0.
- __FIX__: responses to `INFO` are now properly parsed and will not break when
redis sentinel is being used (ISSUE #154).
- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication
configurations (ISSUE #159).
- __FIX__: fix parsing of the output of `CLUSTER NODES` to fetch the slots map
from a node when redis-cluster has slaves in its configuration (ISSUE #165).
- __FIX__: prevent a stack overflow when iterating over large Redis collections
using our abstraction for cursor-based iterators (ISSUE #182).
- __FIX__: properly discards transactions when the server immediately returns an
error response (e.g. -OOM or -ERR on invalid arguments for a command) instead
of a +QUEUED response (ISSUE #187).
- Upgraded to PHPUnit 4.* for the test suite.
v0.8.5 (2014-01-16)
================================================================================
+138 -73
View File
@@ -10,18 +10,22 @@ By default Predis does not require any additional C extension, but it can be opt
Redis protocol. An asynchronous implementation of the client, albeit experimental, is also available
through [Predis\Async](https://github.com/nrk/predis-async).
Predis can be used with [HHVM](http://www.hhvm.com) >= 2.3.0, but there are no guarantees you will
not run into unexpected issues (especially when the JIT compiler is enabled via `Eval.Jit = true`)
due to HHVM being still under heavy development, thus unstable and not yet 100% compatible with PHP.
More details about the project can be found in our [frequently asked questions](FAQ.md) section or
on the online [wiki](https://github.com/nrk/predis/wiki).
## Main features ##
- Wide range of Redis versions supported (from __1.2__ to __2.8__ and unstable) using 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).
- Wide range of Redis versions supported (from __1.2__ to __3.0__ and __unstable__) using profiles.
- Cluster of nodes via client-side sharding using consistent hashing or custom distributors.
- Smart support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication configurations (write on master, read from slaves).
- Transparent key prefixing for all Redis commands.
- Command pipelining (works on both single and aggregate connections).
- Transparent key prefixing for all known Redis commands.
- Command pipelining (works on both single nodes and aggregate connections).
- Abstraction for Redis transactions (Redis >= 2.0) supporting CAS operations (Redis >= 2.2).
- Abstraction for Lua scripting (Redis >= 2.6) with automatic switching between `EVALSHA` or `EVAL`.
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
@@ -45,8 +49,8 @@ Ultimately, archives of each release are [available on GitHub](https://github.co
Predis relies on the autoloading features of PHP to load its files when needed and complies with the
[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:
managed through Composer, but it is also possible to leverage its own autoloader in projects or
scripts not having any autoload facility:
```php
// Prepend a base path if Predis is not available in your "include_path".
@@ -58,14 +62,14 @@ Predis\Autoloader::register();
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.
Alternatively it is possible to generate one single PHP file holding every class like older versions
of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
### Connecting to Redis ###
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:
`6379` as the default host and port with a connection timeout of 5 seconds:
```php
$client = new Predis\Client();
@@ -89,9 +93,9 @@ $client = new Predis\Client([
$client = new Predis\Client('tcp://10.0.0.1:6379');
```
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:
When an array of connection parameters is provided, Predis automatically works in cluster mode using
client-side sharding. Both named arrays and URI strings can be mixed when providing configurations
for each node:
```php
$client = new Predis\Client([
@@ -101,12 +105,12 @@ $client = new Predis\Client([
```
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.
it is recommended to refer to their specific documentation or implementation for details.
### Client configuration ###
Various aspects of the client can be easily configured by passing options to the second argument of
Various aspects of the client can be configured simply by passing options to the second argument of
`Predis\Client::__construct()`. Options are managed using a mini DI-alike container and their values
are usually lazily initialized only when needed. Predis by default supports the following options:
@@ -114,7 +118,7 @@ are usually lazily initialized only when needed. Predis by default supports the
- `prefix`: a prefix string that is automatically applied to keys found in commands.
- `exceptions`: whether the client should throw or return responses upon Redis errors.
- `connections`: connection backends or a connection factory to be used by the client.
- `cluster`: which backend to use for clustering (predis, redis or custom configuration).
- `cluster`: which backend to use for clustering (`predis`, `redis` or custom configuration).
- `replication`: which backend to use for replication (predis or custom configuration).
Users can provide custom option values, they are stored in the options container and can be accessed
@@ -123,11 +127,71 @@ later through the library.
### Aggregate connections ###
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.
Predis is able to aggregate multiple connections which is the base for cluster and replication. By
default the client implements a cluster of nodes using either client-side sharding (default) or a
Redis-backed solution using [redis-cluster](http://redis.io/topics/cluster-tutorial).
As for replication, Predis can handle a single-master and multiple-slaves setup by executing read
operations on slaves and switching to the master for write operations. The replication behavior is
fully configurable.
#### Replication ####
The client can be configured to work in a master / slave replication setup by executing read-only
commands on slave nodes and automatically switch to the master node as soon as a command performing
a write operation is executed. This is the basic configuration needed to work with replication:
```php
// Parameters require one master node specifically marked with `alias=master`.
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => true];
$client = new Predis\Client($parameters, $options);
```
While Predis is able to distinguish commands performing write and read-only operations, `EVAL` and
`EVALSHA` represent a corner case in which the client switches to the master node because it is not
able to tell when a Lua script is safe to be executed on slaves. While this is the default behavior,
when certain Lua scripts do not perform write operations it is possible to provide an hint to tell
the client to stick with slaves for their execution.
```php
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
$options = ['replication' => function () {
// Set scripts that won't trigger a switch from a slave to the master node.
$strategy = new Predis\Replication\ReplicationStrategy();
$strategy->setScriptReadOnly($LUA_SCRIPT);
return new Predis\Connection\Aggregate\MasterSlaveReplication($strategy);
}];
$client = new Predis\Client($parameters, $options);
$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`...
$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too.
```
The `examples` directory contains two complete scripts showing how replication can be configured for
[simple](examples/MasterSlaveReplication.php) or [complex](examples/MasterSlaveReplicationComplex.php)
scenarios.
#### Cluster ####
Simply passing an array of connection parameters to the client constructor configures Predis to work
in cluster mode using client-side sharding. If you, on the other hand, want to leverage Redis >= 3.0
nodes coordinated by redis-cluster, then the client must be initialized like this:
```php
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2'];
$options = ['cluster' => 'redis'];
$client = new Predis\Client($parameters, $options);
```
When using redis-cluster, it is not necessary to pass all of the nodes that compose your cluster but
you can simply specify only a few nodes: Predis will automatically fetch the full and updated slots
map directly from Redis by contacting one of the servers.
__NOTE__: our support for redis-cluster does not currently consider master / slave replication but
this feature will be added in a future release of this library.
### Command pipelines ###
@@ -175,43 +239,7 @@ __NOTE__: the method `transaction()` is available since `v0.8.5`, older versions
for the same purpose but it has been deprecated and will be removed in the next major release.
### 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 or UNIX domain sockets:
```php
$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 ###
### Adding new Redis commands ###
While we try to update Predis to stay up to date with all the commands available in Redis, you might
prefer to stick with an older version of the library or provide a different way to filter arguments
@@ -236,15 +264,16 @@ $response = $client->newcmd();
```
### Scriptable commands ###
### Script 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:
While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using
[`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha), Predis
offers script commands as an higher level abstraction aiming to make things simple. Script commands
can be registered in the server profile used by the client and are accessible as if they were plain
Redis commands, but they define Lua scripts that get transmitted to the server for remote execution.
Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and identify a script
by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval) is used as a fall
back when needed:
```php
// Define a new scriptable command by extending Predis\Command\ScriptedCommand:
@@ -266,7 +295,7 @@ LUA;
}
}
// Inject your scriptable command in the current profile:
// Inject your script command in the current profile:
$client = new Predis\Client();
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
@@ -274,6 +303,42 @@ $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 or UNIX domain sockets:
```php
$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 support whole new network backends, extend
existing ones or provide completely different implementations. Connection classes must implement
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
```php
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
{
// Implementation goes here...
}
// Use MyConnectionClass to handle connections for the `tcp` scheme:
$client = new Predis\Client('tcp://127.0.0.1', [
'connections' => ['tcp' => 'MyConnectionClass'],
]);
```
For a more in-depth insight on how to create new connection backends you can refer to the actual
implementation of the standard connection classes available in the `Predis\Connection` namespace.
## Development ##
@@ -292,13 +357,13 @@ 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
behaviour of the implementation of each command and automatically skips commands not defined in the
behavior of the implementation of each command and automatically skips commands not defined in the
specified Redis profile. If you do not have Redis up and running, integration tests can be disabled.
By default the test suite is configured to execute integration tests using the profile for Redis 2.6
but can optionally target a Redis instance built from `2.8` or `unstable` branches by modifying the
`phpunit.xml` file and setting `REDIS_SERVER_VERSION` respectively to `2.8` or `dev` so that the
correct server profile will be used. You can refer to [the tests README](tests/README.md) for more
detailed information about testing Predis.
By default the test suite is configured to execute integration tests using the profile for Redis 2.8
(which is the current stable version of Redis) but can optionally target a Redis instance built from
the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that
the development server profile will be used. You can refer to [the tests README](tests/README.md)
for more detailed information about testing Predis.
Predis uses Travis CI for continuous integration and the history for past and current builds can be
found [on its project page](http://travis-ci.org/nrk/predis).
+1 -1
View File
@@ -1 +1 @@
0.8.5
0.8.7
+172 -4
View File
@@ -12,7 +12,11 @@
// -------------------------------------------------------------------------- //
// In order to be able to execute this script to create a PEAR package of Predis
// both `onion` and `pear` must be available and executable in your $PATH.
// the `pear` binary must be available and executable in your $PATH.
// The parts used to parse author and version strings are taken from Onion (used
// by this library in the past) just to keep on relying on the package.ini file
// to simplify things. We might consider to switch to using the PEAR classes
// directly in the future.
// -------------------------------------------------------------------------- //
function executeWithBackup($file, $callback)
@@ -36,18 +40,182 @@ function executeWithBackup($file, $callback)
}
}
function buildPackage()
function parseAuthor($string)
{
passthru('onion build && pear -q package && rm package.xml');
$author = array();
if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
if (count($regs) == 4) {
list($orig,$name,$user,$email) = $regs;
$author['name'] = $name;
$author['user'] = $user;
$author['email'] = $email;
} elseif (count($regs) == 3) {
list($orig,$name,$email) = $regs;
$author['name'] = $name;
$author['email'] = $email;
}
} else {
$author['name'] = $string;
}
return $author;
}
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
function parseVersion($string)
{
$version_pattern = '([0-9.]+)';
if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('min' => $regs[1] ?: '0.0.0');
} elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
return array('max' => $regs[1]);
} elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) {
return array(
'min' => $regs[1] ?: '0.0.0',
'max' => $regs[2],
);
}
}
function addRolePath($pkg, $path, $role)
{
if (is_dir($path)) {
$dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($dirTree as $fileinfo) {
if ($fileinfo->isFile()) {
addPackageFile($pkg, $fileinfo, $role, $path);
}
}
} else {
foreach (glob($path) as $filename) {
addPackageFile($pkg, new SplFileInfo($filename), $role);
}
}
}
function addPackageFile($pkg, $fileinfo, $role, $baseDir = '')
{
$fileNode = $pkg->contents->dir->addChild('file');
$fileNode->addAttribute('name', $filepath = $fileinfo->getPathname());
$fileNode->addAttribute('role', $role);
$fileNode->addAttribute('md5sum', md5_file($filepath));
$installNode = $pkg->phprelease->filelist->addChild('install');
$installNode->addAttribute('name', $filepath);
$installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1));
}
function generatePackageXml($packageINI)
{
$XML = <<<XML
<?xml version="1.0"?>
<package packagerversion="1.4.10" version="2.0"
xmlns="http://pear.php.net/dtd/package-2.0"
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd" />
XML;
$cfg = parse_ini_file($packageINI, true);
$pkg = new SimpleXMLElement($XML);
$pkg->name = $cfg['package']['name'];
$pkg->channel = $cfg['package']['channel'];
$pkg->summary = $cfg['package']['desc'];
$pkg->description = $cfg['package']['desc'];
$author = parseAuthor($cfg['package']['author']);
$pkg->addChild('lead');
$pkg->lead->name = $author['name'];
$pkg->lead->user = $author['user'];
$pkg->lead->email = $author['email'];
$pkg->lead->active = 'yes';
$datetime = new DateTime('now');
$pkg->date = $datetime->format('Y-m-d');
$pkg->time = $datetime->format('H:i:s');
$pkg->addChild('version');
$pkg->version->release = $cfg['package']['version'];
$pkg->version->api = $cfg['package']['version'];
$pkg->addChild('stability');
$pkg->stability->release = $cfg['package']['stability'];
$pkg->stability->api = $cfg['package']['stability'];
$pkg->license = $cfg['package']['license'];
$pkg->notes = '-';
$pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/');
$pkg->addChild('dependencies')->addChild('required');
foreach ($cfg['require'] as $required => $version) {
$version = parseVersion($version);
$pkg->dependencies->required->addChild($required);
if (isset($version['min'])) {
$pkg->dependencies->required->$required->min = $version['min'];
}
if (isset($version['max'])) {
$pkg->dependencies->required->$required->min = $version['max'];
}
}
$pkg->addChild('phprelease')->addChild('filelist');
$pathToRole = array(
'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc',
'lib' => 'php', 'src' => 'php',
'test' => 'test', 'tests' => 'test',
);
foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) {
addRolePath($pkg, $path, $role);
}
return $pkg;
}
function savePackageXml($xml)
{
$dom = new DOMDocument("1.0");
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
file_put_contents('package.xml', $dom->saveXML());
}
function buildPackage()
{
passthru('pear -q package && rm package.xml');
}
function modifyPhpunitXml($file)
{
$cfg = new SimpleXMLElement($file, null, true);
$cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']);
$cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory);
$cfg->saveXml($file);
}
// -------------------------------------------------------------------------- //
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
modifyPhpunitXml($file);
$pkg = generatePackageXml('package.ini');
savePackageXml($pkg);
buildPackage();
});
+3
View File
@@ -15,6 +15,9 @@
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"suggest": {
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
"ext-curl": "Allows access to Webdis when paired with phpiredis"
+5
View File
@@ -84,6 +84,11 @@ for ($i = 0; $i < 100; $i++) {
$server1 = $client->getClientFor('first')->info();
$server2 = $client->getClientFor('second')->info();
if (isset($server1['Keyspace'], $server2['Keyspace'])) {
$server1 = $server1['Keyspace'];
$server2 = $server2['Keyspace'];
}
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
);
+4 -3
View File
@@ -20,7 +20,6 @@ require 'SharedConfigurations.php';
use Predis\Command\ScriptedCommand;
use Predis\Connection\MasterSlaveReplication;
use Predis\Profile\ServerProfile;
use Predis\Replication\ReplicationStrategy;
// ------------------------------------------------------------------------- //
@@ -28,7 +27,8 @@ use Predis\Replication\ReplicationStrategy;
// Define a new scripted command that returns all the fields
// of a variable number of hashes with a single roundtrip.
class HashMultipleGetAll extends ScriptedCommand {
class HashMultipleGetAll extends ScriptedCommand
{
const BODY = <<<EOS
local hashes = {}
for _, key in pairs(KEYS) do
@@ -38,7 +38,8 @@ end
return hashes
EOS;
public function getScript() {
public function getScript()
{
return self::BODY;
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ use Predis\Transaction\MultiExecContext;
*/
class Client implements ClientInterface
{
const VERSION = '0.8.5';
const VERSION = '0.8.7';
private $options;
private $profile;
+101 -92
View File
@@ -23,8 +23,8 @@ use Predis\Command\ScriptedCommand;
*/
class PredisClusterHashStrategy implements CommandHashStrategyInterface
{
private $commands;
private $hashGenerator;
protected $commands;
protected $hashGenerator;
/**
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
@@ -42,117 +42,126 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
*/
protected function getDefaultCommands()
{
$keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
$keysAreAllArguments = array($this, 'getKeyFromAllArguments');
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
return array(
/* commands operating on the key space */
'EXISTS' => $keyIsFirstArgument,
'DEL' => $keysAreAllArguments,
'TYPE' => $keyIsFirstArgument,
'EXPIRE' => $keyIsFirstArgument,
'EXPIREAT' => $keyIsFirstArgument,
'PERSIST' => $keyIsFirstArgument,
'PEXPIRE' => $keyIsFirstArgument,
'PEXPIREAT' => $keyIsFirstArgument,
'TTL' => $keyIsFirstArgument,
'PTTL' => $keyIsFirstArgument,
'SORT' => $keyIsFirstArgument, // TODO
'DUMP' => $keyIsFirstArgument,
'RESTORE' => $keyIsFirstArgument,
'EXISTS' => $getKeyFromFirstArgument,
'DEL' => $getKeyFromAllArguments,
'TYPE' => $getKeyFromFirstArgument,
'EXPIRE' => $getKeyFromFirstArgument,
'EXPIREAT' => $getKeyFromFirstArgument,
'PERSIST' => $getKeyFromFirstArgument,
'PEXPIRE' => $getKeyFromFirstArgument,
'PEXPIREAT' => $getKeyFromFirstArgument,
'TTL' => $getKeyFromFirstArgument,
'PTTL' => $getKeyFromFirstArgument,
'SORT' => $getKeyFromFirstArgument, // TODO
'DUMP' => $getKeyFromFirstArgument,
'RESTORE' => $getKeyFromFirstArgument,
/* commands operating on string values */
'APPEND' => $keyIsFirstArgument,
'DECR' => $keyIsFirstArgument,
'DECRBY' => $keyIsFirstArgument,
'GET' => $keyIsFirstArgument,
'GETBIT' => $keyIsFirstArgument,
'MGET' => $keysAreAllArguments,
'SET' => $keyIsFirstArgument,
'GETRANGE' => $keyIsFirstArgument,
'GETSET' => $keyIsFirstArgument,
'INCR' => $keyIsFirstArgument,
'INCRBY' => $keyIsFirstArgument,
'SETBIT' => $keyIsFirstArgument,
'SETEX' => $keyIsFirstArgument,
'APPEND' => $getKeyFromFirstArgument,
'DECR' => $getKeyFromFirstArgument,
'DECRBY' => $getKeyFromFirstArgument,
'GET' => $getKeyFromFirstArgument,
'GETBIT' => $getKeyFromFirstArgument,
'MGET' => $getKeyFromAllArguments,
'SET' => $getKeyFromFirstArgument,
'GETRANGE' => $getKeyFromFirstArgument,
'GETSET' => $getKeyFromFirstArgument,
'INCR' => $getKeyFromFirstArgument,
'INCRBY' => $getKeyFromFirstArgument,
'INCRBYFLOAT' => $getKeyFromFirstArgument,
'SETBIT' => $getKeyFromFirstArgument,
'SETEX' => $getKeyFromFirstArgument,
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
'SETNX' => $keyIsFirstArgument,
'SETRANGE' => $keyIsFirstArgument,
'STRLEN' => $keyIsFirstArgument,
'SUBSTR' => $keyIsFirstArgument,
'SETNX' => $getKeyFromFirstArgument,
'SETRANGE' => $getKeyFromFirstArgument,
'STRLEN' => $getKeyFromFirstArgument,
'SUBSTR' => $getKeyFromFirstArgument,
'BITOP' => array($this, 'getKeyFromBitOp'),
'BITCOUNT' => $keyIsFirstArgument,
'BITCOUNT' => $getKeyFromFirstArgument,
/* commands operating on lists */
'LINSERT' => $keyIsFirstArgument,
'LINDEX' => $keyIsFirstArgument,
'LLEN' => $keyIsFirstArgument,
'LPOP' => $keyIsFirstArgument,
'RPOP' => $keyIsFirstArgument,
'RPOPLPUSH' => $keysAreAllArguments,
'LINSERT' => $getKeyFromFirstArgument,
'LINDEX' => $getKeyFromFirstArgument,
'LLEN' => $getKeyFromFirstArgument,
'LPOP' => $getKeyFromFirstArgument,
'RPOP' => $getKeyFromFirstArgument,
'RPOPLPUSH' => $getKeyFromAllArguments,
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
'LPUSH' => $keyIsFirstArgument,
'LPUSHX' => $keyIsFirstArgument,
'RPUSH' => $keyIsFirstArgument,
'RPUSHX' => $keyIsFirstArgument,
'LRANGE' => $keyIsFirstArgument,
'LREM' => $keyIsFirstArgument,
'LSET' => $keyIsFirstArgument,
'LTRIM' => $keyIsFirstArgument,
'LPUSH' => $getKeyFromFirstArgument,
'LPUSHX' => $getKeyFromFirstArgument,
'RPUSH' => $getKeyFromFirstArgument,
'RPUSHX' => $getKeyFromFirstArgument,
'LRANGE' => $getKeyFromFirstArgument,
'LREM' => $getKeyFromFirstArgument,
'LSET' => $getKeyFromFirstArgument,
'LTRIM' => $getKeyFromFirstArgument,
/* commands operating on sets */
'SADD' => $keyIsFirstArgument,
'SCARD' => $keyIsFirstArgument,
'SDIFF' => $keysAreAllArguments,
'SDIFFSTORE' => $keysAreAllArguments,
'SINTER' => $keysAreAllArguments,
'SINTERSTORE' => $keysAreAllArguments,
'SUNION' => $keysAreAllArguments,
'SUNIONSTORE' => $keysAreAllArguments,
'SISMEMBER' => $keyIsFirstArgument,
'SMEMBERS' => $keyIsFirstArgument,
'SSCAN' => $keyIsFirstArgument,
'SPOP' => $keyIsFirstArgument,
'SRANDMEMBER' => $keyIsFirstArgument,
'SREM' => $keyIsFirstArgument,
'SADD' => $getKeyFromFirstArgument,
'SCARD' => $getKeyFromFirstArgument,
'SDIFF' => $getKeyFromAllArguments,
'SDIFFSTORE' => $getKeyFromAllArguments,
'SINTER' => $getKeyFromAllArguments,
'SINTERSTORE' => $getKeyFromAllArguments,
'SUNION' => $getKeyFromAllArguments,
'SUNIONSTORE' => $getKeyFromAllArguments,
'SISMEMBER' => $getKeyFromFirstArgument,
'SMEMBERS' => $getKeyFromFirstArgument,
'SSCAN' => $getKeyFromFirstArgument,
'SPOP' => $getKeyFromFirstArgument,
'SRANDMEMBER' => $getKeyFromFirstArgument,
'SREM' => $getKeyFromFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $keyIsFirstArgument,
'ZCARD' => $keyIsFirstArgument,
'ZCOUNT' => $keyIsFirstArgument,
'ZINCRBY' => $keyIsFirstArgument,
'ZADD' => $getKeyFromFirstArgument,
'ZCARD' => $getKeyFromFirstArgument,
'ZCOUNT' => $getKeyFromFirstArgument,
'ZINCRBY' => $getKeyFromFirstArgument,
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'ZRANGE' => $keyIsFirstArgument,
'ZRANGEBYSCORE' => $keyIsFirstArgument,
'ZRANK' => $keyIsFirstArgument,
'ZREM' => $keyIsFirstArgument,
'ZREMRANGEBYRANK' => $keyIsFirstArgument,
'ZREMRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANGE' => $keyIsFirstArgument,
'ZREVRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANK' => $keyIsFirstArgument,
'ZSCORE' => $keyIsFirstArgument,
'ZRANGE' => $getKeyFromFirstArgument,
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZRANK' => $getKeyFromFirstArgument,
'ZREM' => $getKeyFromFirstArgument,
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANGE' => $getKeyFromFirstArgument,
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANK' => $getKeyFromFirstArgument,
'ZSCORE' => $getKeyFromFirstArgument,
'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
'ZSCAN' => $keyIsFirstArgument,
'ZSCAN' => $getKeyFromFirstArgument,
'ZLEXCOUNT' => $getKeyFromFirstArgument,
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
/* commands operating on hashes */
'HDEL' => $keyIsFirstArgument,
'HEXISTS' => $keyIsFirstArgument,
'HGET' => $keyIsFirstArgument,
'HGETALL' => $keyIsFirstArgument,
'HMGET' => $keyIsFirstArgument,
'HMSET' => $keyIsFirstArgument,
'HINCRBY' => $keyIsFirstArgument,
'HINCRBYFLOAT' => $keyIsFirstArgument,
'HKEYS' => $keyIsFirstArgument,
'HLEN' => $keyIsFirstArgument,
'HSET' => $keyIsFirstArgument,
'HSETNX' => $keyIsFirstArgument,
'HVALS' => $keyIsFirstArgument,
'HSCAN' => $keyIsFirstArgument,
'HDEL' => $getKeyFromFirstArgument,
'HEXISTS' => $getKeyFromFirstArgument,
'HGET' => $getKeyFromFirstArgument,
'HGETALL' => $getKeyFromFirstArgument,
'HMGET' => $getKeyFromFirstArgument,
'HMSET' => $getKeyFromFirstArgument,
'HINCRBY' => $getKeyFromFirstArgument,
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
'HKEYS' => $getKeyFromFirstArgument,
'HLEN' => $getKeyFromFirstArgument,
'HSET' => $getKeyFromFirstArgument,
'HSETNX' => $getKeyFromFirstArgument,
'HVALS' => $getKeyFromFirstArgument,
'HSCAN' => $getKeyFromFirstArgument,
/* commands operating on HyperLogLog */
'PFADD' => $getKeyFromFirstArgument,
'PFCOUNT' => $getKeyFromAllArguments,
'PFMERGE' => $getKeyFromAllArguments,
/* scripting */
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
+12 -254
View File
@@ -12,8 +12,7 @@
namespace Predis\Cluster;
use Predis\Cluster\Hash\CRC16HashGenerator;
use Predis\Command\CommandInterface;
use Predis\Command\ScriptedCommand;
use Predis\Cluster\Hash\HashGeneratorInterface;
/**
* Default class used by Predis to calculate hashes out of keys of
@@ -21,272 +20,31 @@ use Predis\Command\ScriptedCommand;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RedisClusterHashStrategy implements CommandHashStrategyInterface
class RedisClusterHashStrategy extends PredisClusterHashStrategy
{
private $commands;
private $hashGenerator;
/**
*
*/
public function __construct()
public function __construct(HashGeneratorInterface $hashGenerator = null)
{
$this->commands = $this->getDefaultCommands();
$this->hashGenerator = new CRC16HashGenerator();
parent::__construct($hashGenerator ?: new CRC16HashGenerator());
}
/**
* Returns the default map of supported commands with their handlers.
* Returns only the hashable part of a key (delimited by "{...}"), or the
* whole key if a key tag is not found in the string.
*
* @return array
*/
protected function getDefaultCommands()
{
$keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
return array(
/* commands operating on the key space */
'EXISTS' => $keyIsFirstArgument,
'DEL' => array($this, 'getKeyFromAllArguments'),
'TYPE' => $keyIsFirstArgument,
'EXPIRE' => $keyIsFirstArgument,
'EXPIREAT' => $keyIsFirstArgument,
'PERSIST' => $keyIsFirstArgument,
'PEXPIRE' => $keyIsFirstArgument,
'PEXPIREAT' => $keyIsFirstArgument,
'TTL' => $keyIsFirstArgument,
'PTTL' => $keyIsFirstArgument,
'SORT' => $keyIsFirstArgument, // TODO
/* commands operating on string values */
'APPEND' => $keyIsFirstArgument,
'DECR' => $keyIsFirstArgument,
'DECRBY' => $keyIsFirstArgument,
'GET' => $keyIsFirstArgument,
'GETBIT' => $keyIsFirstArgument,
'MGET' => array($this, 'getKeyFromAllArguments'),
'SET' => $keyIsFirstArgument,
'GETRANGE' => $keyIsFirstArgument,
'GETSET' => $keyIsFirstArgument,
'INCR' => $keyIsFirstArgument,
'INCRBY' => $keyIsFirstArgument,
'SETBIT' => $keyIsFirstArgument,
'SETEX' => $keyIsFirstArgument,
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
'SETNX' => $keyIsFirstArgument,
'SETRANGE' => $keyIsFirstArgument,
'STRLEN' => $keyIsFirstArgument,
'SUBSTR' => $keyIsFirstArgument,
'BITCOUNT' => $keyIsFirstArgument,
/* commands operating on lists */
'LINSERT' => $keyIsFirstArgument,
'LINDEX' => $keyIsFirstArgument,
'LLEN' => $keyIsFirstArgument,
'LPOP' => $keyIsFirstArgument,
'RPOP' => $keyIsFirstArgument,
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
'LPUSH' => $keyIsFirstArgument,
'LPUSHX' => $keyIsFirstArgument,
'RPUSH' => $keyIsFirstArgument,
'RPUSHX' => $keyIsFirstArgument,
'LRANGE' => $keyIsFirstArgument,
'LREM' => $keyIsFirstArgument,
'LSET' => $keyIsFirstArgument,
'LTRIM' => $keyIsFirstArgument,
/* commands operating on sets */
'SADD' => $keyIsFirstArgument,
'SCARD' => $keyIsFirstArgument,
'SISMEMBER' => $keyIsFirstArgument,
'SMEMBERS' => $keyIsFirstArgument,
'SSCAN' => $keyIsFirstArgument,
'SPOP' => $keyIsFirstArgument,
'SRANDMEMBER' => $keyIsFirstArgument,
'SREM' => $keyIsFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $keyIsFirstArgument,
'ZCARD' => $keyIsFirstArgument,
'ZCOUNT' => $keyIsFirstArgument,
'ZINCRBY' => $keyIsFirstArgument,
'ZRANGE' => $keyIsFirstArgument,
'ZRANGEBYSCORE' => $keyIsFirstArgument,
'ZRANK' => $keyIsFirstArgument,
'ZREM' => $keyIsFirstArgument,
'ZREMRANGEBYRANK' => $keyIsFirstArgument,
'ZREMRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANGE' => $keyIsFirstArgument,
'ZREVRANGEBYSCORE' => $keyIsFirstArgument,
'ZREVRANK' => $keyIsFirstArgument,
'ZSCORE' => $keyIsFirstArgument,
'ZSCAN' => $keyIsFirstArgument,
/* commands operating on hashes */
'HDEL' => $keyIsFirstArgument,
'HEXISTS' => $keyIsFirstArgument,
'HGET' => $keyIsFirstArgument,
'HGETALL' => $keyIsFirstArgument,
'HMGET' => $keyIsFirstArgument,
'HMSET' => $keyIsFirstArgument,
'HINCRBY' => $keyIsFirstArgument,
'HINCRBYFLOAT' => $keyIsFirstArgument,
'HKEYS' => $keyIsFirstArgument,
'HLEN' => $keyIsFirstArgument,
'HSET' => $keyIsFirstArgument,
'HSETNX' => $keyIsFirstArgument,
'HVALS' => $keyIsFirstArgument,
'HSCAN' => $keyIsFirstArgument,
/* scripting */
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
);
}
/**
* Returns the list of IDs for the supported commands.
*
* @return array
*/
public function getSupportedCommands()
{
return array_keys($this->commands);
}
/**
* Sets an handler for the specified command ID.
*
* The signature of the callback must have a single parameter
* of type Predis\Command\CommandInterface.
*
* When the callback argument is omitted or NULL, the previously
* associated handler for the specified command ID is removed.
*
* @param string $commandId The ID of the command to be handled.
* @param mixed $callback A valid callable object or NULL.
*/
public function setCommandHandler($commandId, $callback = null)
{
$commandId = strtoupper($commandId);
if (!isset($callback)) {
unset($this->commands[$commandId]);
return;
}
if (!is_callable($callback)) {
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
}
$this->commands[$commandId] = $callback;
}
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
* @param string $key A key.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
protected function extractKeyTag($key)
{
return $command->getArgument(0);
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromAllArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 1) {
return $arguments[0];
}
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from BLPOP and BRPOP commands ensuring that only one key
* is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
if ($command instanceof ScriptedCommand) {
$keys = $command->getKeys();
} else {
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
}
if (count($keys) === 1) {
return $keys[0];
}
}
/**
* {@inheritdoc}
*/
public function getHash(CommandInterface $command)
{
$hash = $command->getHash();
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
$key = call_user_func($this->commands[$cmdID], $command);
if (isset($key)) {
$hash = $this->hashGenerator->hash($key);
$command->setHash($hash);
if (false !== $start = strpos($key, '{')) {
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
$key = substr($key, $start, $end - $start);
}
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function getKeyHash($key)
{
return $this->hashGenerator->hash($key);
return $key;
}
}
@@ -166,16 +166,18 @@ abstract class CursorBasedIterator implements Iterator
*/
public function next()
{
if (!$this->elements && $this->fetchmore) {
$this->fetch();
}
tryFetch: {
if (!$this->elements && $this->fetchmore) {
$this->fetch();
}
if ($this->elements) {
$this->extractNext();
} elseif ($this->cursor) {
$this->next();
} else {
$this->valid = false;
if ($this->elements) {
$this->extractNext();
} elseif ($this->cursor) {
goto tryFetch;
} else {
$this->valid = false;
}
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/pfadd
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogAdd extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFADD';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeVariadic($arguments);
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
return (bool) $data;
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/pfcount
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogCount extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFCOUNT';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/pfmerge
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class HyperLogLogMerge extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'PFMERGE';
}
/**
* {@inheritdoc}
*/
protected function filterArguments(array $arguments)
{
return self::normalizeArguments($arguments);
}
/**
* {@inheritdoc}
*/
public function prefixKeys($prefix)
{
PrefixHelpers::all($this, $prefix);
}
}
+1 -1
View File
@@ -42,7 +42,7 @@ class RawCommand implements CommandInterface
/**
* Creates a new raw command using a variadic method.
*
* @param string $commandID Redis command ID.
* @param string $commandID Redis command ID.
* @param string ... Arguments list for the command.
* @return CommandInterface
*/
+27
View File
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/command
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerCommand extends AbstractCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'COMMAND';
}
}
+24 -13
View File
@@ -34,27 +34,38 @@ class ServerInfo extends AbstractCommand
$infoLines = preg_split('/\r?\n/', $data);
foreach ($infoLines as $row) {
@list($k, $v) = explode(':', $row);
if ($row === '' || !isset($v)) {
if (strpos($row, ':') === false) {
continue;
}
if (!preg_match('/^db\d+$/', $k)) {
if ($k === 'allocation_stats') {
$info[$k] = $this->parseAllocationStats($v);
continue;
}
$info[$k] = $v;
} else {
$info[$k] = $this->parseDatabaseStats($v);
}
list($k, $v) = $this->parseRow($row);
$info[$k] = $v;
}
return $info;
}
/**
* Parses single row of the reply buffer and returns the key-value pair.
*
* @param string $row Single row of the reply buffer.
* @return array
*/
public function parseRow($row)
{
list($k, $v) = explode(':', $row, 2);
if (!preg_match('/^db\d+$/', $k)) {
if ($k === 'allocation_stats') {
$v = $this->parseAllocationStats($v);
}
} else {
$v = $this->parseDatabaseStats($v);
}
return array($k, $v);
}
/**
* Parses the reply buffer and extracts the statistics of each logical DB.
*
+2 -12
View File
@@ -41,18 +41,8 @@ class ServerInfoV26x extends ServerInfo
continue;
}
list($k, $v) = explode(':', $row);
if (!preg_match('/^db\d+$/', $k)) {
if ($k === 'allocation_stats') {
$current[$k] = $this->parseAllocationStats($v);
continue;
}
$current[$k] = $v;
} else {
$current[$k] = $this->parseDatabaseStats($v);
}
list($k, $v) = $this->parseRow($row);
$current[$k] = $v;
}
return $info;
+27
View File
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/zlexcount
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZSetLexCount extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZLEXCOUNT';
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/zrangebylex
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZSetRangeByLex extends ZSetRange
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZRANGEBYLEX';
}
/**
* {@inheritdoc}
*/
protected function prepareOptions($options)
{
$opts = array_change_key_case($options, CASE_UPPER);
$finalizedOpts = array();
if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
$limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
$finalizedOpts[] = 'LIMIT';
$finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
$finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
}
return $finalizedOpts;
}
/**
* {@inheritdoc}
*/
protected function withScores()
{
return false;
}
}
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @link http://redis.io/commands/zremrangebylex
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZSetRemoveRangeByLex extends PrefixableCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZREMRANGEBYLEX';
}
}
@@ -96,6 +96,52 @@ class PhpiredisStreamConnection extends StreamConnection
return parent::checkParameters($parameters);
}
/**
* {@inheritdoc}
*/
protected function tcpStreamInitializer(ConnectionParametersInterface $parameters)
{
$uri = "tcp://{$parameters->host}:{$parameters->port}";
$flags = STREAM_CLIENT_CONNECT;
$socket = null;
if (isset($parameters->async_connect) && $parameters->async_connect) {
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
}
if (isset($parameters->persistent) && $parameters->persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
$uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
}
$resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
if (!$resource) {
$this->onConnectionError(trim($errstr), $errno);
}
if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
$rwtimeout = (float) $parameters->read_write_timeout;
$rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
$timeout = array(
'sec' => $timeoutSeconds = floor($rwtimeout),
'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
);
$socket = $socket ?: socket_import_stream($resource);
@socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
@socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
}
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
$socket = $socket ?: socket_import_stream($resource);
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
}
return $resource;
}
/**
* Initializes the protocol reader resource.
*/
@@ -151,7 +197,7 @@ class PhpiredisStreamConnection extends StreamConnection
$reader = $this->reader;
while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
$buffer = fread($socket, 4096);
$buffer = stream_socket_recvfrom($socket, 4096);
if ($buffer === false || $buffer === '') {
$this->onConnectionError('Error while reading bytes from the server');
+26 -22
View File
@@ -26,7 +26,7 @@ use Predis\Command\RawCommand;
* Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
*
* This connection backend offers smart support for redis-cluster by handling
* automatic slots map (re)generation upon -MOVE or -ASK responses returned by
* automatic slots map (re)generation upon -MOVED or -ASK responses returned by
* Redis when redirecting a client to a different node.
*
* The cluster can be pre-initialized using only a subset of the actual nodes in
@@ -40,13 +40,13 @@ use Predis\Command\RawCommand;
* It is also possible to ask for the full and updated slots map directly to one
* of the nodes and optionally enable such a behaviour upon -MOVED redirections.
* Asking for the cluster configuration to Redis is actually done by issuing a
* CLUSTER NODES command to a random node in the pool.
* CLUSTER SLOTS command to a random node in the pool.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Countable
{
private $askClusterNodes = false;
private $askClusterNodes = true;
private $defaultParameters = array();
private $pool = array();
private $slots = array();
@@ -169,31 +169,35 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
}
/**
* Generates the current slots map by fetching the cluster configuration to
* one of the nodes by leveraging the CLUSTER NODES command.
* Generates an updated slots map fetching the cluster configuration using
* the CLUSTER SLOTS command against the specified node or a random one from
* the pool.
*
* @param SingleConnectionInterface $connection Optional connection instance.
* @return array
*/
public function askClusterNodes()
public function askClusterNodes(SingleConnectionInterface $connection = null)
{
if (!$connection = $this->getRandomConnection()) {
if (!$connection && !$connection = $this->getRandomConnection()) {
return array();
}
$cmdCluster = RawCommand::create('CLUSTER', 'NODES');
$response = $connection->executeCommand($cmdCluster);
$command = RawCommand::create('CLUSTER', 'SLOTS');
$response = $connection->executeCommand($command);
$nodes = explode("\n", $response, -1);
$count = count($nodes);
foreach ($response as $slots) {
// We only support master servers for now, so we ignore subsequent
// elements in the $slots array identifying slaves.
list($start, $end, $master) = $slots;
for ($i = 0; $i < $count; $i++) {
$node = explode(' ', $nodes[$i], 9);
$slots = explode('-', $node[8], 2);
if ($node[1] === ':0') {
$this->setSlots($slots[0], $slots[1], (string) $connection);
if ($master[0] === '') {
$this->setSlots($start, $end, (string) $connection);
} else {
$this->setSlots($slots[0], $slots[1], $node[1]);
$this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
}
}
return $this->slotsMap;
}
/**
@@ -400,7 +404,7 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
}
if ($this->askClusterNodes) {
$this->askClusterNodes();
$this->askClusterNodes($connection);
}
$this->move($connection, $slot);
@@ -490,8 +494,8 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
/**
* Enables automatic fetching of the current slots map from one of the nodes
* using the CLUSTER NODES command. This option is disabled by default but
* asking the current slots map to Redis upon -MOVE responses may reduce
* using the CLUSTER SLOTS command. This option is disabled by default but
* asking the current slots map to Redis upon -MOVED responses may reduce
* overhead by eliminating the trial-and-error nature of the node guessing
* procedure, mostly when targeting many keys that would end up in a lot of
* redirections.
@@ -499,7 +503,7 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
* The slots map can still be manually fetched using the askClusterNodes()
* method whether or not this option is enabled.
*
* @param bool $value Enable or disable the use of CLUSTER NODES.
* @param bool $value Enable or disable the use of CLUSTER SLOTS.
*/
public function enableClusterNodes($value)
{
+3 -3
View File
@@ -76,7 +76,7 @@ class StreamConnection extends AbstractConnection
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
* @return resource
*/
private function tcpStreamInitializer(ConnectionParametersInterface $parameters)
protected function tcpStreamInitializer(ConnectionParametersInterface $parameters)
{
$uri = "tcp://{$parameters->host}:{$parameters->port}";
$flags = STREAM_CLIENT_CONNECT;
@@ -104,7 +104,7 @@ class StreamConnection extends AbstractConnection
stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
}
if (isset($parameters->tcp_nodelay) && version_compare(PHP_VERSION, '5.4.0') >= 0) {
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
$socket = socket_import_stream($resource);
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
}
@@ -183,7 +183,7 @@ class StreamConnection extends AbstractConnection
$socket = $this->getResource();
while (($length = strlen($buffer)) > 0) {
$written = fwrite($socket, $buffer);
$written = @fwrite($socket, $buffer);
if ($length === $written) {
return;
@@ -14,7 +14,6 @@ namespace Predis\Connection;
use Predis\NotSupportedException;
use Predis\ResponseError;
use Predis\Command\CommandInterface;
use Predis\Connection\ConnectionException;
use Predis\Protocol\ProtocolException;
/**
@@ -50,7 +50,7 @@ class ClientConnectionFactory extends AbstractOption
return $factory;
}
if (@class_exists($value)) {
if (is_string($value) && @class_exists($value)) {
$factory = new $value();
if (!$factory instanceof ConnectionFactoryInterface) {
+3 -3
View File
@@ -65,8 +65,8 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
/**
* Queues a command into the pipeline buffer.
*
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @param string $method Command ID.
* @param array $arguments Arguments for the command.
* @return $this
*/
public function __call($method, $arguments)
@@ -90,7 +90,7 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
/**
* Queues a command instance into the pipeline buffer.
*
* @param CommandInterface $command Command to queue in the buffer.
* @param CommandInterface $command Command to queue in the buffer.
* @return $this
*/
public function executeCommand(CommandInterface $command)
+2 -1
View File
@@ -78,7 +78,8 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
'2.4' => 'Predis\Profile\ServerVersion24',
'2.6' => 'Predis\Profile\ServerVersion26',
'2.8' => 'Predis\Profile\ServerVersion28',
'default' => 'Predis\Profile\ServerVersion26',
'3.0' => 'Predis\Profile\ServerVersion30',
'default' => 'Predis\Profile\ServerVersion28',
'dev' => 'Predis\Profile\ServerVersionNext',
);
}
+11
View File
@@ -239,9 +239,20 @@ class ServerVersion28 extends ServerProfile
/* commands operating on sorted sets */
'zscan' => 'Predis\Command\ZSetScan',
'zlexcount' => 'Predis\Command\ZSetLexCount',
'zrangebylex' => 'Predis\Command\ZSetRangeByLex',
'zremrangebylex' => 'Predis\Command\ZSetRemoveRangeByLex',
/* commands operating on hashes */
'hscan' => 'Predis\Command\HashScan',
/* commands operating on HyperLogLog */
'pfadd' => 'Predis\Command\HyperLogLogAdd',
'pfcount' => 'Predis\Command\HyperLogLogCount',
'pfmerge' => 'Predis\Command\HyperLogLogMerge',
/* remote server control commands */
'command' => 'Predis\Command\ServerCommand',
);
}
}
+261
View File
@@ -0,0 +1,261 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Profile;
/**
* Server profile for Redis v3.0.x.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersion30 extends ServerProfile
{
/**
* {@inheritdoc}
*/
public function getVersion()
{
return '3.0';
}
/**
* {@inheritdoc}
*/
public function getSupportedCommands()
{
return array(
/* ---------------- Redis 1.2 ---------------- */
/* commands operating on the key space */
'exists' => 'Predis\Command\KeyExists',
'del' => 'Predis\Command\KeyDelete',
'type' => 'Predis\Command\KeyType',
'keys' => 'Predis\Command\KeyKeys',
'randomkey' => 'Predis\Command\KeyRandom',
'rename' => 'Predis\Command\KeyRename',
'renamenx' => 'Predis\Command\KeyRenamePreserve',
'expire' => 'Predis\Command\KeyExpire',
'expireat' => 'Predis\Command\KeyExpireAt',
'ttl' => 'Predis\Command\KeyTimeToLive',
'move' => 'Predis\Command\KeyMove',
'sort' => 'Predis\Command\KeySort',
'dump' => 'Predis\Command\KeyDump',
'restore' => 'Predis\Command\KeyRestore',
/* commands operating on string values */
'set' => 'Predis\Command\StringSet',
'setnx' => 'Predis\Command\StringSetPreserve',
'mset' => 'Predis\Command\StringSetMultiple',
'msetnx' => 'Predis\Command\StringSetMultiplePreserve',
'get' => 'Predis\Command\StringGet',
'mget' => 'Predis\Command\StringGetMultiple',
'getset' => 'Predis\Command\StringGetSet',
'incr' => 'Predis\Command\StringIncrement',
'incrby' => 'Predis\Command\StringIncrementBy',
'decr' => 'Predis\Command\StringDecrement',
'decrby' => 'Predis\Command\StringDecrementBy',
/* commands operating on lists */
'rpush' => 'Predis\Command\ListPushTail',
'lpush' => 'Predis\Command\ListPushHead',
'llen' => 'Predis\Command\ListLength',
'lrange' => 'Predis\Command\ListRange',
'ltrim' => 'Predis\Command\ListTrim',
'lindex' => 'Predis\Command\ListIndex',
'lset' => 'Predis\Command\ListSet',
'lrem' => 'Predis\Command\ListRemove',
'lpop' => 'Predis\Command\ListPopFirst',
'rpop' => 'Predis\Command\ListPopLast',
'rpoplpush' => 'Predis\Command\ListPopLastPushHead',
/* commands operating on sets */
'sadd' => 'Predis\Command\SetAdd',
'srem' => 'Predis\Command\SetRemove',
'spop' => 'Predis\Command\SetPop',
'smove' => 'Predis\Command\SetMove',
'scard' => 'Predis\Command\SetCardinality',
'sismember' => 'Predis\Command\SetIsMember',
'sinter' => 'Predis\Command\SetIntersection',
'sinterstore' => 'Predis\Command\SetIntersectionStore',
'sunion' => 'Predis\Command\SetUnion',
'sunionstore' => 'Predis\Command\SetUnionStore',
'sdiff' => 'Predis\Command\SetDifference',
'sdiffstore' => 'Predis\Command\SetDifferenceStore',
'smembers' => 'Predis\Command\SetMembers',
'srandmember' => 'Predis\Command\SetRandomMember',
/* commands operating on sorted sets */
'zadd' => 'Predis\Command\ZSetAdd',
'zincrby' => 'Predis\Command\ZSetIncrementBy',
'zrem' => 'Predis\Command\ZSetRemove',
'zrange' => 'Predis\Command\ZSetRange',
'zrevrange' => 'Predis\Command\ZSetReverseRange',
'zrangebyscore' => 'Predis\Command\ZSetRangeByScore',
'zcard' => 'Predis\Command\ZSetCardinality',
'zscore' => 'Predis\Command\ZSetScore',
'zremrangebyscore' => 'Predis\Command\ZSetRemoveRangeByScore',
/* connection related commands */
'ping' => 'Predis\Command\ConnectionPing',
'auth' => 'Predis\Command\ConnectionAuth',
'select' => 'Predis\Command\ConnectionSelect',
'echo' => 'Predis\Command\ConnectionEcho',
'quit' => 'Predis\Command\ConnectionQuit',
/* remote server control commands */
'info' => 'Predis\Command\ServerInfoV26x',
'slaveof' => 'Predis\Command\ServerSlaveOf',
'monitor' => 'Predis\Command\ServerMonitor',
'dbsize' => 'Predis\Command\ServerDatabaseSize',
'flushdb' => 'Predis\Command\ServerFlushDatabase',
'flushall' => 'Predis\Command\ServerFlushAll',
'save' => 'Predis\Command\ServerSave',
'bgsave' => 'Predis\Command\ServerBackgroundSave',
'lastsave' => 'Predis\Command\ServerLastSave',
'shutdown' => 'Predis\Command\ServerShutdown',
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
/* ---------------- Redis 2.0 ---------------- */
/* commands operating on string values */
'setex' => 'Predis\Command\StringSetExpire',
'append' => 'Predis\Command\StringAppend',
'substr' => 'Predis\Command\StringSubstr',
/* commands operating on lists */
'blpop' => 'Predis\Command\ListPopFirstBlocking',
'brpop' => 'Predis\Command\ListPopLastBlocking',
/* commands operating on sorted sets */
'zunionstore' => 'Predis\Command\ZSetUnionStore',
'zinterstore' => 'Predis\Command\ZSetIntersectionStore',
'zcount' => 'Predis\Command\ZSetCount',
'zrank' => 'Predis\Command\ZSetRank',
'zrevrank' => 'Predis\Command\ZSetReverseRank',
'zremrangebyrank' => 'Predis\Command\ZSetRemoveRangeByRank',
/* commands operating on hashes */
'hset' => 'Predis\Command\HashSet',
'hsetnx' => 'Predis\Command\HashSetPreserve',
'hmset' => 'Predis\Command\HashSetMultiple',
'hincrby' => 'Predis\Command\HashIncrementBy',
'hget' => 'Predis\Command\HashGet',
'hmget' => 'Predis\Command\HashGetMultiple',
'hdel' => 'Predis\Command\HashDelete',
'hexists' => 'Predis\Command\HashExists',
'hlen' => 'Predis\Command\HashLength',
'hkeys' => 'Predis\Command\HashKeys',
'hvals' => 'Predis\Command\HashValues',
'hgetall' => 'Predis\Command\HashGetAll',
/* transactions */
'multi' => 'Predis\Command\TransactionMulti',
'exec' => 'Predis\Command\TransactionExec',
'discard' => 'Predis\Command\TransactionDiscard',
/* publish - subscribe */
'subscribe' => 'Predis\Command\PubSubSubscribe',
'unsubscribe' => 'Predis\Command\PubSubUnsubscribe',
'psubscribe' => 'Predis\Command\PubSubSubscribeByPattern',
'punsubscribe' => 'Predis\Command\PubSubUnsubscribeByPattern',
'publish' => 'Predis\Command\PubSubPublish',
/* remote server control commands */
'config' => 'Predis\Command\ServerConfig',
/* ---------------- Redis 2.2 ---------------- */
/* commands operating on the key space */
'persist' => 'Predis\Command\KeyPersist',
/* commands operating on string values */
'strlen' => 'Predis\Command\StringStrlen',
'setrange' => 'Predis\Command\StringSetRange',
'getrange' => 'Predis\Command\StringGetRange',
'setbit' => 'Predis\Command\StringSetBit',
'getbit' => 'Predis\Command\StringGetBit',
/* commands operating on lists */
'rpushx' => 'Predis\Command\ListPushTailX',
'lpushx' => 'Predis\Command\ListPushHeadX',
'linsert' => 'Predis\Command\ListInsert',
'brpoplpush' => 'Predis\Command\ListPopLastPushHeadBlocking',
/* commands operating on sorted sets */
'zrevrangebyscore' => 'Predis\Command\ZSetReverseRangeByScore',
/* transactions */
'watch' => 'Predis\Command\TransactionWatch',
'unwatch' => 'Predis\Command\TransactionUnwatch',
/* remote server control commands */
'object' => 'Predis\Command\ServerObject',
'slowlog' => 'Predis\Command\ServerSlowlog',
/* ---------------- Redis 2.4 ---------------- */
/* remote server control commands */
'client' => 'Predis\Command\ServerClient',
/* ---------------- Redis 2.6 ---------------- */
/* commands operating on the key space */
'pttl' => 'Predis\Command\KeyPreciseTimeToLive',
'pexpire' => 'Predis\Command\KeyPreciseExpire',
'pexpireat' => 'Predis\Command\KeyPreciseExpireAt',
/* commands operating on string values */
'psetex' => 'Predis\Command\StringPreciseSetExpire',
'incrbyfloat' => 'Predis\Command\StringIncrementByFloat',
'bitop' => 'Predis\Command\StringBitOp',
'bitcount' => 'Predis\Command\StringBitCount',
/* commands operating on hashes */
'hincrbyfloat' => 'Predis\Command\HashIncrementByFloat',
/* scripting */
'eval' => 'Predis\Command\ServerEval',
'evalsha' => 'Predis\Command\ServerEvalSHA',
'script' => 'Predis\Command\ServerScript',
/* remote server control commands */
'time' => 'Predis\Command\ServerTime',
/* ---------------- Redis 2.8 ---------------- */
/* commands operating on the key space */
'scan' => 'Predis\Command\KeyScan',
/* commands operating on sets */
'sscan' => 'Predis\Command\SetScan',
/* commands operating on sorted sets */
'zscan' => 'Predis\Command\ZSetScan',
'zlexcount' => 'Predis\Command\ZSetLexCount',
'zrangebylex' => 'Predis\Command\ZSetRangeByLex',
'zremrangebylex' => 'Predis\Command\ZSetRemoveRangeByLex',
/* commands operating on hashes */
'hscan' => 'Predis\Command\HashScan',
/* commands operating on HyperLogLog */
'pfadd' => 'Predis\Command\HyperLogLogAdd',
'pfcount' => 'Predis\Command\HyperLogLogCount',
'pfmerge' => 'Predis\Command\HyperLogLogMerge',
/* remote server control commands */
'command' => 'Predis\Command\ServerCommand',
/* ---------------- Redis 3.0 ---------------- */
);
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace Predis\Profile;
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ServerVersionNext extends ServerVersion28
class ServerVersionNext extends ServerVersion30
{
/**
* {@inheritdoc}
@@ -24,6 +24,7 @@ abstract class AbstractPubSubContext implements \Iterator
const PUNSUBSCRIBE = 'punsubscribe';
const MESSAGE = 'message';
const PMESSAGE = 'pmessage';
const PONG = 'pong';
const STATUS_VALID = 1; // 0b0001
const STATUS_SUBSCRIBED = 2; // 0b0010
@@ -51,6 +52,17 @@ abstract class AbstractPubSubContext implements \Iterator
return ($this->statusFlags & $value) === $value;
}
/**
* PING the server with an optional payload that will be echoed as a
* PONG message in the pub/sub loop.
*
* @param string $payload Optional PING payload.
*/
public function ping($payload = null)
{
$this->writeCommand('PING', array($payload));
}
/**
* Subscribes to the specified channels.
*
+6
View File
@@ -133,6 +133,12 @@ class PubSubContext extends AbstractPubSubContext
'payload' => $response[3],
);
case self::PONG:
return (object) array(
'kind' => $response[0],
'payload' => $response[1],
);
default:
$message = "Received an unknown message type {$response[0]} inside of a pubsub context";
throw new ClientException($message);
@@ -201,6 +201,8 @@ class ReplicationStrategy
'ZRANK' => true,
'ZREVRANK' => true,
'ZSCAN' => true,
'ZLEXCOUNT' => true,
'ZRANGEBYLEX' => true,
'HGET' => true,
'HMGET' => true,
'HEXISTS' => true,
@@ -217,6 +219,7 @@ class ReplicationStrategy
'OBJECT' => true,
'BITCOUNT' => true,
'TIME' => true,
'PFCOUNT' => true,
'SORT' => array($this, 'isSortReadOnly'),
);
}
+12 -9
View File
@@ -123,11 +123,11 @@ class MultiExecContext implements BasicClientInterface, ExecutableContextInterfa
$profile = $client->getProfile();
if ($profile->supportsCommands(array('multi', 'exec', 'discard')) === false) {
if ($profile->supportsCommands(array('MULTI', 'EXEC', 'DISCARD')) === false) {
throw new NotSupportedException('The current profile does not support MULTI, EXEC and DISCARD');
}
$this->canWatch = $profile->supportsCommands(array('watch', 'unwatch'));
$this->canWatch = $profile->supportsCommands(array('WATCH', 'UNWATCH'));
}
/**
@@ -200,23 +200,26 @@ class MultiExecContext implements BasicClientInterface, ExecutableContextInterfa
/**
* Executes the specified Redis command.
*
* @param CommandInterface $command Command instance.
* @param CommandInterface $command Command instance.
* @return $this|mixed
*/
public function executeCommand(CommandInterface $command)
{
$this->initialize();
$response = $this->client->executeCommand($command);
if ($this->checkState(self::STATE_CAS)) {
return $response;
return $this->client->executeCommand($command);
}
if (!$response instanceof ResponseQueued) {
$this->onProtocolError('The server did not respond with a QUEUED status reply');
}
$response = $this->client->getConnection()->executeCommand($command);
$this->commands->enqueue($command);
if ($response instanceof ResponseQueued) {
$this->commands->enqueue($command);
} elseif ($response instanceof ResponseErrorInterface) {
throw new AbortedMultiExecException($this, $response->getMessage());
} else {
$this->onProtocolError('The server did not return a +QUEUED status response.');
}
return $this;
}
+1 -2
View File
@@ -10,7 +10,7 @@ name = "Predis"
desc = "Flexible and feature-complete PHP client library for Redis"
homepage = "http://github.com/nrk/predis"
license = "MIT"
version = "0.8.5"
version = "0.8.7"
stability = "stable"
channel = "pear.nrk.io"
@@ -24,7 +24,6 @@ pearinstaller = "1.4.1"
*.xml.dist = test
*.md = doc
LICENSE = doc
lib = php
[optional phpiredis]
hint = "Add support for faster protocol handling with phpiredis"
+89 -22
View File
@@ -20,6 +20,8 @@ use Predis\Profile\ServerProfileInterface;
*/
abstract class PredisTestCase extends PHPUnit_Framework_TestCase
{
protected $redisServerVersion = null;
/**
* Verifies that a Redis command is a valid Predis\Command\CommandInterface
* instance with the specified ID and command arguments.
@@ -164,13 +166,17 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
}
/**
* @param string $expectedVersion Expected redis version.
* @param string $operator Comparison operator.
* @param callable $callback Callback for matching version.
* @throws PHPUnit_Framework_SkippedTestError When expected redis version is not met.
* Returns the server version of the Redis instance used by the test suite.
*
* @return string
* @throws RuntimeException When the client cannot retrieve the current server version
*/
protected function executeOnRedisVersion($expectedVersion, $operator, $callback)
protected function getRedisServerVersion()
{
if (isset($this->redisServerVersion)) {
return $this->redisServerVersion;
}
$client = $this->createClient(null, null, true);
$info = array_change_key_case($client->info());
@@ -184,6 +190,20 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
throw new RuntimeException('Unable to retrieve server info');
}
$this->redisServerVersion = $version;
return $version;
}
/**
* @param string $expectedVersion Expected redis version.
* @param string $operator Comparison operator.
* @param callable $callback Callback for matching version.
* @throws PHPUnit_Framework_SkippedTestError When expected redis version is not met
*/
protected function executeOnRedisVersion($expectedVersion, $operator, $callback)
{
$version = $this->getRedisServerVersion();
$comparation = version_compare($version, $expectedVersion);
if ($match = eval("return $comparation $operator 0;")) {
@@ -211,23 +231,6 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
return $match;
}
/**
* @param string $expectedVersion Expected redis version.
* @param string $message Optional message.
* @param bool $remote Based on local profile or remote redis version.
* @throws RuntimeException when unable to retrieve server info or redis version
* @throws \PHPUnit_Framework_SkippedTestError when expected redis version is not met
*/
public function markTestSkippedOnRedisVersionBelow($expectedVersion, $message = '', $remote = true)
{
$callback = function ($test, $version) use ($message, $expectedVersion) {
$test->markTestSkipped($message ?: "Test requires Redis $expectedVersion, current is $version.");
};
$method = $remote ? 'executeOnRedisVersion' : 'executeOnProfileVersion';
$this->$method($expectedVersion, '<', $callback);
}
/**
* Sleep the test case with microseconds resolution.
*
@@ -237,4 +240,68 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
{
usleep($seconds * 1000000);
}
/**
*
*/
protected function setRequiredRedisVersionFromAnnotation()
{
$annotations = $this->getAnnotations();
if (isset($annotations['method']['requiresRedisVersion'], $annotations['method']['group']) &&
!empty($annotations['method']['requiresRedisVersion']) &&
in_array('connected', $annotations['method']['group'])
) {
$this->required['requiresRedisVersion'] = $annotations['method']['requiresRedisVersion'][0];
}
}
/**
*
*/
protected function checkRequiredRedisVersion()
{
if (!isset($this->required['requiresRedisVersion'])) {
return;
}
$srvVersion = $this->getRedisServerVersion();
$expectation = explode(' ', $this->required['requiresRedisVersion'], 2);
if (count($expectation) === 1) {
$expOperator = '>=';
$expVersion = $expectation[0];
} else {
$expOperator = $expectation[0];
$expVersion = $expectation[1];
}
$comparation = version_compare($srvVersion, $expVersion);
if (!$match = eval("return $comparation $expOperator 0;")) {
$this->markTestSkipped(
"This test requires Redis $expOperator $expVersion but the current version is $srvVersion."
);
}
}
/**
* {@inheritdoc}
*/
protected function setRequirementsFromAnnotation()
{
parent::setRequirementsFromAnnotation();
$this->setRequiredRedisVersionFromAnnotation();
}
/**
* {@inheritdoc}
*/
protected function checkRequirements()
{
parent::checkRequirements();
$this->checkRequiredRedisVersion();
}
}
@@ -277,6 +277,7 @@ class PredisClusterHashStrategyTest extends PredisTestCase
'GETSET' => 'keys-first',
'INCR' => 'keys-first',
'INCRBY' => 'keys-first',
'INCRBYFLOAT' => 'keys-first',
'SETBIT' => 'keys-first',
'SETEX' => 'keys-first',
'MSET' => 'keys-interleaved',
@@ -341,6 +342,9 @@ class PredisClusterHashStrategyTest extends PredisTestCase
'ZSCORE' => 'keys-first',
'ZUNIONSTORE' => 'keys-zaggregated',
'ZSCAN' => 'keys-first',
'ZLEXCOUNT' => 'keys-first',
'ZRANGEBYLEX' => 'keys-first',
'ZREMRANGEBYLEX' => 'keys-first',
/* commands operating on hashes */
'HDEL' => 'keys-first',
@@ -358,6 +362,11 @@ class PredisClusterHashStrategyTest extends PredisTestCase
'HVALS' => 'keys-first',
'HSCAN' => 'keys-first',
/* commands operating on HyperLogLog */
'PFADD' => 'keys-first',
'PFCOUNT' => 'keys-all',
'PFMERGE' => 'keys-all',
/* scripting */
'EVAL' => 'keys-script',
'EVALSHA' => 'keys-script',
@@ -22,14 +22,21 @@ class RedisClusterHashStrategyTest extends PredisTestCase
/**
* @group disconnected
*/
public function testDoesNotSupportKeyTags()
public function testSupportsKeyTags()
{
$strategy = $this->getHashStrategy();
$this->assertSame(35910, $strategy->getKeyHash('{foo}'));
$this->assertSame(60032, $strategy->getKeyHash('{foo}:bar'));
$this->assertSame(27528, $strategy->getKeyHash('{foo}:baz'));
$this->assertSame(34064, $strategy->getKeyHash('bar:{foo}:bar'));
$this->assertSame(44950, $strategy->getKeyHash('{foo}'));
$this->assertSame(44950, $strategy->getKeyHash('{foo}:bar'));
$this->assertSame(44950, $strategy->getKeyHash('{foo}:baz'));
$this->assertSame(44950, $strategy->getKeyHash('bar:{foo}:baz'));
$this->assertSame(44950, $strategy->getKeyHash('bar:{foo}:{baz}'));
$this->assertSame(44950, $strategy->getKeyHash('bar:{foo}:baz{}'));
$this->assertSame(9415, $strategy->getKeyHash('{}bar:{foo}:baz'));
$this->assertSame(0, $strategy->getKeyHash(''));
$this->assertSame(31641, $strategy->getKeyHash('{}'));
}
/**
@@ -268,6 +275,8 @@ class RedisClusterHashStrategyTest extends PredisTestCase
'TTL' => 'keys-first',
'PTTL' => 'keys-first',
'SORT' => 'keys-first', // TODO
'DUMP' => 'keys-first',
'RESTORE' => 'keys-first',
/* commands operating on string values */
'APPEND' => 'keys-first',
@@ -281,6 +290,7 @@ class RedisClusterHashStrategyTest extends PredisTestCase
'GETSET' => 'keys-first',
'INCR' => 'keys-first',
'INCRBY' => 'keys-first',
'INCRBYFLOAT' => 'keys-first',
'SETBIT' => 'keys-first',
'SETEX' => 'keys-first',
'MSET' => 'keys-interleaved',
@@ -289,6 +299,7 @@ class RedisClusterHashStrategyTest extends PredisTestCase
'SETRANGE' => 'keys-first',
'STRLEN' => 'keys-first',
'SUBSTR' => 'keys-first',
'BITOP' => 'keys-bitop',
'BITCOUNT' => 'keys-first',
/* commands operating on lists */
@@ -297,8 +308,10 @@ class RedisClusterHashStrategyTest extends PredisTestCase
'LLEN' => 'keys-first',
'LPOP' => 'keys-first',
'RPOP' => 'keys-first',
'RPOPLPUSH' => 'keys-all',
'BLPOP' => 'keys-blockinglist',
'BRPOP' => 'keys-blockinglist',
'BRPOPLPUSH' => 'keys-blockinglist',
'LPUSH' => 'keys-first',
'LPUSHX' => 'keys-first',
'RPUSH' => 'keys-first',
@@ -311,6 +324,12 @@ class RedisClusterHashStrategyTest extends PredisTestCase
/* commands operating on sets */
'SADD' => 'keys-first',
'SCARD' => 'keys-first',
'SDIFF' => 'keys-all',
'SDIFFSTORE' => 'keys-all',
'SINTER' => 'keys-all',
'SINTERSTORE' => 'keys-all',
'SUNION' => 'keys-all',
'SUNIONSTORE' => 'keys-all',
'SISMEMBER' => 'keys-first',
'SMEMBERS' => 'keys-first',
'SSCAN' => 'keys-first',
@@ -323,6 +342,7 @@ class RedisClusterHashStrategyTest extends PredisTestCase
'ZCARD' => 'keys-first',
'ZCOUNT' => 'keys-first',
'ZINCRBY' => 'keys-first',
'ZINTERSTORE' => 'keys-zaggregated',
'ZRANGE' => 'keys-first',
'ZRANGEBYSCORE' => 'keys-first',
'ZRANK' => 'keys-first',
@@ -333,7 +353,11 @@ class RedisClusterHashStrategyTest extends PredisTestCase
'ZREVRANGEBYSCORE' => 'keys-first',
'ZREVRANK' => 'keys-first',
'ZSCORE' => 'keys-first',
'ZUNIONSTORE' => 'keys-zaggregated',
'ZSCAN' => 'keys-first',
'ZLEXCOUNT' => 'keys-first',
'ZRANGEBYLEX' => 'keys-first',
'ZREMRANGEBYLEX' => 'keys-first',
/* commands operating on hashes */
'HDEL' => 'keys-first',
@@ -351,6 +375,11 @@ class RedisClusterHashStrategyTest extends PredisTestCase
'HVALS' => 'keys-first',
'HSCAN' => 'keys-first',
/* commands operating on HyperLogLog */
'PFADD' => 'keys-first',
'PFCOUNT' => 'keys-all',
'PFMERGE' => 'keys-all',
/* scripting */
'EVAL' => 'keys-script',
'EVALSHA' => 'keys-script',
@@ -18,7 +18,7 @@ use Predis\Profile\ServerProfile;
/**
* @group realm-iterators
*/
class SortedSetTest extends PredisTestCase
class SortedSetKeyTest extends PredisTestCase
{
/**
* @group disconnected
@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-hyperloglog
* @todo Add integration tests depending on the minor redis version
*/
class HyperLogLogAddTest extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return 'Predis\Command\HyperLogLogAdd';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return 'PFADD';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
$arguments = array('key', 'a', 'b', 'c');
$expected = array('key', 'a', 'b', 'c');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testFilterArgumentsFieldsAsSingleArray()
{
$arguments = array('key', array('a', 'b', 'c'));
$expected = array('key', 'a', 'b', 'c');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
$command = $this->getCommand();
$this->assertSame(false, $command->parseResponse(0));
$this->assertSame(true, $command->parseResponse(1));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
$redis = $this->getClient();
$redis->lpush('metavars', 'foo', 'hoge');
$redis->pfadd('metavars', 'foofoo');
}
}
@@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-hyperloglog
* @todo Add integration tests depending on the minor redis version
*/
class HyperLogLogCountTest extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return 'Predis\Command\HyperLogLogCount';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return 'PFCOUNT';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
$arguments = array('key:1', 'key:2');
$expected = array('key:1', 'key:2');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testFilterArgumentsFieldsAsSingleArray()
{
$arguments = array(array('key:1', 'key:2'));
$expected = array('key:1', 'key:2');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
$command = $this->getCommand();
$this->assertSame(0, $command->parseResponse(0));
$this->assertSame(1, $command->parseResponse(1));
$this->assertSame(10, $command->parseResponse(10));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
$redis = $this->getClient();
$redis->lpush('metavars', 'foo', 'hoge');
$redis->pfcount('metavars');
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongTypeOfAtLeastOneKey()
{
$redis = $this->getClient();
$redis->pfadd('metavars:1', 'foo', 'hoge');
$redis->lpush('metavars:2', 'foofoo');
$redis->pfcount('metavars:1', 'metavars:2');
}
}
@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-hyperloglog
* @todo Add integration tests depending on the minor redis version
*/
class HyperLogLogMergeTest extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return 'Predis\Command\HyperLogLogMerge';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return 'PFMERGE';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
$arguments = array('key:1', 'key:2', 'key:3');
$expected = array('key:1', 'key:2', 'key:3');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testFilterArgumentsFieldsAsSingleArray()
{
$arguments = array(array('key:1', 'key:2', 'key:3'));
$expected = array('key:1', 'key:2', 'key:3');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
$this->assertSame('OK', $this->getCommand()->parseResponse('OK'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
$redis = $this->getClient();
$redis->pfadd('metavars:1', 'foo', 'hoge');
$redis->lpush('metavars:2', 'foofoo');
$redis->pfmerge('metavars:1', 'metavars:2');
}
}
+4 -1
View File
@@ -69,9 +69,12 @@ class RawCommandTest extends PredisTestCase
}
/**
* The signature of RawCommand::create() requires one argument which is the
* ID of the command (other arguments are fetched dinamically). If the first
* argument is missing, PHP emits an E_WARNING.
*
* @group disconnected
* @expectedException PHPUnit_Framework_Error_Warning
* @expectedExceptionMessage Missing argument 1 for Predis\Command\RawCommand::create()
*/
public function testPHPWarningOnMissingCommandIDWithStaticCreate()
{
+4 -7
View File
@@ -143,11 +143,10 @@ BUFFER;
/**
* @group connected
* @requiresRedisVersion >= 2.6.9
*/
public function testGetsNameOfConnection()
{
$this->markTestSkippedOnRedisVersionBelow('2.6.9');
$redis = $this->getClient();
$clientName = $redis->client('GETNAME');
$this->assertNull($clientName);
@@ -159,11 +158,10 @@ BUFFER;
/**
* @group connected
* @requiresRedisVersion >= 2.6.9
*/
public function testSetsNameOfConnection()
{
$this->markTestSkippedOnRedisVersionBelow('2.6.9');
$redis = $this->getClient();
$expectedConnectionName = 'foo-baz';
@@ -185,13 +183,12 @@ BUFFER;
/**
* @group connected
* @expectedException Predis\ServerException
* @requiresRedisVersion >= 2.6.9
* @dataProvider invalidConnectionNameProvider
* @expectedException Predis\ServerException
*/
public function testInvalidSetNameOfConnection($invalidConnectionName)
{
$this->markTestSkippedOnRedisVersionBelow('2.6.9');
$redis = $this->getClient();
$redis->client('SETNAME', $invalidConnectionName);
}
+118
View File
@@ -0,0 +1,118 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-server
*/
class ServerCommandTest extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return 'Predis\Command\ServerCommand';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return 'COMMAND';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
$arguments = array('INFO', 'DEL');
$expected = array('INFO', 'DEL');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
$raw = array(
array('get', 2, array('readonly', 'fast'), 1, 1, 1),
array('set', -3, array('write', 'denyoom'), 1, 1, 1),
array('watch', -2, array('readonly', 'noscript', 'fast'), 1, -1, 1),
array('unwatch', 1, array('readonly', 'noscript', 'fast'), 0, 0, 0),
array('info', -1, array('readonly', 'loading', 'stale'), 0, 0, 0),
);
$expected = $raw;
$command = $this->getCommand();
$this->assertSame($expected, $command->parseResponse($raw));
}
/**
* @group disconnected
*/
public function testParseEmptyResponse()
{
$raw = array(null);
$expected = array(null);
$command = $this->getCommand();
$this->assertSame($expected, $command->parseResponse($raw));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.13
*/
public function testReturnsEmptyCommandInfoOnNonExistingCommand()
{
$redis = $this->getClient();
$this->assertCount(1, $response = $redis->command('INFO', 'FOOBAR'));
$this->assertSame(array(null), $response);
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.13
*/
public function testReturnsCommandInfoOnExistingCommand()
{
$redis = $this->getClient();
$expected = array(array('command', 0, array('readonly', 'loading', 'stale'), 0, 0, 0));
$this->assertCount(1, $response = $redis->command('INFO', 'COMMAND'));
$this->assertSame($expected, $response);
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.13
*/
public function testReturnsListOfCommandInfoWithNoArguments()
{
$redis = $this->getClient();
$this->assertGreaterThan(100, count($response = $redis->command()));
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-zset
*/
class ZSetLexCountTest extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return 'Predis\Command\ZSetLexCount';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return 'ZLEXCOUNT';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
$arguments = array('key', '+', '-');
$expected = array('key', '+', '-');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group disconnected
*/
public function testPrefixKeys()
{
$arguments = array('key', '+', '-');
$expected = array('prefix:key', '+', '-');
$command = $this->getCommandWithArgumentsArray($arguments);
$command->prefixKeys('prefix:');
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testPrefixKeysIgnoredOnEmptyArguments()
{
$command = $this->getCommand();
$command->prefixKeys('prefix:');
$this->assertSame(array(), $command->getArguments());
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testExclusiveIntervalRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(3, $redis->zlexcount('letters', '(b', '(f'));
$this->assertSame(5, $redis->zlexcount('letters', '(b', '(z'));
$this->assertSame(4, $redis->zlexcount('letters', '(0', '(e'));
$this->assertSame(0, $redis->zlexcount('letters', '(f', '(b'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testInclusiveIntervalRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(5, $redis->zlexcount('letters', '[b', '[f'));
$this->assertSame(6, $redis->zlexcount('letters', '[b', '[z'));
$this->assertSame(5, $redis->zlexcount('letters', '[0', '[e'));
$this->assertSame(0, $redis->zlexcount('letters', '[f', '[b'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testWholeRangeInterval()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(7, $redis->zlexcount('letters', '-', '+'));
$this->assertSame(0, $redis->zlexcount('letters', '+', '-'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage min or max not valid string range item
*/
public function testThrowsExceptionOnInvalidRangeFormat()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$redis->zlexcount('letters', 'b', 'f');
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
$redis = $this->getClient();
$redis->set('foo', 'bar');
$redis->zlexcount('foo', '+', '-');
}
}
+223
View File
@@ -0,0 +1,223 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-zset
*/
class ZSetRangeByLexTest extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return 'Predis\Command\ZSetRangeByLex';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return 'ZRANGEBYLEX';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
$modifiers = array(
'limit' => array(0, 100),
);
$arguments = array('zset', '[a', '[z', $modifiers);
$expected = array('zset', '[a', '[z', 'LIMIT', 0, 100);
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testFilterArgumentsWithNamedLimit()
{
$arguments = array('zset', '[a', '[z', array('limit' => array('offset' => 1, 'count' => 2)));
$expected = array('zset', '[a', '[z', 'LIMIT', 1, 2);
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
$raw = array('a', 'b', 'c');
$expected = array('a', 'b', 'c');
$command = $this->getCommand();
$this->assertSame($expected, $command->parseResponse($raw));
}
/**
* @group disconnected
*/
public function testPrefixKeys()
{
$modifiers = array(
'limit' => array(0, 100),
);
$arguments = array('zset', '[a', '[z', $modifiers);
$expected = array('prefix:zset', '[a', '[z', 'LIMIT', 0, 100);
$command = $this->getCommandWithArgumentsArray($arguments);
$command->prefixKeys('prefix:');
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testPrefixKeysIgnoredOnEmptyArguments()
{
$command = $this->getCommand();
$command->prefixKeys('prefix:');
$this->assertSame(array(), $command->getArguments());
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testReturnsElementsInWholeRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->zrangebylex('letters', '-', '+'));
$this->assertSame(array(), $redis->zrangebylex('letters', '+', '-'));
$this->assertSame(array(), $redis->zrangebylex('unknown', '-', '+'));
$this->assertSame(array(), $redis->zrangebylex('unknown', '+', '-'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testReturnsElementsInInclusiveRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(array('a'), $redis->zrangebylex('letters', '[a', '[a'));
$this->assertSame(array('c', 'd', 'e', 'f'), $redis->zrangebylex('letters', '[c', '[f'));
$this->assertSame(array('a', 'b', 'c'), $redis->zrangebylex('letters', '-', '[c'));
$this->assertSame(array(), $redis->zrangebylex('letters', '+', '[c'));
$this->assertSame(array(), $redis->zrangebylex('letters', '[x', '[z'));
$this->assertSame(array(), $redis->zrangebylex('unknown', '[0', '[1'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testReturnsElementsInExclusiveRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(array(), $redis->zrangebylex('letters', '(a', '(a'));
$this->assertSame(array('d', 'e'), $redis->zrangebylex('letters', '(c', '(f'));
$this->assertSame(array('a', 'b'), $redis->zrangebylex('letters', '-', '(c'));
$this->assertSame(array(), $redis->zrangebylex('letters', '+', '(c'));
$this->assertSame(array(), $redis->zrangebylex('letters', '(x', '(z'));
$this->assertSame(array(), $redis->zrangebylex('unknown', '(0', '(1'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testReturnsElementsInMixedRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(array(), $redis->zrangebylex('letters', '[a', '(a'));
$this->assertSame(array(), $redis->zrangebylex('letters', '(a', '[a'));
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '[c', '(f'));
$this->assertSame(array('d', 'e', 'f'), $redis->zrangebylex('letters', '(c', '[f'));
$this->assertSame(array(), $redis->zrangebylex('unknown', '[0', '(5'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testRangeWithLimitModifier()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '-', '+', 'LIMIT', '2', '3'));
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '-', '+', array('limit' => array(2, 3))));
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '-', '+', array('limit' => array('offset' => 2, 'count' => 3))));
$this->assertSame(array(), $redis->zrangebylex('letters', '[a', '[f', 'LIMIT', '2', '0'));
$this->assertSame(array(), $redis->zrangebylex('letters', '[a', '[f', 'LIMIT', '-4', '2'));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage min or max not valid string range item
*/
public function testThrowsExceptionOnInvalidRangeFormat()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$redis->zrangebylex('letters', 'b', 'f');
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
$redis = $this->getClient();
$redis->set('foo', 'bar');
$redis->zrangebylex('foo', '-', '+');
}
}
@@ -0,0 +1,168 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
/**
* @group commands
* @group realm-zset
*/
class ZSetRemoveRangeByLexTest extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand()
{
return 'Predis\Command\ZSetRemoveRangeByLex';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId()
{
return 'ZREMRANGEBYLEX';
}
/**
* @group disconnected
*/
public function testFilterArguments()
{
$arguments = array('key', '[a', '[b');
$expected = array('key', '[a', '[b');
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse()
{
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group disconnected
*/
public function testPrefixKeys()
{
$arguments = array('key', '[a', '[b');
$expected = array('prefix:key', '[a', '[b');
$command = $this->getCommandWithArgumentsArray($arguments);
$command->prefixKeys('prefix:');
$this->assertSame($expected, $command->getArguments());
}
/**
* @group disconnected
*/
public function testPrefixKeysIgnoredOnEmptyArguments()
{
$command = $this->getCommand();
$command->prefixKeys('prefix:');
$this->assertSame(array(), $command->getArguments());
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testRemovesRangeByLexWithWholeRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(0, $redis->zremrangebylex('letters', '+', '-'));
$this->assertSame(7, $redis->zremrangebylex('letters', '-', '+'));
$this->assertSame(array(), $redis->zrange('letters', 0, -1));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testRemovesRangeByLexWithInclusiveRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(3, $redis->zremrangebylex('letters', '[b', '[d'));
$this->assertSame(array('a', 'e', 'f', 'g'), $redis->zrange('letters', 0, -1));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testRemovesRangeByLexWithExclusiveRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(3, $redis->zremrangebylex('letters', '(a', '(e'));
$this->assertSame(array('a', 'e', 'f', 'g'), $redis->zrange('letters', 0, -1));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
*/
public function testRemovesRangeByLexWithMixedRange()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$this->assertSame(3, $redis->zremrangebylex('letters', '[b', '(e'));
$this->assertSame(array('a', 'e', 'f', 'g'), $redis->zrange('letters', 0, -1));
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage min or max not valid string range item
*/
public function testThrowsExceptionOnInvalidRangeFormat()
{
$redis = $this->getClient();
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
$redis->zremrangebylex('letters', 'b', 'f');
}
/**
* @group connected
* @requiresRedisVersion >= 2.8.9
* @expectedException Predis\ServerException
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
*/
public function testThrowsExceptionOnWrongType()
{
$redis = $this->getClient();
$redis->set('foo', 'bar');
$redis->zremrangebylex('foo', '[a', '[b');
}
}
@@ -14,7 +14,6 @@ namespace Predis\Connection;
require_once __DIR__.'/../CommunicationExceptionTest.php';
use Predis\CommunicationExceptionTest;
use Predis\Connection\SingleConnectionInterface;
/**
*
@@ -16,7 +16,7 @@ use PredisTestCase;
* @todo ConnectionParameters::define();
* @todo ConnectionParameters::undefine();
*/
class ParametersTest extends PredisTestCase
class ConnectionParametersTest extends PredisTestCase
{
/**
* @group disconnected
+93 -3
View File
@@ -13,6 +13,7 @@ namespace Predis\Connection;
use PredisTestCase;
use Predis\ResponseError;
use Predis\Command\RawCommand;
use Predis\Profile\ServerProfile;
/**
@@ -397,6 +398,7 @@ class RedisClusterTest extends PredisTestCase
$connection2->expects($this->never())->method('writeCommand');
$cluster = new RedisCluster();
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -417,6 +419,7 @@ class RedisClusterTest extends PredisTestCase
$connection2->expects($this->once())->method('readResponse')->with($command);
$cluster = new RedisCluster();
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -426,7 +429,7 @@ class RedisClusterTest extends PredisTestCase
/**
* @group disconnected
*/
public function testDoesNotSupportKeyTags()
public function testSupportsKeyTags()
{
$profile = ServerProfile::getDefault();
@@ -444,8 +447,8 @@ class RedisClusterTest extends PredisTestCase
$set = $profile->createCommand('set', array('{node:1001}:bar', 'foobar'));
$get = $profile->createCommand('get', array('{node:1001}:bar'));
$this->assertSame($connection2, $cluster->getConnection($set));
$this->assertSame($connection2, $cluster->getConnection($get));
$this->assertSame($connection1, $cluster->getConnection($set));
$this->assertSame($connection1, $cluster->getConnection($get));
}
/**
@@ -476,6 +479,7 @@ class RedisClusterTest extends PredisTestCase
$factory->expects($this->never())->method('create');
$cluster = new RedisCluster($factory);
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -519,6 +523,7 @@ class RedisClusterTest extends PredisTestCase
->will($this->returnValue($connection3));
$cluster = new RedisCluster($factory);
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -552,6 +557,7 @@ class RedisClusterTest extends PredisTestCase
$factory->expects($this->never())->method('create');
$cluster = new RedisCluster($factory);
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -592,6 +598,7 @@ class RedisClusterTest extends PredisTestCase
->will($this->returnValue($connection3));
$cluster = new RedisCluster($factory);
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -600,6 +607,89 @@ class RedisClusterTest extends PredisTestCase
$this->assertSame(3, count($cluster));
}
/**
* @group disconnected
*/
public function testFetchSlotsMapFromClusterWithClusterSlotsCommand()
{
$response = array(
array(12288, 13311, array('10.1.0.51', 6387), array('10.1.0.52', 6387)),
array(3072 , 4095, array('10.1.0.52', 6392), array('10.1.0.51', 6392)),
array(6144 , 7167, array('', 6384), array('10.1.0.52', 6384)),
array(14336, 15359, array('10.1.0.51', 6388), array('10.1.0.52', 6388)),
array(15360, 16383, array('10.1.0.52', 6398), array('10.1.0.51', 6398)),
array(1024 , 2047, array('10.1.0.52', 6391), array('10.1.0.51', 6391)),
array(11264, 12287, array('10.1.0.52', 6396), array('10.1.0.51', 6396)),
array( 5120, 6143, array('10.1.0.52', 6393), array('10.1.0.51', 6393)),
array( 0, 1023, array('10.1.0.51', 6381), array('10.1.0.52', 6381)),
array(13312, 14335, array('10.1.0.52', 6397), array('10.1.0.51', 6397)),
array( 4096, 5119, array('10.1.0.51', 6383), array('10.1.0.52', 6383)),
array( 9216, 10239, array('10.1.0.52', 6395), array('10.1.0.51', 6395)),
array( 8192, 9215, array('10.1.0.51', 6385), array('10.1.0.52', 6385)),
array(10240, 11263, array('10.1.0.51', 6386), array('10.1.0.52', 6386)),
array( 2048, 3071, array('10.1.0.51', 6382), array('10.1.0.52', 6382)),
array( 7168, 8191, array('10.1.0.52', 6394), array('10.1.0.51', 6394)),
);
$command = RawCommand::create('CLUSTER', 'SLOTS');
$connection1 = $this->getMockConnection('tcp://10.1.0.51:6384');
$connection1->expects($this->once())
->method('executeCommand')
->with($command)
->will($this->returnValue($response));
$factory = $this->getMock('Predis\Connection\ConnectionFactory');
$cluster = new RedisCluster($factory);
$cluster->add($connection1);
$cluster->askClusterNodes();
$this->assertSame($cluster->getConnectionBySlot('6144'), $connection1);
}
/**
* @group disconnected
*/
public function testAskSlotsMapToRedisClusterOnMovedResponseByDefault()
{
$cmdGET = RawCommand::create('GET', 'node:1001');
$rspMOVED = new ResponseError('MOVED 1970 127.0.0.1:6380');
$rspSlotsArray = array(
array(0 , 8191, array('127.0.0.1', 6379)),
array(8192, 16383, array('127.0.0.1', 6380)),
);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->once())
->method('executeCommand')
->with($cmdGET)
->will($this->returnValue($rspMOVED));
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
$connection2->expects($this->at(0))
->method('executeCommand')
->with($this->isRedisCommand('CLUSTER', array('SLOTS')))
->will($this->returnValue($rspSlotsArray));
$connection2->expects($this->at(2))
->method('executeCommand')
->with($cmdGET)
->will($this->returnValue('foobar'));
$factory = $this->getMock('Predis\Connection\ConnectionFactory');
$factory->expects($this->once())
->method('create')
->with(array('host' => '127.0.0.1', 'port' => '6380'))
->will($this->returnValue($connection2));
$cluster = new RedisCluster($factory);
$cluster->add($connection1);
$this->assertSame('foobar', $cluster->executeCommand($cmdGET));
$this->assertSame(2, count($cluster));
}
/**
* @group disconnected
* @expectedException Predis\NotSupportedException
+1 -1
View File
@@ -19,7 +19,7 @@ use Predis\Command\Processor\ProcessorChain;
*/
class ServerProfileTest extends PredisTestCase
{
const DEFAULT_PROFILE_VERSION = '2.6';
const DEFAULT_PROFILE_VERSION = '2.8';
const DEVELOPMENT_PROFILE_VERSION = '3.0';
/**
+8 -1
View File
@@ -176,7 +176,14 @@ class ServerVersion28Test extends PredisProfileTestCase
135 => 'scan',
136 => 'sscan',
137 => 'zscan',
138 => 'hscan',
138 => 'zlexcount',
139 => 'zrangebylex',
140 => 'zremrangebylex',
141 => 'hscan',
142 => 'pfadd',
143 => 'pfcount',
144 => 'pfmerge',
145 => 'command',
);
}
}
@@ -0,0 +1,189 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Profile;
/**
*
*/
class ServerVersion30Test extends PredisProfileTestCase
{
/**
* {@inheritdoc}
*/
public function getProfile($version = null)
{
return new ServerVersion30();
}
/**
* {@inheritdoc}
*/
public function getExpectedVersion()
{
return '3.0';
}
/**
* {@inheritdoc}
*/
public function getExpectedCommands()
{
return array(
0 => 'exists',
1 => 'del',
2 => 'type',
3 => 'keys',
4 => 'randomkey',
5 => 'rename',
6 => 'renamenx',
7 => 'expire',
8 => 'expireat',
9 => 'ttl',
10 => 'move',
11 => 'sort',
12 => 'dump',
13 => 'restore',
14 => 'set',
15 => 'setnx',
16 => 'mset',
17 => 'msetnx',
18 => 'get',
19 => 'mget',
20 => 'getset',
21 => 'incr',
22 => 'incrby',
23 => 'decr',
24 => 'decrby',
25 => 'rpush',
26 => 'lpush',
27 => 'llen',
28 => 'lrange',
29 => 'ltrim',
30 => 'lindex',
31 => 'lset',
32 => 'lrem',
33 => 'lpop',
34 => 'rpop',
35 => 'rpoplpush',
36 => 'sadd',
37 => 'srem',
38 => 'spop',
39 => 'smove',
40 => 'scard',
41 => 'sismember',
42 => 'sinter',
43 => 'sinterstore',
44 => 'sunion',
45 => 'sunionstore',
46 => 'sdiff',
47 => 'sdiffstore',
48 => 'smembers',
49 => 'srandmember',
50 => 'zadd',
51 => 'zincrby',
52 => 'zrem',
53 => 'zrange',
54 => 'zrevrange',
55 => 'zrangebyscore',
56 => 'zcard',
57 => 'zscore',
58 => 'zremrangebyscore',
59 => 'ping',
60 => 'auth',
61 => 'select',
62 => 'echo',
63 => 'quit',
64 => 'info',
65 => 'slaveof',
66 => 'monitor',
67 => 'dbsize',
68 => 'flushdb',
69 => 'flushall',
70 => 'save',
71 => 'bgsave',
72 => 'lastsave',
73 => 'shutdown',
74 => 'bgrewriteaof',
75 => 'setex',
76 => 'append',
77 => 'substr',
78 => 'blpop',
79 => 'brpop',
80 => 'zunionstore',
81 => 'zinterstore',
82 => 'zcount',
83 => 'zrank',
84 => 'zrevrank',
85 => 'zremrangebyrank',
86 => 'hset',
87 => 'hsetnx',
88 => 'hmset',
89 => 'hincrby',
90 => 'hget',
91 => 'hmget',
92 => 'hdel',
93 => 'hexists',
94 => 'hlen',
95 => 'hkeys',
96 => 'hvals',
97 => 'hgetall',
98 => 'multi',
99 => 'exec',
100 => 'discard',
101 => 'subscribe',
102 => 'unsubscribe',
103 => 'psubscribe',
104 => 'punsubscribe',
105 => 'publish',
106 => 'config',
107 => 'persist',
108 => 'strlen',
109 => 'setrange',
110 => 'getrange',
111 => 'setbit',
112 => 'getbit',
113 => 'rpushx',
114 => 'lpushx',
115 => 'linsert',
116 => 'brpoplpush',
117 => 'zrevrangebyscore',
118 => 'watch',
119 => 'unwatch',
120 => 'object',
121 => 'slowlog',
122 => 'client',
123 => 'pttl',
124 => 'pexpire',
125 => 'pexpireat',
126 => 'psetex',
127 => 'incrbyfloat',
128 => 'bitop',
129 => 'bitcount',
130 => 'hincrbyfloat',
131 => 'eval',
132 => 'evalsha',
133 => 'script',
134 => 'time',
135 => 'scan',
136 => 'sscan',
137 => 'zscan',
138 => 'zlexcount',
139 => 'zrangebylex',
140 => 'zremrangebylex',
141 => 'hscan',
142 => 'pfadd',
143 => 'pfcount',
144 => 'pfmerge',
145 => 'command',
);
}
}
@@ -176,7 +176,14 @@ class ServerVersionNextTest extends PredisProfileTestCase
135 => 'scan',
136 => 'sscan',
137 => 'zscan',
138 => 'hscan',
138 => 'zlexcount',
139 => 'zrangebylex',
140 => 'zremrangebylex',
141 => 'hscan',
142 => 'pfadd',
143 => 'pfcount',
144 => 'pfmerge',
145 => 'command',
);
}
}
+34 -1
View File
@@ -144,6 +144,39 @@ class PubSubContextTest extends PredisTestCase
$this->assertNull($pubsub->next());
}
public function testHandlesPongMessages()
{
$rawmessage = array('pong', '');
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
$connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
$client = new Client($connection);
$pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
$message = $pubsub->current();
$this->assertSame('pong', $message->kind);
$this->assertSame('', $message->payload);
}
/**
* @group disconnected
*/
public function testHandlesPongMessagesWithPayload()
{
$rawmessage = array('pong', 'foobar');
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
$connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
$client = new Client($connection);
$pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
$message = $pubsub->current();
$this->assertSame('pong', $message->kind);
$this->assertSame('foobar', $message->payload);
}
/**
* @group disconnected
*/
@@ -302,6 +335,6 @@ class PubSubContextTest extends PredisTestCase
$this->assertSame(array('message1', 'message2', 'QUIT'), $messages);
$this->assertFalse($pubsub->valid());
$this->assertTrue($consumer->ping());
$this->assertEquals('ECHO', $consumer->echo('ECHO'));
}
}
@@ -286,6 +286,7 @@ class ReplicationStrategyTest extends PredisTestCase
'GETSET' => 'write',
'INCR' => 'write',
'INCRBY' => 'write',
'INCRBYFLOAT' => 'write',
'SETBIT' => 'write',
'SETEX' => 'write',
'MSET' => 'write',
@@ -341,6 +342,9 @@ class ReplicationStrategyTest extends PredisTestCase
'ZREVRANK' => 'read',
'ZSCORE' => 'read',
'ZSCAN' => 'read',
'ZLEXCOUNT' => 'read',
'ZRANGEBYLEX' => 'read',
'ZREMRANGEBYLEX' => 'write',
/* commands operating on hashes */
'HDEL' => 'write',
@@ -357,6 +361,11 @@ class ReplicationStrategyTest extends PredisTestCase
'HVALS' => 'read',
'HSCAN' => 'read',
/* commands operating on HyperLogLog */
'PFADD' => 'write',
'PFMERGE' => 'write',
'PFCOUNT' => 'read',
/* scripting */
'EVAL' => 'write',
'EVALSHA' => 'write',
@@ -14,6 +14,7 @@ namespace Predis\Transaction;
use PredisTestCase;
use Predis\Client;
use Predis\ResponseQueued;
use Predis\ResponseError;
use Predis\ServerException;
use Predis\Command\CommandInterface;
@@ -29,8 +30,15 @@ class MultiExecContextTest extends PredisTestCase
*/
public function testThrowsExceptionOnUnsupportedMultiExecInProfile()
{
$profile = $this->getMock('Predis\Profile\ServerProfileInterface');
$profile->expects($this->once())
->method('supportsCommands')
->with(array('MULTI', 'EXEC', 'DISCARD'))
->will($this->returnValue(false));
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
$client = new Client($connection, array('profile' => '1.2'));
$client = new Client($connection, array('profile' => $profile));
$tx = new MultiExecContext($client);
}
@@ -41,10 +49,20 @@ class MultiExecContextTest extends PredisTestCase
*/
public function testThrowsExceptionOnUnsupportedWatchUnwatchInProfile()
{
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
$client = new Client($connection, array('profile' => '2.0'));
$tx = new MultiExecContext($client, array('options' => 'cas'));
$profile = $this->getMock('Predis\Profile\ServerProfileInterface');
$profile->expects($this->at(0))
->method('supportsCommands')
->with(array('MULTI', 'EXEC', 'DISCARD'))
->will($this->returnValue(true));
$profile->expects($this->at(1))
->method('supportsCommands')
->with(array('WATCH', 'UNWATCH'))
->will($this->returnValue(false));
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
$client = new Client($connection, array('profile' => $profile));
$tx = new MultiExecContext($client, array('options' => 'cas'));
$tx->watch('foo');
}
@@ -473,6 +491,50 @@ class MultiExecContextTest extends PredisTestCase
$this->assertSame(array('MULTI', 'SET', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
}
/**
* @group disconnected
*/
public function testProperlyDiscardsTransactionAfterServerExceptionInBlock()
{
$connection = $this->getMockedConnection(function (CommandInterface $command) {
switch ($command->getId()) {
case 'MULTI':
return true;
case 'ECHO':
return new ResponseError('ERR simulated failure on ECHO');
case 'EXEC':
return new ResponseError('EXECABORT Transaction discarded because of previous errors.');
default:
return new ResponseQueued();
}
});
$client = new Client($connection);
// First attempt
$tx = new MultiExecContext($client);
try {
$tx->multi()->set('foo', 'bar')->echo('simulated failure')->exec();
} catch (\Exception $exception) {
$this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
$this->assertSame('ERR simulated failure on ECHO', $exception->getMessage());
}
// Second attempt
$tx = new MultiExecContext($client);
try {
$tx->multi()->set('foo', 'bar')->echo('simulated failure')->exec();
} catch (\Exception $exception) {
$this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
$this->assertSame('ERR simulated failure on ECHO', $exception->getMessage());
}
}
// ******************************************************************** //
// ---- INTEGRATION TESTS --------------------------------------------- //
// ******************************************************************** //
+1 -1
View File
@@ -11,7 +11,7 @@
if (file_exists(__DIR__.'/../autoload.php')) {
require __DIR__.'/../autoload.php';
} else if (@include('Predis/Autoloader.php')) {
} elseif (@include('Predis/Autoloader.php')) {
Predis\Autoloader::register();
} else {
die('ERROR: Unable to find a suitable mean to register Predis\Autoloader.');