Compare commits

...

28 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
36 changed files with 1027 additions and 446 deletions
+31 -1
View File
@@ -1,7 +1,37 @@
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 not the default server profile as there are no changes that would
- 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
+132 -71
View File
@@ -10,9 +10,9 @@ 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.4.0, but there are no guarantees you will
not run into unexpected issues (especially with the JIT compiler enabled via `Eval.Jit = true`) due
to HHVM being still under heavy development, thus unstable and not yet 100% compatible with PHP.
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).
@@ -20,12 +20,12 @@ 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.
@@ -49,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".
@@ -62,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();
@@ -93,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([
@@ -105,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:
@@ -118,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
@@ -127,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 ###
@@ -179,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
@@ -240,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:
@@ -270,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');
@@ -278,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 ##
@@ -296,7 +357,7 @@ 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.8
(which is the current stable version of Redis) but can optionally target a Redis instance built from
+1 -1
View File
@@ -1 +1 @@
0.8.6
0.8.7
+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.6';
const VERSION = '0.8.7';
private $options;
private $profile;
@@ -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.
+4 -277
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,286 +20,14 @@ 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();
}
/**
* Returns the default map of supported commands with their handlers.
*
* @return array
*/
protected function getDefaultCommands()
{
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
return array(
/* commands operating on the key space */
'EXISTS' => $getKeyFromFirstArgument,
'DEL' => $getKeyFromAllArguments,
'TYPE' => $getKeyFromFirstArgument,
'EXPIRE' => $getKeyFromFirstArgument,
'EXPIREAT' => $getKeyFromFirstArgument,
'PERSIST' => $getKeyFromFirstArgument,
'PEXPIRE' => $getKeyFromFirstArgument,
'PEXPIREAT' => $getKeyFromFirstArgument,
'TTL' => $getKeyFromFirstArgument,
'PTTL' => $getKeyFromFirstArgument,
'SORT' => $getKeyFromFirstArgument, // TODO
/* commands operating on string values */
'APPEND' => $getKeyFromFirstArgument,
'DECR' => $getKeyFromFirstArgument,
'DECRBY' => $getKeyFromFirstArgument,
'GET' => $getKeyFromFirstArgument,
'GETBIT' => $getKeyFromFirstArgument,
'MGET' => $getKeyFromAllArguments,
'SET' => $getKeyFromFirstArgument,
'GETRANGE' => $getKeyFromFirstArgument,
'GETSET' => $getKeyFromFirstArgument,
'INCR' => $getKeyFromFirstArgument,
'INCRBY' => $getKeyFromFirstArgument,
'INCRBYFLOAT' => $getKeyFromFirstArgument,
'SETBIT' => $getKeyFromFirstArgument,
'SETEX' => $getKeyFromFirstArgument,
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
'SETNX' => $getKeyFromFirstArgument,
'SETRANGE' => $getKeyFromFirstArgument,
'STRLEN' => $getKeyFromFirstArgument,
'SUBSTR' => $getKeyFromFirstArgument,
'BITCOUNT' => $getKeyFromFirstArgument,
/* commands operating on lists */
'LINSERT' => $getKeyFromFirstArgument,
'LINDEX' => $getKeyFromFirstArgument,
'LLEN' => $getKeyFromFirstArgument,
'LPOP' => $getKeyFromFirstArgument,
'RPOP' => $getKeyFromFirstArgument,
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
'LPUSH' => $getKeyFromFirstArgument,
'LPUSHX' => $getKeyFromFirstArgument,
'RPUSH' => $getKeyFromFirstArgument,
'RPUSHX' => $getKeyFromFirstArgument,
'LRANGE' => $getKeyFromFirstArgument,
'LREM' => $getKeyFromFirstArgument,
'LSET' => $getKeyFromFirstArgument,
'LTRIM' => $getKeyFromFirstArgument,
/* commands operating on sets */
'SADD' => $getKeyFromFirstArgument,
'SCARD' => $getKeyFromFirstArgument,
'SISMEMBER' => $getKeyFromFirstArgument,
'SMEMBERS' => $getKeyFromFirstArgument,
'SSCAN' => $getKeyFromFirstArgument,
'SPOP' => $getKeyFromFirstArgument,
'SRANDMEMBER' => $getKeyFromFirstArgument,
'SREM' => $getKeyFromFirstArgument,
/* commands operating on sorted sets */
'ZADD' => $getKeyFromFirstArgument,
'ZCARD' => $getKeyFromFirstArgument,
'ZCOUNT' => $getKeyFromFirstArgument,
'ZINCRBY' => $getKeyFromFirstArgument,
'ZRANGE' => $getKeyFromFirstArgument,
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZRANK' => $getKeyFromFirstArgument,
'ZREM' => $getKeyFromFirstArgument,
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANGE' => $getKeyFromFirstArgument,
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANK' => $getKeyFromFirstArgument,
'ZSCORE' => $getKeyFromFirstArgument,
'ZSCAN' => $getKeyFromFirstArgument,
'ZLEXCOUNT' => $getKeyFromFirstArgument,
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
/* commands operating on hashes */
'HDEL' => $getKeyFromFirstArgument,
'HEXISTS' => $getKeyFromFirstArgument,
'HGET' => $getKeyFromFirstArgument,
'HGETALL' => $getKeyFromFirstArgument,
'HMGET' => $getKeyFromFirstArgument,
'HMSET' => $getKeyFromFirstArgument,
'HINCRBY' => $getKeyFromFirstArgument,
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
'HKEYS' => $getKeyFromFirstArgument,
'HLEN' => $getKeyFromFirstArgument,
'HSET' => $getKeyFromFirstArgument,
'HSETNX' => $getKeyFromFirstArgument,
'HVALS' => $getKeyFromFirstArgument,
'HSCAN' => $getKeyFromFirstArgument,
/* commands operating on HyperLogLog */
'PFADD' => $getKeyFromFirstArgument,
'PFCOUNT' => $getKeyFromAllArguments,
'PFMERGE' => $getKeyFromAllArguments,
/* scripting */
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
);
}
/**
* Returns the list of IDs for the supported commands.
*
* @return array
*/
public function getSupportedCommands()
{
return array_keys($this->commands);
}
/**
* Sets an handler for the specified command ID.
*
* The signature of the callback must have a single parameter
* of type Predis\Command\CommandInterface.
*
* When the callback argument is omitted or NULL, the previously
* associated handler for the specified command ID is removed.
*
* @param string $commandId The ID of the command to be handled.
* @param mixed $callback A valid callable object or NULL.
*/
public function setCommandHandler($commandId, $callback = null)
{
$commandId = strtoupper($commandId);
if (!isset($callback)) {
unset($this->commands[$commandId]);
return;
}
if (!is_callable($callback)) {
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
}
$this->commands[$commandId] = $callback;
}
/**
* Extracts the key from the first argument of a command instance.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromFirstArgument(CommandInterface $command)
{
return $command->getArgument(0);
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromAllArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 1) {
return $arguments[0];
}
}
/**
* Extracts the key from a command that can accept multiple keys ensuring
* that only one key is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromInterleavedArguments(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from BLPOP and BRPOP commands ensuring that only one key
* is actually specified to comply with redis-cluster.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromBlockingListCommands(CommandInterface $command)
{
$arguments = $command->getArguments();
if (count($arguments) === 2) {
return $arguments[0];
}
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
* @param CommandInterface $command Command instance.
* @return string
*/
protected function getKeyFromScriptingCommands(CommandInterface $command)
{
if ($command instanceof ScriptedCommand) {
$keys = $command->getKeys();
} else {
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
}
if (count($keys) === 1) {
return $keys[0];
}
}
/**
* {@inheritdoc}
*/
public function getHash(CommandInterface $command)
{
$hash = $command->getHash();
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
$key = call_user_func($this->commands[$cmdID], $command);
if (isset($key)) {
$hash = $this->hashGenerator->hash($key);
$command->setHash($hash);
}
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function getKeyHash($key)
{
$key = $this->extractKeyTag($key);
$hash = $this->hashGenerator->hash($key);
return $hash;
parent::__construct($hashGenerator ?: new CRC16HashGenerator());
}
/**
+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';
}
}
+2 -1
View File
@@ -48,7 +48,7 @@ class ServerInfo extends AbstractCommand
/**
* Parses single row of the reply buffer and returns the key-value pair.
*
* @param string $row Single row of the reply buffer.
* @param string $row Single row of the reply buffer.
* @return array
*/
public function parseRow($row)
@@ -62,6 +62,7 @@ class ServerInfo extends AbstractCommand
} else {
$v = $this->parseDatabaseStats($v);
}
return array($k, $v);
}
@@ -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.
*/
+26 -27
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,36 +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);
if (false === strpos($node[2], 'master')) {
continue;
}
$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;
}
/**
@@ -405,7 +404,7 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
}
if ($this->askClusterNodes) {
$this->askClusterNodes();
$this->askClusterNodes($connection);
}
$this->move($connection, $slot);
@@ -495,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.
@@ -504,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)
{
+1 -1
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;
@@ -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;
/**
+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)
+1
View File
@@ -78,6 +78,7 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
'2.4' => 'Predis\Profile\ServerVersion24',
'2.6' => 'Predis\Profile\ServerVersion26',
'2.8' => 'Predis\Profile\ServerVersion28',
'3.0' => 'Predis\Profile\ServerVersion30',
'default' => 'Predis\Profile\ServerVersion28',
'dev' => 'Predis\Profile\ServerVersionNext',
);
+3
View File
@@ -250,6 +250,9 @@ class ServerVersion28 extends ServerProfile
'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);
+4 -4
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,7 +200,7 @@ 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)
@@ -215,7 +215,7 @@ class MultiExecContext implements BasicClientInterface, ExecutableContextInterfa
if ($response instanceof ResponseQueued) {
$this->commands->enqueue($command);
} else if ($response instanceof ResponseErrorInterface) {
} elseif ($response instanceof ResponseErrorInterface) {
throw new AbortedMultiExecException($this, $response->getMessage());
} else {
$this->onProtocolError('The server did not return a +QUEUED status response.');
+1 -1
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.6"
version = "0.8.7"
stability = "stable"
channel = "pear.nrk.io"
@@ -275,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',
@@ -297,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 */
@@ -305,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',
@@ -319,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',
@@ -331,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',
@@ -341,6 +353,7 @@ 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',
@@ -18,7 +18,7 @@ use Predis\Profile\ServerProfile;
/**
* @group realm-iterators
*/
class SortedSetTest extends PredisTestCase
class SortedSetKeyTest extends PredisTestCase
{
/**
* @group disconnected
+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()));
}
}
@@ -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
+72 -41
View File
@@ -398,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);
@@ -418,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);
@@ -427,7 +429,7 @@ class RedisClusterTest extends PredisTestCase
/**
* @group disconnected
*/
public function testDoesNotSupportKeyTags()
public function testSupportsKeyTags()
{
$profile = ServerProfile::getDefault();
@@ -445,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));
}
/**
@@ -477,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);
@@ -520,6 +523,7 @@ class RedisClusterTest extends PredisTestCase
->will($this->returnValue($connection3));
$cluster = new RedisCluster($factory);
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -553,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);
@@ -593,6 +598,7 @@ class RedisClusterTest extends PredisTestCase
->will($this->returnValue($connection3));
$cluster = new RedisCluster($factory);
$cluster->enableClusterNodes(false);
$cluster->add($connection1);
$cluster->add($connection2);
@@ -604,52 +610,36 @@ class RedisClusterTest extends PredisTestCase
/**
* @group disconnected
*/
public function testFetchSlotsMapFromClusterWithClusterNodesCommand()
public function testFetchSlotsMapFromClusterWithClusterSlotsCommand()
{
$output =<<<EOS
1284a54dc3245d305ae5e3f5507579f9a3e4bd80 10.1.0.51:6387 master - 0 1400000953094 20 connected 12288-13311
1fb644aa255ef92db789a93cc834f597e9a3800b 10.1.0.52:6392 master - 0 1400000955139 15 connected 3072-4095
fe814c66d4171db548b6dce537a88c3994cd7197 10.1.0.51:6391 slave 7ca7ed3780c405ee50757550e19ade5921097d25 0 1400000954111 5 connected
b3a3b7564c75c096bdc72fc314500cdd666adf87 :0 myself,master - 0 0 18 connected 6144-7167
67b25b19a07b56a155e25145957dce82a6605f26 10.1.0.51:6388 master - 0 1400000954111 21 connected 14336-15359
ba6e596ae5382ad05f1df5abaaa1489a0e0b2449 10.1.0.52:6398 master - 0 1400000953094 13 connected 15360-16383
7ca7ed3780c405ee50757550e19ade5921097d25 10.1.0.52:6391 master - 0 1400000954111 5 connected 1024-2047
4d90ab772120a49b177eb7bd4799b592f5048e16 10.1.0.51:6394 slave fa9b9fae41e3d53d2a901dbfe6fd1527c874768c 0 1400000955139 26 connected
5731adc383f5f7afbfcca2ee75d17609e436b146 10.1.0.51:6398 slave ba6e596ae5382ad05f1df5abaaa1489a0e0b2449 0 1400000953094 21 connected
fd0bd0cd8461bd44b759fefaabaf541788ec65cf 10.1.0.52:6396 master - 0 1400000955139 7 connected 11264-12287
d012ddf044d0ef5d72d171f8af1e700488b79241 10.1.0.52:6393 master - 0 1400000953604 10 connected 5120-6143
462ab20db112a007f3ffc4e7e105b0c963aa8981 10.1.0.52:6382 slave a1948d989b092bc96ff91019d9d3b74361edcdfa 0 1400000954111 18 connected
f468a7018ff5a0701a1b614b8ed1b400cddcd578 10.1.0.52:6385 slave 2a1845f27d0f24904d67e2f2cd3e7380949e9d3a 0 1400000953604 25 connected
a48e023927edcc0d001be0535130f41d8e93adde 10.1.0.52:6383 slave a547c6de8acfbf4a97a1c341bddc2efffea83032 0 1400000955139 12 connected
2530af9ee9b8d4e359cefedd7492ba777dd1e5aa 10.1.0.51:6392 slave 1fb644aa255ef92db789a93cc834f597e9a3800b 0 1400000953603 17 connected
2d3934ef56abbe25949063474d2e408ae2fb41af 10.1.0.51:6395 slave 0f70284073a6613343fd9a2da82a0549c1efaaae 0 1400000954627 27 connected
6a0430bf8e1beed8911aa69b002ac7a00ad60428 10.1.0.52:6387 slave 1284a54dc3245d305ae5e3f5507579f9a3e4bd80 0 1400000954111 20 connected
578ad5ff1970a22419a6afca0cc97bfb729f6b47 10.1.0.51:6397 slave fd637fc700d31395d1f324348b49ad38248ba8d4 0 1400000955139 18 connected
c3083761437050f1cd80a5f95b3ff5aabba3bb97 10.1.0.51:6381 master - 0 1400000955139 11 connected 0-1023
846d3410ce8e7cea565617686f4685a35d376706 10.1.0.52:6384 slave b3a3b7564c75c096bdc72fc314500cdd666adf87 0 1400000953094 18 connected
fd637fc700d31395d1f324348b49ad38248ba8d4 10.1.0.52:6397 master - 0 1400000954627 16 connected 13312-14335
a547c6de8acfbf4a97a1c341bddc2efffea83032 10.1.0.51:6383 master - 0 1400000954627 8 connected 4096-5119
0f70284073a6613343fd9a2da82a0549c1efaaae 10.1.0.52:6395 master - 0 1400000954627 27 connected 9216-10239
2a1845f27d0f24904d67e2f2cd3e7380949e9d3a 10.1.0.51:6385 master - 0 1400000953603 25 connected 8192-9215
ba018d6d154205a47b83de33efbbe846cb6f4b1f 10.1.0.52:6381 slave c3083761437050f1cd80a5f95b3ff5aabba3bb97 0 1400000955139 11 connected
5a830c9ef0d09eb25b521395466e803052ddbef4 10.1.0.51:6386 master - 0 1400000954627 9 connected 10240-11263
91d9840437794ab1ff77b59f831ce80ffaaa4c5a 10.1.0.51:6393 slave d012ddf044d0ef5d72d171f8af1e700488b79241 0 1400000954627 14 connected
d6bdbb495fcb66ba3001c2a07c067d5aa354cf55 10.1.0.52:6388 slave 67b25b19a07b56a155e25145957dce82a6605f26 0 1400000954111 21 connected
2176380db50d687d37e37c3daeeea6e7f8feed0c 10.1.0.51:6396 slave fd0bd0cd8461bd44b759fefaabaf541788ec65cf 0 1400000953603 22 connected
c93ffb4a2ff815f9029156383b00f106234b9546 10.1.0.52:6386 slave 5a830c9ef0d09eb25b521395466e803052ddbef4 0 1400000953604 9 connected
a1948d989b092bc96ff91019d9d3b74361edcdfa 10.1.0.51:6382 master - 0 1400000953094 17 connected 2048-3071
fa9b9fae41e3d53d2a901dbfe6fd1527c874768c 10.1.0.52:6394 master - 0 1400000953094 1 connected 7168-8191
EOS;
$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', 'NODES');
$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($output));
->will($this->returnValue($response));
$factory = $this->getMock('Predis\Connection\ConnectionFactoryInterface');
$factory = $this->getMock('Predis\Connection\ConnectionFactory');
$cluster = new RedisCluster($factory);
$cluster->add($connection1);
@@ -659,6 +649,47 @@ EOS;
$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
@@ -183,6 +183,7 @@ class ServerVersion28Test extends PredisProfileTestCase
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',
);
}
}
@@ -183,6 +183,7 @@ class ServerVersionNextTest extends PredisProfileTestCase
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'));
}
}
@@ -30,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);
}
@@ -42,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');
}
+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.');