Compare commits

..

3 Commits

Author SHA1 Message Date
Daniele Alessandri 6e8e23279e [tests] Update Predis\Connection\Aggregate\SentinelReplication tests.
Updated to verify that new sentinels retrieved from "SENTINEL" response
have their role automatically set to "sentinel". Also applied some minor
changes by dropping useless alias.
2020-09-12 19:44:56 +02:00
Daniele Alessandri 0392208520 Update README and CHANGELOG for role-specific default parameters. 2020-09-12 19:44:40 +02:00
Daniele Alessandri d9de9b5df3 Implement role-specific default parameters.
Until now users could specify a set of default parameters applied to all
connections created by the connection factory when not explicitly set in
the user-supplied set of parameters of each single node connection. This
is definitely handy, but it has some limits especially when dealing with
sentinel nodes since there are times when it is better to use different
defaults (e.g. "timeout") and they do not support certain parameters.

This commit adds the ability to specify role-specific default parameters
that gets applied only to connections targeting specific roles. This is
mostly useful for sentinels as they usually require lower timeouts than
normal Redis nodes and may also have a different password.

Role-specific parameters are passed as part of the "parameters" client
option in the form of named sub-arrays and take precedence over global
parameters, but they still do not override parameters explicitly set by
the user for single nodes.

Supported keys are "role.sentinel", "role.master" and "role.slave". In
regards to "role.sentinel", please note that:

  - sentinels do not support ACL authentication or database selection so
    so "username" and "database" are always stripped off.
  - "password" is never inherited from global defaults because users can
    have password-protected Redis nodes but unprotected Redis sentinels.
    In such cases, users must explicitly set a password either for each
    sentinel connection or just once in "role.sentinel".

Here is a brief example showing how to configure Predis for replication
supervised by redis-sentinel using different timeout and password values
for sentinel nodes compared to normal master and replica nodes.

  $client = new Predis\Client($arrayOfSentinels, [
    'replication' => 'sentinel',
    'service' => $sentinelService,

    'parameters' => [
      // Set of global default parameters, applied to *any* connection:
      'scheme' => true,
      'tcp_nodelay' => true,
      'timeout' => 5,
      'username' => $redisUsername, // Won't be inherited by sentinels.
      'password' => $redisPassword, // Won't be inherited by sentinels.

      // Set of sentinels-specific default parameters:
      'role.sentinel' => [
        // For sentinels, "scheme" and "tcp_nodelay" are inherited from
        // default parameters and "timeout" is overridden. On the other
        // hand both "username" and "password" are never inherited but
        // still explicitly set a password for sentinels because, in our
        // example, sentinels are indeed password-protected.
        'timeout' => 0.200,
        'password' => $sentinelPassword,
      ],
  ]);
2020-09-12 17:27:32 +02:00
65 changed files with 1815 additions and 1145 deletions
+4 -16
View File
@@ -1,52 +1,40 @@
name: Tests
on:
- push
- pull_request
on: [push, pull_request]
jobs:
predis:
name: PHP ${{ matrix.php-versions }} (Redis ${{ matrix.redis-versions }})
runs-on: ubuntu-latest
services:
redis:
image: redis:${{ matrix.redis-versions }}
ports:
- 6379:6379
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1']
redis-versions: ['3', '4', '5', '6', '7']
php-versions: ['7.2', '7.3', '7.4', '8.0']
redis-versions: ['3', '4', '5', '6']
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP with Composer and extensions
with:
php-version: ${{ matrix.php-versions }}
uses: shivammathur/setup-php@v2
- name: Get Composer cache directory
id: composercache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache Composer dependencies
uses: actions/cache@v2
with:
php-version: ${{ matrix.php-versions }}
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
env:
PHP_VERSION: ${{ matrix.php-versions }}
run: composer install --no-progress --prefer-dist --optimize-autoloader $(if [ "$PHP_VERSION" == "8.0" ]; then echo "--ignore-platform-reqs"; fi;)
- name: Test with PHPUnit
run: vendor/bin/phpunit
+1072 -30
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2021 Daniele Alessandri
Copyright (c) 2009-2020 Daniele Alessandri
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
+67 -12
View File
@@ -8,12 +8,21 @@
A flexible and feature-complete [Redis](http://redis.io) client for PHP 7.2 and newer.
__ATTENTION:__ you are on the README file of an unstable branch of Predis specifically meant for the
development of future releases. This means that the code on this branch is potentially unstable, and
breaking change may happen without any prior notice. Do not use it in production environments or use
it at your own risk!
Predis does not require any additional C extension by default, but it can be optionally paired with
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of the serialization and parsing
of the [Redis RESP Protocol](http://redis.io/topics/protocol).
More details about this project can be found on the [frequently asked questions](FAQ.md).
## Main features ##
- Support for Redis from __3.0__ to __7.0__.
- Support for Redis from __2.0__ to __3.2__.
- Support for clustering using client-side sharding and pluggable keyspace distributors.
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel).
@@ -35,10 +44,6 @@ This library can be found on [Packagist](http://packagist.org/packages/predis/pr
management of projects dependencies using [Composer](http://packagist.org/about-composer).
Compressed archives of each release are [available on GitHub](https://github.com/predis/predis/releases).
```shell
composer require predis/predis
```
### Loading the library ###
@@ -164,6 +169,40 @@ Users can also provide custom options with values or callable objects (for lazy
are stored in the options container for later use through the library.
### Global and role-specific default connection parameters ###
While the `parameters` client option is useful to apply a set of default parameters and their values
to connections created by the underlying connection factory, sometimes it is useful to set different
values depending on the actual role of the target node (just to make an example, for sentinel nodes
it is common to use a lower connect() timeout compared to the default value for normal Redis nodes).
To make this possible `parameters` allows passing role-specific default values as named arrays using
three special keys: `role.sentinel`, `role.master` and `role.slave`. These role-specific parameters
take precedence over global default parameters passed at the root level of `parameters` but they do
not override parameters explicitly set by the user for each single node just like global defaults.
```php
$options = [
'parameters' => [
// Root level is for global default parameters.
'database' => 10,
'password' => $redisSecretPassword,
// ...
'role.master' => [
// Sub-key for default parameters targeting master Redis nodes.
],
'role.slave' => [
// Sub-key for default parameters targeting replica Redis nodes.
],
'role.sentinel' => [
// Sub-key for default parameters targeting Redis Sentinel nodes.
],
],
];
```
### Aggregate connections ###
Aggregate connections are the foundation upon which Predis implements clustering and replication and
@@ -229,22 +268,38 @@ the `service` option set to the name of the service:
```php
$sentinels = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
$options = ['replication' => 'sentinel', 'service' => 'mymaster'];
$options = ['replication' => 'sentinel', 'service' => 'myservice'];
$client = new Predis\Client($sentinels, $options);
```
If the master and slave nodes are configured to require an authentication from clients, a password
must be provided via the global `parameters` client option. This option can also be used to specify
a different database index. The client options array would then look like this:
When master and replica nodes are configured to require authentication from clients, users must pass
`password` (password-based authentication) or `username` and `password` (ACL-based authentication on
Redis >= 6.0) via the global `parameters` client option.
```php
$options = [
'replication' => 'sentinel',
'service' => 'mymaster',
'service' => 'myservice',
'parameters' => [
'password' => $secretpassword,
'database' => 10,
'password' => $secretRedisPassword,
],
];
```
For sentinels protected by a password (supported since Redis >= 5.0) its value is not inherited from
the global `parameters` client option so a `password` must be set using the `role.sentinel` sub-key:
```php
$options = [
'replication' => 'sentinel',
'service' => 'myservice',
'parameters' => [
'password' => $secretRedisPassword,
'role.sentinel' => [
'password' => $secretSentinelPassword
]
],
];
```
+1 -1
View File
@@ -1 +1 @@
2.0.1
2.0.0-dev
+1 -1
View File
@@ -31,7 +31,7 @@
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0 || ~9.4.4"
"phpunit/phpunit": "^8.0 || ^9.0"
},
"suggest": {
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
+4 -3
View File
@@ -40,7 +40,7 @@ use Predis\Transaction\MultiExec as MultiExecTransaction;
*/
class Client implements ClientInterface, \IteratorAggregate
{
const VERSION = '2.0.1';
const VERSION = '2.0.0-dev';
/** @var OptionsInterface */
private $options;
@@ -209,7 +209,9 @@ class Client implements ClientInterface, \IteratorAggregate
throw new \InvalidArgumentException("Cannot find a connection by $selector matching `$value`");
}
return new static($connection, $this->getOptions());
$client = new static($connection, $this->getOptions());
return $client;
}
/**
@@ -513,7 +515,6 @@ class Client implements ClientInterface, \IteratorAggregate
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
$clients = array();
+148 -152
View File
@@ -15,7 +15,6 @@ use Predis\Command\CommandInterface;
use Predis\Command\FactoryInterface;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Response\Status;
/**
* Interface defining a client able to execute commands against Redis.
@@ -25,157 +24,154 @@ use Predis\Response\Status;
* and more friendly interface to ease programming which is described in the
* following list of methods:
*
* @method int del(string[]|string $keyOrKeys, string ...$keys = null)
* @method string|null dump(string $key)
* @method int exists(string $key)
* @method int expire(string $key, int $seconds)
* @method int expireat(string $key, int $timestamp)
* @method array keys(string $pattern)
* @method int move(string $key, int $db)
* @method mixed object($subcommand, string $key)
* @method int persist(string $key)
* @method int pexpire(string $key, int $milliseconds)
* @method int pexpireat(string $key, int $timestamp)
* @method int pttl(string $key)
* @method string|null randomkey()
* @method mixed rename(string $key, string $target)
* @method int renamenx(string $key, string $target)
* @method array scan($cursor, array $options = null)
* @method array sort(string $key, array $options = null)
* @method int ttl(string $key)
* @method mixed type(string $key)
* @method int append(string $key, $value)
* @method int bitcount(string $key, $start = null, $end = null)
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
* @method int bitpos(string $key, $bit, $start = null, $end = null)
* @method int decr(string $key)
* @method int decrby(string $key, int $decrement)
* @method string|null get(string $key)
* @method int getbit(string $key, $offset)
* @method string getrange(string $key, $start, $end)
* @method string|null getset(string $key, $value)
* @method int incr(string $key)
* @method int incrby(string $key, int $increment)
* @method string incrbyfloat(string $key, int|float $increment)
* @method array mget(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed mset(array $dictionary)
* @method int msetnx(array $dictionary)
* @method Status psetex(string $key, $milliseconds, $value)
* @method Status set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method int setbit(string $key, $offset, $value)
* @method Status setex(string $key, $seconds, $value)
* @method int setnx(string $key, $value)
* @method int setrange(string $key, $offset, $value)
* @method int strlen(string $key)
* @method int hdel(string $key, array $fields)
* @method int hexists(string $key, string $field)
* @method string|null hget(string $key, string $field)
* @method array hgetall(string $key)
* @method int hincrby(string $key, string $field, int $increment)
* @method string hincrbyfloat(string $key, string $field, int|float $increment)
* @method array hkeys(string $key)
* @method int hlen(string $key)
* @method array hmget(string $key, array $fields)
* @method mixed hmset(string $key, array $dictionary)
* @method array hscan(string $key, $cursor, array $options = null)
* @method int hset(string $key, string $field, string $value)
* @method int hsetnx(string $key, string $field, string $value)
* @method array hvals(string $key)
* @method int hstrlen(string $key, string $field)
* @method array|null blpop(array|string $keys, int|float $timeout)
* @method array|null brpop(array|string $keys, int|float $timeout)
* @method string|null brpoplpush(string $source, string $destination, int|float $timeout)
* @method string|null lindex(string $key, int $index)
* @method int linsert(string $key, $whence, $pivot, $value)
* @method int llen(string $key)
* @method string|null lpop(string $key)
* @method int lpush(string $key, array $values)
* @method int lpushx(string $key, array $values)
* @method string[] lrange(string $key, int $start, int $stop)
* @method int lrem(string $key, int $count, string $value)
* @method mixed lset(string $key, int $index, string $value)
* @method mixed ltrim(string $key, int $start, int $stop)
* @method string|null rpop(string $key)
* @method string|null rpoplpush(string $source, string $destination)
* @method int rpush(string $key, array $values)
* @method int rpushx(string $key, array $values)
* @method int sadd(string $key, array $members)
* @method int scard(string $key)
* @method string[] sdiff(array|string $keys)
* @method int sdiffstore(string $destination, array|string $keys)
* @method string[] sinter(array|string $keys)
* @method int sinterstore(string $destination, array|string $keys)
* @method int sismember(string $key, string $member)
* @method string[] smembers(string $key)
* @method int smove(string $source, string $destination, string $member)
* @method string|array|null spop(string $key, int $count = null)
* @method string|null srandmember(string $key, int $count = null)
* @method int srem(string $key, array|string $member)
* @method array sscan(string $key, int $cursor, array $options = null)
* @method string[] sunion(array|string $keys)
* @method int sunionstore(string $destination, array|string $keys)
* @method int touch(string[]|string $keyOrKeys, string ...$keys = null)
* @method int zadd(string $key, array $membersAndScoresDictionary)
* @method int zcard(string $key)
* @method string zcount(string $key, int|string $min, int|string $max)
* @method string zincrby(string $key, int $increment, string $member)
* @method int zinterstore(string $destination, array|string $keys, array $options = null)
* @method array zpopmin(string $key, int $count = 1)
* @method array zpopmax(string $key, int $count = 1)
* @method array zrange(string $key, int|string $start, int|string $stop, array $options = null)
* @method array zrangebyscore(string $key, int|string $min, int|string $max, array $options = null)
* @method int|null zrank(string $key, string $member)
* @method int zrem(string $key, string ...$member)
* @method int zremrangebyrank(string $key, int|string $start, int|string $stop)
* @method int zremrangebyscore(string $key, int|string $min, int|string $max)
* @method array zrevrange(string $key, int|string $start, int|string $stop, array $options = null)
* @method array zrevrangebyscore(string $key, int|string $max, int|string $min, array $options = null)
* @method int|null zrevrank(string $key, string $member)
* @method int zunionstore(string $destination, array|string $keys, array $options = null)
* @method string|null zscore(string $key, string $member)
* @method array zscan(string $key, int $cursor, array $options = null)
* @method array zrangebylex(string $key, string $start, string $stop, array $options = null)
* @method array zrevrangebylex(string $key, string $start, string $stop, array $options = null)
* @method int zremrangebylex(string $key, string $min, string $max)
* @method int zlexcount(string $key, string $min, string $max)
* @method int pfadd(string $key, array $elements)
* @method mixed pfmerge(string $destinationKey, array|string $sourceKeys)
* @method int pfcount(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed pubsub($subcommand, $argument)
* @method int publish($channel, $message)
* @method mixed discard()
* @method array|null exec()
* @method mixed multi()
* @method mixed unwatch()
* @method mixed watch(string $key)
* @method mixed eval(string $script, int $numkeys, string ...$keyOrArg = null)
* @method mixed evalsha(string $script, int $numkeys, string ...$keyOrArg = null)
* @method mixed script($subcommand, $argument = null)
* @method mixed auth(string $password)
* @method string echo(string $message)
* @method mixed ping(string $message = null)
* @method mixed select(int $database)
* @method mixed bgrewriteaof()
* @method mixed bgsave()
* @method mixed client($subcommand, $argument = null)
* @method mixed config($subcommand, $argument = null)
* @method int dbsize()
* @method mixed flushall()
* @method mixed flushdb()
* @method array info($section = null)
* @method int lastsave()
* @method mixed save()
* @method mixed slaveof(string $host, int $port)
* @method mixed slowlog($subcommand, $argument = null)
* @method array time()
* @method array command()
* @method int geoadd(string $key, $longitude, $latitude, $member)
* @method array geohash(string $key, array $members)
* @method array geopos(string $key, array $members)
* @method string|null geodist(string $key, $member1, $member2, $unit = null)
* @method array georadius(string $key, $longitude, $latitude, $radius, $unit, array $options = null)
* @method array georadiusbymember(string $key, $member, $radius, $unit, array $options = null)
* @method int del(array|string $keys)
* @method string|null dump($key)
* @method int exists($key)
* @method int expire($key, $seconds)
* @method int expireat($key, $timestamp)
* @method array keys($pattern)
* @method int move($key, $db)
* @method mixed object($subcommand, $key)
* @method int persist($key)
* @method int pexpire($key, $milliseconds)
* @method int pexpireat($key, $timestamp)
* @method int pttl($key)
* @method string|null randomkey()
* @method mixed rename($key, $target)
* @method int renamenx($key, $target)
* @method array scan($cursor, array $options = null)
* @method array sort($key, array $options = null)
* @method int ttl($key)
* @method mixed type($key)
* @method int append($key, $value)
* @method int bitcount($key, $start = null, $end = null)
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield($key, $subcommand, ...$subcommandArg)
* @method int bitpos($key, $bit, $start = null, $end = null)
* @method int decr($key)
* @method int decrby($key, $decrement)
* @method string|null get($key)
* @method int getbit($key, $offset)
* @method string getrange($key, $start, $end)
* @method string|null getset($key, $value)
* @method int incr($key)
* @method int incrby($key, $increment)
* @method string incrbyfloat($key, $increment)
* @method array mget(array $keys)
* @method mixed mset(array $dictionary)
* @method int msetnx(array $dictionary)
* @method mixed psetex($key, $milliseconds, $value)
* @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method int setbit($key, $offset, $value)
* @method int setex($key, $seconds, $value)
* @method int setnx($key, $value)
* @method int setrange($key, $offset, $value)
* @method int strlen($key)
* @method int hdel($key, array $fields)
* @method int hexists($key, $field)
* @method string|null hget($key, $field)
* @method array hgetall($key)
* @method int hincrby($key, $field, $increment)
* @method string hincrbyfloat($key, $field, $increment)
* @method array hkeys($key)
* @method int hlen($key)
* @method array hmget($key, array $fields)
* @method mixed hmset($key, array $dictionary)
* @method array hscan($key, $cursor, array $options = null)
* @method int hset($key, $field, $value)
* @method int hsetnx($key, $field, $value)
* @method array hvals($key)
* @method int hstrlen($key, $field)
* @method array|null blpop(array|string $keys, $timeout)
* @method array|null brpop(array|string $keys, $timeout)
* @method string|null brpoplpush($source, $destination, $timeout)
* @method string|null lindex($key, $index)
* @method int linsert($key, $whence, $pivot, $value)
* @method int llen($key)
* @method string|null lpop($key)
* @method int lpush($key, array $values)
* @method int lpushx($key, array $values)
* @method array lrange($key, $start, $stop)
* @method int lrem($key, $count, $value)
* @method mixed lset($key, $index, $value)
* @method mixed ltrim($key, $start, $stop)
* @method string|null rpop($key)
* @method string|null rpoplpush($source, $destination)
* @method int rpush($key, array $values)
* @method int rpushx($key, array $values)
* @method int sadd($key, array $members)
* @method int scard($key)
* @method array sdiff(array|string $keys)
* @method int sdiffstore($destination, array|string $keys)
* @method array sinter(array|string $keys)
* @method int sinterstore($destination, array|string $keys)
* @method int sismember($key, $member)
* @method array smembers($key)
* @method int smove($source, $destination, $member)
* @method string|null spop($key, $count = null)
* @method string|null srandmember($key, $count = null)
* @method int srem($key, $member)
* @method array sscan($key, $cursor, array $options = null)
* @method array sunion(array|string $keys)
* @method int sunionstore($destination, array|string $keys)
* @method int zadd($key, array $membersAndScoresDictionary)
* @method int zcard($key)
* @method string zcount($key, $min, $max)
* @method string zincrby($key, $increment, $member)
* @method int zinterstore($destination, array|string $keys, array $options = null)
* @method array zrange($key, $start, $stop, array $options = null)
* @method array zrangebyscore($key, $min, $max, array $options = null)
* @method int|null zrank($key, $member)
* @method int zrem($key, $member)
* @method int zremrangebyrank($key, $start, $stop)
* @method int zremrangebyscore($key, $min, $max)
* @method array zrevrange($key, $start, $stop, array $options = null)
* @method array zrevrangebyscore($key, $max, $min, array $options = null)
* @method int|null zrevrank($key, $member)
* @method int zunionstore($destination, array|string $keys, array $options = null)
* @method string|null zscore($key, $member)
* @method array zscan($key, $cursor, array $options = null)
* @method array zrangebylex($key, $start, $stop, array $options = null)
* @method array zrevrangebylex($key, $start, $stop, array $options = null)
* @method int zremrangebylex($key, $min, $max)
* @method int zlexcount($key, $min, $max)
* @method int pfadd($key, array $elements)
* @method mixed pfmerge($destinationKey, array|string $sourceKeys)
* @method int pfcount(array|string $keys)
* @method mixed pubsub($subcommand, $argument)
* @method int publish($channel, $message)
* @method mixed discard()
* @method array|null exec()
* @method mixed multi()
* @method mixed unwatch()
* @method mixed watch($key)
* @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method mixed script($subcommand, $argument = null)
* @method mixed auth($password)
* @method string echo($message)
* @method mixed ping($message = null)
* @method mixed select($database)
* @method mixed bgrewriteaof()
* @method mixed bgsave()
* @method mixed client($subcommand, $argument = null)
* @method mixed config($subcommand, $argument = null)
* @method int dbsize()
* @method mixed flushall()
* @method mixed flushdb()
* @method array info($section = null)
* @method int lastsave()
* @method mixed save()
* @method mixed slaveof($host, $port)
* @method mixed slowlog($subcommand, $argument = null)
* @method array time()
* @method array command()
* @method int geoadd($key, $longitude, $latitude, $member)
* @method array geohash($key, array $members)
* @method array geopos($key, array $members)
* @method string|null geodist($key, $member1, $member2, $unit = null)
* @method array georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
* @method array georadiusbymember($key, $member, $radius, $unit, array $options = null)
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
+2 -1
View File
@@ -239,8 +239,9 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
public function get($value)
{
$hash = $this->hash($value);
$node = $this->getByHash($hash);
return $this->getByHash($hash);
return $node;
}
/**
+2 -1
View File
@@ -40,8 +40,9 @@ class PredisStrategy extends ClusterStrategy
{
$key = $this->extractKeyTag($key);
$hash = $this->distributor->hash($key);
$slot = $this->distributor->getSlot($hash);
return $this->distributor->getSlot($hash);
return $slot;
}
/**
+2 -1
View File
@@ -41,8 +41,9 @@ class RedisStrategy extends ClusterStrategy
public function getSlotByKey($key)
{
$key = $this->extractKeyTag($key);
$slot = $this->hashGenerator->hash($key) & 0x3FFF;
return $this->hashGenerator->hash($key) & 0x3FFF;
return $slot;
}
/**
-6
View File
@@ -125,7 +125,6 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($slot)
{
return isset($this->slots[$slot]);
@@ -138,7 +137,6 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
*
* @return string
*/
#[\ReturnTypeWillChange]
public function offsetGet($slot)
{
if (isset($this->slots[$slot])) {
@@ -154,7 +152,6 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
*
* @return string
*/
#[\ReturnTypeWillChange]
public function offsetSet($slot, $connection)
{
if (!static::isValid($slot)) {
@@ -171,7 +168,6 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
*
* @return string
*/
#[\ReturnTypeWillChange]
public function offsetUnset($slot)
{
unset($this->slots[$slot]);
@@ -182,7 +178,6 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return count($this->slots);
@@ -193,7 +188,6 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
*
* @return \ArrayIterator
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->slots);
@@ -92,7 +92,7 @@ abstract class CursorBasedIterator implements \Iterator
{
$options = array();
if (strlen(strval($this->match)) > 0) {
if (strlen($this->match) > 0) {
$options['MATCH'] = $this->match;
}
@@ -139,7 +139,6 @@ abstract class CursorBasedIterator implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function rewind()
{
$this->reset();
@@ -149,7 +148,6 @@ abstract class CursorBasedIterator implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->current;
@@ -158,7 +156,6 @@ abstract class CursorBasedIterator implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->position;
@@ -167,7 +164,6 @@ abstract class CursorBasedIterator implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function next()
{
tryFetch: {
@@ -188,7 +184,6 @@ abstract class CursorBasedIterator implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function valid()
{
return $this->valid;
-5
View File
@@ -128,7 +128,6 @@ class ListKey implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function rewind()
{
$this->reset();
@@ -138,7 +137,6 @@ class ListKey implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->current;
@@ -147,7 +145,6 @@ class ListKey implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->position;
@@ -156,7 +153,6 @@ class ListKey implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function next()
{
if (!$this->elements && $this->fetchmore) {
@@ -173,7 +169,6 @@ class ListKey implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function valid()
{
return $this->valid;
+1 -1
View File
@@ -92,7 +92,7 @@ abstract class Command implements CommandInterface
*/
public static function normalizeArguments(array $arguments)
{
if (count($arguments) === 1 && isset($arguments[0]) && is_array($arguments[0])) {
if (count($arguments) === 1 && is_array($arguments[0])) {
return $arguments[0];
}
+1 -1
View File
@@ -73,7 +73,7 @@ interface CommandInterface
/**
* Parses a raw response and returns a PHP object.
*
* @param string|array|null $data Binary string containing the whole response.
* @param string $data Binary string containing the whole response.
*
* @return mixed
*/
+1 -5
View File
@@ -71,7 +71,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
/**
* Returns an iterator over the list of command processor in the chain.
*
* @return \Traversable<int, ProcessorInterface>
* @return \ArrayIterator
*/
public function getIterator()
{
@@ -91,7 +91,6 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetExists($index)
{
return isset($this->processors[$index]);
@@ -100,7 +99,6 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetGet($index)
{
return $this->processors[$index];
@@ -109,7 +107,6 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetSet($index, $processor)
{
if (!$processor instanceof ProcessorInterface) {
@@ -124,7 +121,6 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetUnset($index)
{
unset($this->processors[$index]);
+2 -1
View File
@@ -51,8 +51,9 @@ final class RawCommand implements CommandInterface
public static function create($commandID /* [ $arg, ... */)
{
$arguments = func_get_args();
$command = new static(array_shift($arguments), $arguments);
return new static(array_shift($arguments), $arguments);
return $command;
}
/**
+1 -4
View File
@@ -33,10 +33,7 @@ class SENTINEL extends RedisCommand
*/
public function parseResponse($data)
{
$argument = $this->getArgument(0);
$argument = is_null($argument) ? null : strtolower($argument);
switch ($argument) {
switch (strtolower($this->getArgument(0))) {
case 'masters':
case 'slaves':
return self::processMastersOrSlaves($data);
-40
View File
@@ -1,40 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/touch
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class TOUCH extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'TOUCH';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
$arguments = self::normalizeArguments($arguments);
parent::setArguments($arguments);
}
}
-44
View File
@@ -1,44 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/zpopmax
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZPOPMAX extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZPOPMAX';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
$result = array();
for ($i = 0; $i < count($data); ++$i) {
$result[$data[$i]] = $data[++$i];
}
return $result;
}
}
-44
View File
@@ -1,44 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
/**
* @link http://redis.io/commands/zpopmin
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
class ZPOPMIN extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'ZPOPMIN';
}
/**
* {@inheritdoc}
*/
public function parseResponse($data)
{
$result = array();
for ($i = 0; $i < count($data); ++$i) {
$result[$data[$i]] = $data[++$i];
}
return $result;
}
}
+5 -9
View File
@@ -26,21 +26,17 @@ abstract class CommunicationException extends PredisException
* @param NodeConnectionInterface $connection Connection that generated the exception.
* @param string $message Error message.
* @param int $code Error code.
* @param \Exception|null $innerException Inner exception for wrapping the original error.
* @param \Exception $innerException Inner exception for wrapping the original error.
*/
public function __construct(
NodeConnectionInterface $connection,
$message = "",
$code = 0,
$message = null,
$code = null,
\Exception $innerException = null
) {
parent::__construct(
is_null($message) ? '' : $message,
is_null($code) ? 0 : $code,
$innerException
);
$this->connection = $connection;
parent::__construct($message, $code, $innerException);
}
/**
+1 -1
View File
@@ -126,7 +126,7 @@ abstract class AbstractConnection implements NodeConnectionInterface
* @param string $message Error message.
* @param int $code Error code.
*/
protected function onConnectionError($message, $code = 0)
protected function onConnectionError($message, $code = null)
{
CommunicationException::handle(
new ConnectionException($this, "$message [{$this->getParameters()}]", $code)
+6 -4
View File
@@ -138,7 +138,9 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
);
}
return $this->distributor->getBySlot($slot);
$node = $this->distributor->getBySlot($slot);
return $node;
}
/**
@@ -187,7 +189,9 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
public function getConnectionByKey($key)
{
$hash = $this->strategy->getSlotByKey($key);
return $this->distributor->getBySlot($hash);
$node = $this->distributor->getBySlot($hash);
return $node;
}
/**
@@ -204,7 +208,6 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function count()
{
return count($this->pool);
@@ -213,7 +216,6 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->pool);
+13 -54
View File
@@ -22,8 +22,6 @@ use Predis\Connection\FactoryInterface;
use Predis\Connection\NodeConnectionInterface;
use Predis\NotSupportedException;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
use Predis\Response\ServerException;
use Predis\Response\Error as ErrorResponse;
/**
* Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
@@ -56,7 +54,6 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
private $strategy;
private $connections;
private $retryLimit = 5;
private $retryInterval = 10;
/**
* @param FactoryInterface $connections Optional connection factory.
@@ -85,26 +82,6 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
$this->retryLimit = (int) $retry;
}
/**
* Sets the initial retry interval (milliseconds).
*
* @param int $retryInterval Milliseconds between retries.
*/
public function setRetryInterval($retryInterval)
{
$this->retryInterval = (int) $retryInterval;
}
/**
* Returns the retry interval (milliseconds).
*
* @return int Milliseconds between retries.
*/
public function getRetryInterval()
{
return (int) $this->retryInterval;
}
/**
* {@inheritdoc}
*/
@@ -230,7 +207,6 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
private function queryClusterNodeForSlotMap(NodeConnectionInterface $connection)
{
$retries = 0;
$retryAfter = $this->retryInterval;
$command = RawCommand::create('CLUSTER', 'SLOTS');
RETRY_COMMAND: {
@@ -250,10 +226,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`');
}
usleep($retryAfter * 1000);
$retryAfter = $retryAfter * 2;
++$retries;
goto RETRY_COMMAND;
}
}
@@ -469,7 +442,9 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
}
$this->move($connection, $slot);
return $this->executeCommand($command);
$response = $this->executeCommand($command);
return $response;
}
/**
@@ -490,7 +465,9 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
}
$connection->executeCommand(RawCommand::create('ASKING'));
return $connection->executeCommand($command);
$response = $connection->executeCommand($command);
return $response;
}
/**
@@ -509,40 +486,24 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
*/
private function retryCommandOnFailure(CommandInterface $command, $method)
{
$retries = 0;
$retryAfter = $this->retryInterval;
$failure = false;
RETRY_COMMAND: {
try {
$response = $this->getConnectionByCommand($command)->$method($command);
} catch (ConnectionException $exception) {
$connection = $exception->getConnection();
$connection->disconnect();
if ($response instanceof ErrorResponse) {
$message = $response->getMessage();
$this->remove($connection);
if (strpos($message, 'CLUSTERDOWN') !== false) {
throw new ServerException($message);
}
}
} catch (\Throwable $exception) {
usleep($retryAfter * 1000);
$retryAfter = $retryAfter * 2;
if ($exception instanceof ConnectionException) {
$connection = $exception->getConnection();
if ($connection) {
$connection->disconnect();
$this->remove($connection);
}
}
if ($retries === $this->retryLimit) {
if ($failure) {
throw $exception;
} elseif ($this->useClusterSlots) {
$this->askSlotMap();
}
++$retries;
$failure = true;
goto RETRY_COMMAND;
}
@@ -584,7 +545,6 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function count()
{
return count($this->pool);
@@ -593,7 +553,6 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
if ($this->slotmap->isEmpty()) {
+64 -2
View File
@@ -11,6 +11,7 @@
namespace Predis\Connection;
use InvalidArgumentException;
use Predis\Command\RawCommand;
/**
@@ -119,6 +120,28 @@ class Factory implements FactoryInterface
*/
public function setDefaultParameters(array $parameters)
{
if (isset($parameters['role.master']) && !is_array($parameters['role.master'])) {
throw new InvalidArgumentException('Default parameters for `role.master` must be passed as a named array');
}
if (isset($parameters['role.slave']) && !is_array($parameters['role.slave'])) {
throw new InvalidArgumentException('Default parameters for `role.slave` must be passed as a named array');
}
if (isset($parameters['role.sentinel'])) {
if (!is_array($parameters['role.sentinel'])) {
throw new InvalidArgumentException('Default parameters for `role.sentinel` must be passed as a named array');
}
// NOTE: sentinels do not support "SELECT" and ACL "AUTH" commands
// so we must strip "database" and "username" from "role.sentinel"
// to prevent spurious commands from being sent to sentinel nodes.
unset(
$parameters['role.sentinel']['username'],
$parameters['role.sentinel']['database']
);
}
$this->defaults = $parameters;
}
@@ -132,6 +155,37 @@ class Factory implements FactoryInterface
return $this->defaults;
}
/**
* Applies default connection parameters to the user supplied parameters.
*
* @param array $parameters Input connection parameters
*
* @return array
*/
protected function applyDefaultParameters(array $parameters)
{
static $stripInternal = ['role.sentinel' => null, 'role.master' => null, 'role.slave' => null];
$stripAdditional = [];
if (isset($parameters['role'])) {
switch ($role = $parameters['role']) {
case 'sentinel':
// NOTE: we strip these from global defaults when dealing with sentinel nodes.
$stripAdditional = ['username' => null, 'password' => null, 'database' => null];
case 'master':
case 'slave':
if (isset($this->defaults["role.$role"])) {
$parameters += $this->defaults["role.$role"];
}
}
}
$parameters += array_diff_key($this->defaults, $stripInternal, $stripAdditional);
return $parameters;
}
/**
* Creates a connection parameters instance from the supplied argument.
*
@@ -144,11 +198,19 @@ class Factory implements FactoryInterface
if (is_string($parameters)) {
$parameters = Parameters::parse($parameters);
} else {
$parameters = $parameters ?: array();
$parameters = $parameters ?? [];
}
if (isset($parameters['role']) && $parameters['role'] === 'sentinel') {
// NOTE: sentinels do not support "SELECT" and ACL "AUTH" commands so we must strip
// "database" and "username" from input parameters to prevent spurious commands from
// being sent to sentinel nodes but they can still accept "password" when explicitly
// set (password-based authentication for sentinels is supported on Redis >= 5.0).
unset($parameters['username'], $parameters['database']);
}
if ($this->defaults) {
$parameters += $this->defaults;
$parameters = $this->applyDefaultParameters($parameters);
}
return new Parameters($parameters);
-8
View File
@@ -26,14 +26,6 @@ class Parameters implements ParametersInterface
'port' => 6379,
);
/**
* Set of connection paramaters already filtered
* for NULL or 0-length string values.
*
* @var array
*/
protected $parameters;
/**
* @param array $parameters Named array of connection parameters.
*/
+5 -5
View File
@@ -66,9 +66,9 @@ class PhpiredisSocketConnection extends AbstractConnection
*/
public function __destruct()
{
parent::__destruct();
phpiredis_reader_destroy($this->reader);
parent::__destruct();
}
/**
@@ -227,7 +227,9 @@ class PhpiredisSocketConnection extends AbstractConnection
$protocol = SOL_TCP;
}
if (false === $socket = @socket_create($domain, SOCK_STREAM, $protocol)) {
$socket = @socket_create($domain, SOCK_STREAM, $protocol);
if (!is_resource($socket)) {
$this->emitSocketError();
}
@@ -342,9 +344,7 @@ class PhpiredisSocketConnection extends AbstractConnection
public function disconnect()
{
if ($this->isConnected()) {
phpiredis_reader_reset($this->reader);
socket_close($this->getResource());
parent::disconnect();
}
}
+1 -11
View File
@@ -67,19 +67,9 @@ class PhpiredisStreamConnection extends StreamConnection
*/
public function __destruct()
{
parent::__destruct();
phpiredis_reader_destroy($this->reader);
}
/**
* {@inheritdoc}
*/
public function disconnect()
{
phpiredis_reader_reset($this->reader);
parent::disconnect();
parent::__destruct();
}
/**
@@ -20,7 +20,6 @@ use Predis\Connection\NodeConnectionInterface;
use Predis\Connection\Parameters;
use Predis\Replication\ReplicationStrategy;
use Predis\Replication\RoleException;
use Predis\Response\Error;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
use Predis\Response\ServerException;
@@ -70,11 +69,6 @@ class SentinelReplication implements ReplicationInterface
*/
protected $sentinels = array();
/**
* @var int
*/
protected $sentinelIndex = 0;
/**
* @var NodeConnectionInterface
*/
@@ -157,14 +151,14 @@ class SentinelReplication implements ReplicationInterface
}
/**
* Sets the time to wait (in milliseconds) before fetching a new configuration
* Sets the time to wait (in seconds) before fetching a new configuration
* from one of the sentinels.
*
* @param float $milliseconds Time to wait before the next attempt.
* @param float $seconds Time to wait before the next attempt.
*/
public function setRetryWait($milliseconds)
public function setRetryWait($seconds)
{
$this->retryWait = (float) $milliseconds;
$this->retryWait = (float) $seconds;
}
/**
@@ -247,6 +241,8 @@ class SentinelReplication implements ReplicationInterface
/**
* Creates a new connection to a sentinel server.
*
* @param mixed $parameters Connection parameters or connection instance
*
* @return NodeConnectionInterface
*/
protected function createSentinelConnection($parameters)
@@ -260,21 +256,19 @@ class SentinelReplication implements ReplicationInterface
}
if (is_array($parameters)) {
// NOTE: sentinels do not accept AUTH and SELECT commands so we must
// explicitly set them to NULL to avoid problems when using default
// parameters set via client options. Actually AUTH is supported for
// sentinels starting with Redis 5 but we have to differentiate from
// sentinels passwords and nodes passwords, this will be implemented
// in a later release.
$parameters['database'] = null;
$parameters['username'] = null;
// NOTE: we enforce the "sentinel" role so that appropriate default
// parameters are applied when creating the new connection instance
// and blacklisted ones are stripped off from input parameters.
$parameters['role'] = 'sentinel';
if (!isset($parameters['timeout'])) {
$parameters['timeout'] = $this->sentinelTimeout;
}
}
return $this->connectionFactory->create($parameters);
$connection = $this->connectionFactory->create($parameters);
return $connection;
}
/**
@@ -287,13 +281,11 @@ class SentinelReplication implements ReplicationInterface
public function getSentinelConnection()
{
if (!$this->sentinelConnection) {
if ($this->sentinelIndex >= count($this->sentinels)) {
$this->sentinelIndex = 0;
if (!$this->sentinels) {
throw new \Predis\ClientException('No sentinel server available for autodiscovery.');
}
$sentinel = $this->sentinels[$this->sentinelIndex];
++$this->sentinelIndex;
$sentinel = array_shift($this->sentinels);
$this->sentinelConnection = $this->createSentinelConnection($sentinel);
}
@@ -314,7 +306,6 @@ class SentinelReplication implements ReplicationInterface
);
$this->sentinels = array();
$this->sentinelIndex = 0;
// NOTE: sentinel server does not return itself, so we add it back.
$this->sentinels[] = $sentinel->getParameters()->toArray();
@@ -545,17 +536,13 @@ class SentinelReplication implements ReplicationInterface
* @param NodeConnectionInterface $connection Connection to a redis server.
* @param string $role Expected role of the server ("master", "slave" or "sentinel").
*
* @throws RoleException|ConnectionException
* @throws RoleException
*/
protected function assertConnectionRole(NodeConnectionInterface $connection, $role)
{
$role = strtolower($role);
$actualRole = $connection->executeCommand(RawCommand::create('ROLE'));
if ($actualRole instanceof Error) {
throw new ConnectionException($connection, $actualRole->getMessage());
}
if ($role !== $actualRole[0]) {
throw new RoleException($connection, "Expected $role but got $actualRole[0] [$connection]");
}
+7 -3
View File
@@ -151,7 +151,9 @@ class StreamConnection extends AbstractConnection
}
}
return $this->createStreamSocket($parameters, $address, $flags);
$resource = $this->createStreamSocket($parameters, $address, $flags);
return $resource;
}
/**
@@ -181,7 +183,9 @@ class StreamConnection extends AbstractConnection
}
}
return $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags);
$resource = $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags);
return $resource;
}
/**
@@ -360,7 +364,7 @@ class StreamConnection extends AbstractConnection
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
foreach ($arguments as $argument) {
$arglen = strlen(strval($argument));
$arglen = strlen($argument);
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
}
+3 -8
View File
@@ -12,7 +12,7 @@
namespace Predis\Monitor;
use Predis\ClientInterface;
use Predis\Connection\Cluster\ClusterInterface;
use Predis\Connection\AggregateConnectionInterface;
use Predis\NotSupportedException;
/**
@@ -56,9 +56,9 @@ class Consumer implements \Iterator
*/
private function assertClient(ClientInterface $client)
{
if ($client->getConnection() instanceof ClusterInterface) {
if ($client->getConnection() instanceof AggregateConnectionInterface) {
throw new NotSupportedException(
'Cannot initialize a monitor consumer over cluster connections.'
'Cannot initialize a monitor consumer over aggregate connections.'
);
}
@@ -91,7 +91,6 @@ class Consumer implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function rewind()
{
// NOOP
@@ -102,7 +101,6 @@ class Consumer implements \Iterator
*
* @return object
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->getValue();
@@ -111,7 +109,6 @@ class Consumer implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->position;
@@ -120,7 +117,6 @@ class Consumer implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function next()
{
++$this->position;
@@ -131,7 +127,6 @@ class Consumer implements \Iterator
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
return $this->valid;
+1 -1
View File
@@ -14,7 +14,7 @@ namespace Predis\Protocol;
use Predis\CommunicationException;
/**
* Exception used to identify errors encountered while parsing the Redis wire
* Exception used to indentify errors encountered while parsing the Redis wire
* protocol.
*
* @author Daniele Alessandri <suppakilla@gmail.com>
+3 -1
View File
@@ -95,7 +95,9 @@ class ResponseReader implements ResponseReaderInterface
$this->onProtocolError($connection, "Unknown response prefix: '$prefix'");
}
return $this->handlers[$prefix]->handle($connection, substr($header, 1));
$payload = $this->handlers[$prefix]->handle($connection, substr($header, 1));
return $payload;
}
/**
-5
View File
@@ -151,7 +151,6 @@ abstract class AbstractConsumer implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function rewind()
{
// NOOP
@@ -163,7 +162,6 @@ abstract class AbstractConsumer implements \Iterator
*
* @return array
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->getValue();
@@ -172,7 +170,6 @@ abstract class AbstractConsumer implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->position;
@@ -181,7 +178,6 @@ abstract class AbstractConsumer implements \Iterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function next()
{
if ($this->valid()) {
@@ -196,7 +192,6 @@ abstract class AbstractConsumer implements \Iterator
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
$isValid = $this->isFlagSet(self::STATUS_VALID);
+3 -3
View File
@@ -14,7 +14,7 @@ namespace Predis\PubSub;
use Predis\ClientException;
use Predis\ClientInterface;
use Predis\Command\Command;
use Predis\Connection\Cluster\ClusterInterface;
use Predis\Connection\AggregateConnectionInterface;
use Predis\NotSupportedException;
/**
@@ -62,9 +62,9 @@ class Consumer extends AbstractConsumer
*/
private function checkCapabilities(ClientInterface $client)
{
if ($client->getConnection() instanceof ClusterInterface) {
if ($client->getConnection() instanceof AggregateConnectionInterface) {
throw new NotSupportedException(
'Cannot initialize a PUB/SUB consumer over cluster connections.'
'Cannot initialize a PUB/SUB consumer over aggregate connections.'
);
}
+1 -2
View File
@@ -62,8 +62,7 @@ class ReplicationStrategy
}
if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
$argument = $command->getArgument(0);
$sha1 = $eval ? sha1(strval($argument)) : $argument;
$sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
if (isset($this->readonlySHA1[$sha1])) {
if (true === $readonly = $this->readonlySHA1[$sha1]) {
@@ -34,7 +34,6 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function rewind()
{
// NOOP
@@ -43,7 +42,6 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->current;
@@ -52,7 +50,6 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->position;
@@ -61,7 +58,6 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function next()
{
if (++$this->position < $this->size) {
@@ -72,7 +68,6 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function valid()
{
return $this->position < $this->size;
@@ -87,7 +82,6 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return $this->size;
-1
View File
@@ -61,7 +61,6 @@ class MultiBulkTuple extends MultiBulk implements \OuterIterator
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function getInnerIterator()
{
return $this->iterator;
+1 -1
View File
@@ -59,7 +59,7 @@ class Status implements ResponseInterface
*
* @param string $payload Status response payload.
*
* @return self
* @return string
*/
public static function get($payload)
{
-6
View File
@@ -54,7 +54,6 @@ class Handler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function open($save_path, $session_id)
{
// NOOP
@@ -64,7 +63,6 @@ class Handler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function close()
{
// NOOP
@@ -74,7 +72,6 @@ class Handler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function gc($maxlifetime)
{
// NOOP
@@ -84,7 +81,6 @@ class Handler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function read($session_id)
{
if ($data = $this->client->get($session_id)) {
@@ -96,7 +92,6 @@ class Handler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function write($session_id, $session_data)
{
$this->client->setex($session_id, $this->ttl, $session_data);
@@ -107,7 +102,6 @@ class Handler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function destroy($session_id)
{
$this->client->del($session_id);
@@ -27,10 +27,9 @@ class AbortedMultiExecException extends PredisException
* @param string $message Error message.
* @param int $code Error code.
*/
public function __construct(MultiExec $transaction, $message, $code = 0)
public function __construct(MultiExec $transaction, $message, $code = null)
{
parent::__construct($message, is_null($code) ? 0 : $code);
parent::__construct($message, $code);
$this->transaction = $transaction;
}
+3 -3
View File
@@ -16,7 +16,7 @@ use Predis\ClientException;
use Predis\ClientInterface;
use Predis\Command\CommandInterface;
use Predis\CommunicationException;
use Predis\Connection\Cluster\ClusterInterface;
use Predis\Connection\AggregateConnectionInterface;
use Predis\NotSupportedException;
use Predis\Protocol\ProtocolException;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
@@ -66,9 +66,9 @@ class MultiExec implements ClientContextInterface
*/
private function assertClient(ClientInterface $client)
{
if ($client->getConnection() instanceof ClusterInterface) {
if ($client->getConnection() instanceof AggregateConnectionInterface) {
throw new NotSupportedException(
'Cannot initialize a MULTI/EXEC transaction over cluster connections.'
'Cannot initialize a MULTI/EXEC transaction over aggregate connections.'
);
}
+1 -5
View File
@@ -10,7 +10,6 @@
*/
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Util\Test as TestUtil;
use Predis\Client;
use Predis\Command;
use Predis\Connection;
@@ -316,10 +315,7 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
*/
protected function getRequiredRedisServerVersion(): ?string
{
$annotations = TestUtil::parseTestMethodAnnotations(
get_class($this),
$this->getName(false)
);
$annotations = $this->getAnnotations();
if (isset($annotations['method']['requiresRedisVersion'], $annotations['method']['group']) &&
!empty($annotations['method']['requiresRedisVersion']) &&
+4 -4
View File
@@ -236,7 +236,7 @@ class SlotMapTest extends PredisTestCase
{
$slotmap = new SlotMap();
$this->assertCount(0, $slotmap);
$this->assertSame(0, count($slotmap));
}
/**
@@ -247,13 +247,13 @@ class SlotMapTest extends PredisTestCase
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertCount(5461, $slotmap);
$this->assertSame(5461, count($slotmap));
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$this->assertCount(10923, $slotmap);
$this->assertSame(10923, count($slotmap));
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertCount(16384, $slotmap);
$this->assertSame(16384, count($slotmap));
}
/**
+1 -22
View File
@@ -106,34 +106,13 @@ class COMMAND_Test extends PredisCommandTestCase
// NOTE: starting with Redis 6.0 and the introduction of Access Control
// Lists, COMMAND INFO returns an additional array for each specified
// command in the request with a list of the ACL categories associated
// command in yhe request with a list of the ACL categories associated
// to a command. We simply append this additional array in the expected
// response if the test suite is executed against Redis >= 6.0.
if ($this->isRedisServerVersion('>=', '6.0')) {
$expected[0][] = array('@read', '@string', '@fast');
}
// NOTE: starting with Redis 7.0 COMMAND INFO returns an additional arrays:
// - Command tips: https://redis.io/topics/command-tips.
// - Key specifications: https://redis.io/topics/key-specs.
// - Subcommands: https://redis.io/commands/command/#subcommands.
// We simply append this additional array in the expected response if the
// test suite is executed against Redis >= 7.0.
if ($this->isRedisServerVersion('>=', '7.0')) {
$expected[0][] = array();
$expected[0][] = array(
array(
'flags',
array('RO','access'),
'begin_search',
array('type','index','spec', array('index',1)),
'find_keys',
array('type','range','spec', array('lastkey',0,'keystep',1,'limit',0))
)
);
$expected[0][] = array();
}
$this->assertCount(1, $response = $redis->command('INFO', 'GET'));
// NOTE: we use assertEquals instead of assertSame because Redis returns
+2 -8
View File
@@ -104,7 +104,7 @@ class CONFIG_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->assertIsArray($configs = $redis->config('GET', 'dbfilename'));
$this->assertCount(1, $configs);
$this->assertEquals(1, count($configs));
$this->assertArrayHasKey('dbfilename', $configs);
}
@@ -143,13 +143,7 @@ class CONFIG_Test extends PredisCommandTestCase
public function testThrowsExceptionWhenSettingUnknownConfiguration(): void
{
$this->expectException('Predis\Response\ServerException');
if ($this->isRedisServerVersion('<=', '6.0')) {
$this->expectExceptionMessage('ERR Unsupported CONFIG parameter: foo');
}
if ($this->isRedisServerVersion('>=', '7.0')) {
$this->expectExceptionMessage("ERR Unknown option or number of arguments for CONFIG SET - 'foo'");
}
$this->expectExceptionMessage('ERR Unsupported CONFIG parameter: foo');
$redis = $this->getClient();
+1 -1
View File
@@ -320,7 +320,7 @@ BUFFER;
$redis = $this->getClient();
$command = $this->getCommand();
$this->assertIsArray($info = $redis->executeCommand($command));
$this->assertInternalType('array', $info = $redis->executeCommand($command));
$this->assertArrayHasKey('redis_version', $info);
}
}
-15
View File
@@ -92,19 +92,4 @@ class LPOP_Test extends PredisCommandTestCase
$redis->set('foo', 'bar');
$redis->lpop('foo');
}
/**
* @group connected
* @requiresRedisVersion >= 6.2
*/
public function testPopsSpecifiedNumberOfElements(): void
{
$redis = $this->getClient();
$redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f');
$this->assertSame(array('a', 'b'), $redis->lpop('letters', 2));
$this->assertSame(array('c', 'd'), $redis->lpop('letters', 2));
$this->assertSame(array('e', 'f'), $redis->lrange('letters', 0, -1));
}
}
+1 -1
View File
@@ -85,7 +85,7 @@ class MOVE_Test extends PredisCommandTestCase
public function testThrowsExceptionOnInvalidDatabases(): void
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessageMatches('/ERR.*out of range/');
$this->expectExceptionMessage('ERR index out of range');
$redis = $this->getClient();
+1 -1
View File
@@ -87,7 +87,7 @@ class SELECT_Test extends PredisCommandTestCase
public function testThrowsExceptionOnUnexpectedDatabaseName(): void
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessageMatches('/ERR.*(invalid DB index|value is not an integer or out of range)/');
$this->expectExceptionMessage('ERR invalid DB index');
$redis = $this->getClient();
+3 -3
View File
@@ -185,11 +185,11 @@ class SORT_Test extends PredisCommandTestCase
$redis = $this->getClient();
$redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
$this->assertCount(
$this->assertEquals(
count($unordered),
$redis->sort('list:unordered', array(
'store' => 'list:ordered',
)),
$unordered
))
);
$this->assertEquals(array(1, 2, 3, 10, 30, 100), $redis->lrange('list:ordered', 0, -1));
-16
View File
@@ -101,22 +101,6 @@ class SREM_Test extends PredisCommandTestCase
$this->assertSame(0, $redis->srem('digits', 1));
}
/**
* @group connected
* @requiresRedisVersion >= 2.4.0
*/
public function testRemovesMembersInArrayTypeFromSetVariadic(): void
{
$redis = $this->getClient();
$redis->sadd('letters', 'a', 'b', 'c', 'd');
$this->assertSame(2, $redis->srem('letters', ['b', 'd', 'z']));
$this->assertSameValues(array('a', 'c'), $redis->smembers('letters'));
$this->assertSame(0, $redis->srem('digits', [1]));
}
/**
* @group connected
*/
-98
View File
@@ -1,98 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
/**
* @group commands
* @group realm-server
*/
class TOUCH_Test extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand(): string
{
return 'Predis\Command\Redis\TOUCH';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId(): string
{
return 'TOUCH';
}
/**
* @requiresRedisVersion >= 3.2.1
*
* @group disconnected
*/
public function testFilterArguments(): void
{
$arguments = ['key1', 'key2', 'key3'];
$expected = ['key1', 'key2', 'key3'];
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @requiresRedisVersion >= 3.2.1
*
* @group disconnected
*/
public function testFilterArgumentsAsSingleArray(): void
{
$arguments = [['key1', 'key2', 'key3']];
$expected = ['key1', 'key2', 'key3'];
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @requiresRedisVersion >= 3.2.1
*
* @group disconnected
*/
public function testParseResponse(): void
{
$command = $this->getCommand();
$this->assertSame(10, $command->parseResponse(10));
}
/**
* @requiresRedisVersion >= 3.2.1
*
* @group connected
*/
public function testReturnsNumberOfDeletedKeys(): void
{
$redis = $this->getClient();
$this->assertSame(0, $redis->touch('foo'));
$redis->set('foo', 'bar');
$this->assertSame(1, $redis->touch('foo'));
$this->assertSame(1, $redis->touch('foo', 'hoge'));
$redis->set('hoge', 'piyo');
$this->assertSame(1, $redis->touch('foo'));
$this->assertSame(2, $redis->touch('foo', 'hoge'));
}
}
-102
View File
@@ -1,102 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
/**
* @group commands
* @group realm-zset
*/
class ZPOPMAX_Test extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand(): string
{
return 'Predis\Command\Redis\ZPOPMAX';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId(): string
{
return 'ZPOPMAX';
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group disconnected
*/
public function testFilterArguments(): void
{
$arguments = array('zset', 2);
$expected = array('zset', 2);
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group disconnected
*/
public function testParseResponse(): void
{
$raw = array('element1', '1', 'element2', '2', 'element3', '3');
$expected = array('element1' => '1', 'element2' => '2', 'element3' => '3');
$command = $this->getCommand();
$this->assertSame($expected, $command->parseResponse($raw));
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group connected
*/
public function testReturnsElements(): void
{
$redis = $this->getClient();
$this->assertSame(array(), $redis->zpopmax('letters'));
$this->assertSame(array(), $redis->zpopmax('letters', 3));
$redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
$this->assertSame(array('f' => '30'), $redis->zpopmax('letters'));
$this->assertSame(array('e' => '20', 'd' => '20', 'c' => '10'), $redis->zpopmax('letters', 3));
$this->assertSame(array('b' => '0', 'a' => '-10'), $redis->zpopmax('letters', 3));
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group connected
*/
public function testThrowsExceptionOnWrongType(): void
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
$redis = $this->getClient();
$redis->set('foo', 'bar');
$redis->zpopmax('foo');
}
}
-101
View File
@@ -1,101 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
/**
* @group commands
* @group realm-zset
*/
class ZPOPMIN_Test extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand(): string
{
return 'Predis\Command\Redis\ZPOPMIN';
}
/**
* {@inheritdoc}
*/
protected function getExpectedId(): string
{
return 'ZPOPMIN';
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group disconnected
*/
public function testFilterArguments(): void
{
$arguments = array('zset', 2);
$expected = array('zset', 2);
$command = $this->getCommand();
$command->setArguments($arguments);
$this->assertSame($expected, $command->getArguments());
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group disconnected
*/
public function testParseResponse(): void
{
$raw = array('element1', '1', 'element2', '2', 'element3', '3');
$expected = array('element1' => '1', 'element2' => '2', 'element3' => '3');
$command = $this->getCommand();
$this->assertSame($expected, $command->parseResponse($raw));
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group connected
*/
public function testReturnsElements(): void
{
$redis = $this->getClient();
$this->assertSame(array(), $redis->zpopmin('letters'));
$this->assertSame(array(), $redis->zpopmin('letters', 3));
$redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
$this->assertSame(array('a' => '-10'), $redis->zpopmin('letters'));
$this->assertSame(array('b' => '0', 'c' => '10', 'd' => '20'), $redis->zpopmin('letters', 3));
$this->assertSame(array('e' => '20', 'f' => '30'), $redis->zpopmin('letters', 3));
}
/**
* @requiresRedisVersion >= 5.0.0
*
* @group connected
*/
public function testThrowsExceptionOnWrongType(): void
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
$redis = $this->getClient();
$redis->set('foo', 'bar');
$redis->zpopmin('foo');
}
}
+1 -1
View File
@@ -90,7 +90,7 @@ class RedisFactoryTest extends PredisTestCase
$this->assertTrue($factory->supports('mock'));
$this->assertTrue($factory->supports('MOCK'));
$this->assertInstanceOf($factory->getCommandClass('mock'), $command);
$this->assertSame(get_class($command), $factory->getCommandClass('mock'));
}
/**
@@ -40,7 +40,7 @@ class PredisClusterTest extends PredisTestCase
$cluster->add($connection1);
$cluster->add($connection2);
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
$this->assertSame($connection1, $cluster->getConnectionById('127.0.0.1:7001'));
$this->assertSame($connection2, $cluster->getConnectionById('127.0.0.1:7002'));
}
@@ -58,7 +58,7 @@ class PredisClusterTest extends PredisTestCase
$cluster->add($connection1);
$cluster->add($connection2);
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
$this->assertSame($connection1, $cluster->getConnectionByAlias('node01'));
$this->assertSame($connection2, $cluster->getConnectionByAlias('node02'));
}
@@ -71,7 +71,7 @@ class RedisClusterTest extends PredisTestCase
$cluster->add($connection1);
$cluster->add($connection2);
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
$this->assertSame($connection1, $cluster->getConnectionById('127.0.0.1:6379'));
$this->assertSame($connection2, $cluster->getConnectionById('127.0.0.1:6380'));
}
@@ -92,7 +92,7 @@ class RedisClusterTest extends PredisTestCase
$this->assertTrue($cluster->remove($connection1));
$this->assertFalse($cluster->remove($connection3));
$this->assertCount(1, $cluster);
$this->assertSame(1, count($cluster));
}
/**
@@ -110,7 +110,7 @@ class RedisClusterTest extends PredisTestCase
$this->assertTrue($cluster->removeById('127.0.0.1:6380'));
$this->assertFalse($cluster->removeById('127.0.0.1:6390'));
$this->assertCount(1, $cluster);
$this->assertSame(1, count($cluster));
}
/**
@@ -128,11 +128,11 @@ class RedisClusterTest extends PredisTestCase
$cluster->add($connection2);
$cluster->add($connection3);
$this->assertCount(3, $cluster);
$this->assertSame(3, count($cluster));
$cluster->remove($connection3);
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
}
/**
@@ -977,7 +977,7 @@ class RedisClusterTest extends PredisTestCase
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
}
/**
@@ -1033,7 +1033,7 @@ class RedisClusterTest extends PredisTestCase
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
}
/**
@@ -1071,7 +1071,7 @@ class RedisClusterTest extends PredisTestCase
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
}
/**
@@ -1121,7 +1121,7 @@ class RedisClusterTest extends PredisTestCase
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertSame('foobar', $cluster->executeCommand($command));
$this->assertCount(3, $cluster);
$this->assertSame(3, count($cluster));
}
/**
@@ -1259,7 +1259,7 @@ class RedisClusterTest extends PredisTestCase
$cluster->add($connection1);
$this->assertSame('foobar', $cluster->executeCommand($cmdGET));
$this->assertCount(2, $cluster);
$this->assertSame(2, count($cluster));
}
/**
@@ -1301,144 +1301,4 @@ class RedisClusterTest extends PredisTestCase
$this->assertEquals($cluster, $unserialized);
}
/**
* @medium
* @group disconnected
* @group slow
*/
public function testRetryCommandSuccessOnClusterDownErrors()
{
$clusterDownError= new Response\Error("CLUSTERDOWN") ;
$command = Command\RawCommand::create('get', 'node:1001');
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->exactly(3))
->method('executeCommand')
->with($command)
->will($this->onConsecutiveCalls(
$clusterDownError,
$clusterDownError,
'foobar'));
$cluster = new RedisCluster(new Connection\Factory());
$cluster->useClusterSlots(false);
$cluster->setRetryLimit(2);
$cluster->add($connection1);
$this->assertSame('foobar', $cluster->executeCommand($command));
}
/**
* @medium
* @group disconnected
* @group slow
*/
public function testRetryCommandFailureOnClusterDownErrors()
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessage('CLUSTERDOWN');
$clusterDownError= new Response\Error("CLUSTERDOWN") ;
$command = Command\RawCommand::create('get', 'node:1001');
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->exactly(3))
->method('executeCommand')
->with($command)
->will($this->onConsecutiveCalls(
$clusterDownError,
$clusterDownError,
$clusterDownError
));
$cluster = new RedisCluster(new Connection\Factory());
$cluster->useClusterSlots(false);
$cluster->setRetryLimit(2);
$cluster->add($connection1);
$cluster->executeCommand($command);
}
/**
* @medium
* @group disconnected
* @group slow
*/
public function testQueryClusterNodeForSlotMapPauseDurationOnRetry()
{
$slotsmap = array(
array(0, 5460, array('127.0.0.1', 9381), array()),
array(5461, 10922, array('127.0.0.1', 6382), array()),
array(10923, 16383, array('127.0.0.1', 6383), array()),
);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
$connection1
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->willThrowException(
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
);
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10922');
$connection2
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->willThrowException(
new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6383]')
);
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10923-16383');
$connection3
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->willReturn($slotsmap);
$factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
$factory
->expects($this->never())
->method('create');
// TODO: I'm not sure about mocking a protected method, but it'll do for now
/** @var Connection\Cluster\RedisCluster|MockObject */
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\RedisCluster')
->onlyMethods(array('getRandomConnection'))
->setConstructorArgs(array($factory))
->getMock();
$cluster
->expects($this->exactly(3))
->method('getRandomConnection')
->willReturnOnConsecutiveCalls($connection1, $connection2, $connection3);
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$cluster->setRetryInterval(2000);
$startTime = time() ;
$cluster->askSlotMap();
$endTime = time();
$totalTime=$endTime-$startTime;
$t1 = $cluster->getRetryInterval() ;
$t2 = $t1 * 2;
$expectedTime = ($t1 + $t2 )/1000 ; // expected time for 2 retries (fail 1=wait 2s, fail 2=wait 4s , OK)
$this->AssertEqualsWithDelta($expectedTime, $totalTime, 1, "Unexpected execution time") ;
$this->assertCount(16384, $cluster->getSlotMap());
}
}
+314 -2
View File
@@ -44,8 +44,108 @@ class FactoryTest extends PredisTestCase
));
$this->assertSame($defaults, $factory->getDefaultParameters());
}
$parameters = array('database' => 10, 'persistent' => true);
/**
* @group disconnected
*/
public function testSettingDefaultParametersForMasterRole(): void
{
$factory = new Factory();
$factory->setDefaultParameters($expected = array(
'role.master' => [
'username' => 'myusername',
'password' => 'secret',
'database' => 10,
]
));
$this->assertSame($expected, $factory->getDefaultParameters());
}
/**
* @group disconnected
*/
public function testSettingDefaultParametersForMasterRoleAcceptsArrayOnly(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Default parameters for `role.master` must be passed as a named array');
$factory = new Factory();
$factory->setDefaultParameters(array(
'role.master' => 'invalid value',
));
}
/**
* @group disconnected
*/
public function testSettingDefaultParametersForSlaveRole(): void
{
$factory = new Factory();
$factory->setDefaultParameters($expected = array(
'role.slave' => [
'username' => 'myusername',
'password' => 'secret',
'database' => 10,
]
));
$this->assertSame($expected, $factory->getDefaultParameters());
}
/**
* @group disconnected
*/
public function testSettingDefaultParametersForSlaveRoleAcceptsArrayOnly(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Default parameters for `role.slave` must be passed as a named array');
$factory = new Factory();
$factory->setDefaultParameters(array(
'role.slave' => 'invalid value',
));
}
/**
* @group disconnected
*/
public function testSettingDefaultParametersForSentinelRoleIgnoresUsernameAndPassword(): void
{
$factory = new Factory();
$factory->setDefaultParameters(array(
'role.sentinel' => [
'username' => 'myusername',
'password' => 'secret',
'database' => 10,
]
));
$expected = array(
'role.sentinel' => [
'password' => 'secret',
]
);
$this->assertSame($expected, $factory->getDefaultParameters());
}
/**
* @group disconnected
*/
public function testSettingDefaultParametersForSentinelRoleAcceptsArrayOnly(): void
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Default parameters for `role.sentinel` must be passed as a named array');
$factory = new Factory();
$factory->setDefaultParameters(array(
'role.sentinel' => 'invalid value',
));
}
/**
@@ -195,7 +295,7 @@ class FactoryTest extends PredisTestCase
/**
* @group disconnected
*/
public function testCreateConnectionWithArrayParametersAndDefaults(): void
public function testCreateConnectionWithDefaultParametersDoNotOverrideExplicitInputParameters(): void
{
$factory = new Factory();
@@ -223,6 +323,218 @@ class FactoryTest extends PredisTestCase
$this->assertNull($parameters->path);
}
/**
* @group disconnected
*/
public function testCreateConnectionForSentinelRoleIgnoresUsernameAndDatabase(): void
{
$factory = new Factory();
$connection = $factory->create($inputParams = array(
'role' => 'sentinel',
'username' => 'myusername',
'password' => 'mypassword',
'database' => 10,
));
$parameters = $connection->getParameters();
$this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
$this->assertEquals($inputParams['role'], $parameters->role);
$this->assertEquals($inputParams['password'], $parameters->password);
$this->assertNull($parameters->username);
$this->assertNull($parameters->database);
}
/**
* @group disconnected
*/
public function testCreateConnectionForSentinelRoleDoesNotInheritPasswordFromGlobalDefaultParameters(): void
{
$factory = new Factory();
$factory->setDefaultParameters($defaultParams = array(
'password' => 'pwd.default',
));
$connectionSentinelRole = $factory->create($inputParamsSentinelRole = array(
'role' => 'sentinel',
));
$parameters = $connectionSentinelRole->getParameters();
$this->assertNull($parameters->password);
}
/**
* @group disconnected
*/
public function testCreateConnectionForSentinelRoleDoesNotInheritUsernameFromGlobalDefaultParameters(): void
{
$factory = new Factory();
$factory->setDefaultParameters($defaultParams = array(
'username' => 'usr.default',
));
$connectionSentinelRole = $factory->create($inputParamsSentinelRole = array(
'role' => 'sentinel',
));
$parameters = $connectionSentinelRole->getParameters();
$this->assertNull($parameters->username);
}
/**
* @group disconnected
*/
public function testCreateConnectionForSentinelRoleDoesNotInheritDatabaseFromGlobalDefaultParameters(): void
{
$factory = new Factory();
$factory->setDefaultParameters($defaultParams = array(
'database' => 15,
));
$connectionSentinelRole = $factory->create($inputParamsSentinelRole = array(
'role' => 'sentinel',
));
$parameters = $connectionSentinelRole->getParameters();
$this->assertNull($parameters->database);
}
/**
* @group disconnected
*/
public function testCreateConnectionWithDefaultRoleParametersDoNotOverrideExplicitInputParameters(): void
{
$factory = new Factory();
$factory->setDefaultParameters($defaultParams = array(
'timeout' => 20,
'password' => 'pwd.default.norole',
'role.master' => [
'password' => 'pwd.role.master',
'timeout' => 10,
],
'role.slave' => [
'password' => 'pwd.role.slave',
'timeout' => 5,
],
'role.sentinel' => [
'password' => 'pwd.role.sentinel',
'timeout' => 1,
],
));
// NO ROLE
$connectionNoRole = $factory->create($inputParamsNoRole = array(
'password' => 'pwd.local.norole',
'timeout' => 30,
));
$parameters = $connectionNoRole->getParameters();
$this->assertEquals('pwd.local.norole', $parameters->password);
$this->assertEquals(30, $parameters->timeout);
// ROLE MASTER
$connectionMasterRole = $factory->create($inputParamsMasterRole = array(
'role' => 'master',
'password' => 'pwd.local.master',
'timeout' => 30,
));
$parameters = $connectionMasterRole->getParameters();
$this->assertEquals('pwd.local.master', $parameters->password);
$this->assertEquals(30, $parameters->timeout);
// ROLE SLAVE
$connectionSlaveRole = $factory->create($inputParamsSlaveRole = array(
'role' => 'slave',
'password' => 'pwd.local.slave',
'timeout' => 30,
));
$parameters = $connectionSlaveRole->getParameters();
$this->assertEquals('pwd.local.slave', $parameters->password);
$this->assertEquals(30, $parameters->timeout);
// ROLE SENTINEL
$connectionSentinelRole = $factory->create($inputParamsSentinelRole = array(
'role' => 'slave',
'password' => 'pwd.local.sentinel',
'timeout' => 30,
));
$parameters = $connectionSentinelRole->getParameters();
$this->assertEquals('pwd.local.sentinel', $parameters->password);
$this->assertEquals(30, $parameters->timeout);
}
/**
* @group disconnected
*/
public function testCreateConnectionWithDefaultRoleParametersOverridesDefaultGlobalParameters(): void
{
$factory = new Factory();
$factory->setDefaultParameters($defaultParams = array(
'timeout' => 20,
'password' => 'pwd.default.norole',
'role.master' => [
'password' => 'pwd.role.master',
'timeout' => 10,
],
'role.slave' => [
'password' => 'pwd.role.slave',
'timeout' => 5,
],
'role.sentinel' => [
'password' => 'pwd.role.sentinel',
'timeout' => 1,
],
));
// NO ROLE
$connectionNoRole = $factory->create($inputParamsNoRole = array(
// EMPTY
));
$parameters = $connectionNoRole->getParameters();
$this->assertEquals('pwd.default.norole', $parameters->password);
$this->assertEquals(20, $parameters->timeout);
// ROLE MASTER
$connectionMasterRole = $factory->create($inputParamsMasterRole = array(
'role' => 'master',
));
$parameters = $connectionMasterRole->getParameters();
$this->assertEquals('pwd.role.master', $parameters->password);
$this->assertEquals(10, $parameters->timeout);
// ROLE SLAVE
$connectionSlaveRole = $factory->create($inputParamsSlaveRole = array(
'role' => 'slave',
));
$parameters = $connectionSlaveRole->getParameters();
$this->assertEquals('pwd.role.slave', $parameters->password);
$this->assertEquals(5, $parameters->timeout);
// ROLE SENTINEL
$connectionSentinelRole = $factory->create($inputParamsSentinelRole = array(
'role' => 'sentinel',
));
$parameters = $connectionSentinelRole->getParameters();
$this->assertEquals('pwd.role.sentinel', $parameters->password);
$this->assertEquals(1, $parameters->timeout);
}
/**
* @group disconnected
*/
@@ -41,7 +41,7 @@ class SentinelReplicationTest extends PredisTestCase
public function testParametersForSentinelConnectionShouldUsePasswordForAuthentication(): void
{
$replication = $this->getReplicationConnection('svc', array(
'tcp://127.0.0.1:5381?alias=sentinel1&password=secret',
'tcp://127.0.0.1:5381?password=secret',
));
$parameters = $replication->getSentinelConnection()->getParameters()->toArray();
@@ -122,9 +122,9 @@ class SentinelReplicationTest extends PredisTestCase
*/
public function testMethodGetSentinelConnectionReturnsFirstAvailableSentinel(): void
{
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel&alias=sentinel1');
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel&alias=sentinel2');
$sentinel3 = $this->getMockSentinelConnection('tcp://127.0.0.1:5383?role=sentinel&alias=sentinel3');
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel');
$sentinel3 = $this->getMockSentinelConnection('tcp://127.0.0.1:5383?role=sentinel');
$replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2, $sentinel3));
@@ -305,15 +305,16 @@ class SentinelReplicationTest extends PredisTestCase
// TODO: sorry for the smell...
$reflection = new \ReflectionProperty($replication, 'sentinels');
$reflection->setAccessible(true);
$retrievedSentinels = $reflection->getValue($replication);
$expected = array(
array('host' => '127.0.0.1', 'port' => '5381'),
array('host' => '127.0.0.1', 'port' => '5382'),
array('host' => '127.0.0.1', 'port' => '5383'),
$expectedSentinels = array(
array('scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => '5381', 'role' => 'sentinel'),
array('host' => '127.0.0.1', 'port' => '5382', 'role' => 'sentinel'),
array('host' => '127.0.0.1', 'port' => '5383', 'role' => 'sentinel'),
);
$this->assertSame($sentinel1, $replication->getSentinelConnection());
$this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication)));
$this->assertEquals($expectedSentinels, $retrievedSentinels);
}
/**
@@ -321,7 +322,7 @@ class SentinelReplicationTest extends PredisTestCase
*/
public function testMethodUpdateSentinelsRemovesCurrentSentinelAndRetriesNextOneOnFailure(): void
{
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel&alias=sentinel1');
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
$sentinel1
->expects($this->once())
->method('executeCommand')
@@ -332,7 +333,7 @@ class SentinelReplicationTest extends PredisTestCase
new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')
);
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel&alias=sentinel2');
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel');
$sentinel2
->expects($this->once())
->method('executeCommand')
@@ -357,14 +358,15 @@ class SentinelReplicationTest extends PredisTestCase
// TODO: sorry for the smell...
$reflection = new \ReflectionProperty($replication, 'sentinels');
$reflection->setAccessible(true);
$retrievedSentinels = $reflection->getValue($replication);
$expected = array(
array('host' => '127.0.0.1', 'port' => '5382'),
array('host' => '127.0.0.1', 'port' => '5383'),
$expectedSentinels = array(
array('scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => '5382', 'role' => 'sentinel'),
array('host' => '127.0.0.1', 'port' => '5383', 'role' => 'sentinel'),
);
$this->assertSame($sentinel2, $replication->getSentinelConnection());
$this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication)));
$this->assertEquals($expectedSentinels, $retrievedSentinels);
}
/**
@@ -395,7 +397,7 @@ class SentinelReplicationTest extends PredisTestCase
*/
public function testMethodQuerySentinelFetchesMasterNodeSlaveNodesAndSentinelNodes(): void
{
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel&alias=sentinel1');
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
$sentinel1
->expects($this->exactly(3))
->method('executeCommand')
@@ -442,7 +444,7 @@ class SentinelReplicationTest extends PredisTestCase
)
);
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel&alias=sentinel2');
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel');
$master = $this->getMockConnection('tcp://127.0.0.1:6381?role=master');
$slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?role=slave');
@@ -454,14 +456,15 @@ class SentinelReplicationTest extends PredisTestCase
// TODO: sorry for the smell...
$reflection = new \ReflectionProperty($replication, 'sentinels');
$reflection->setAccessible(true);
$retrievedSentinels = $reflection->getValue($replication);
$sentinels = array(
array('host' => '127.0.0.1', 'port' => '5381'),
array('host' => '127.0.0.1', 'port' => '5382'),
$expectedSentinels = array(
array('scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => '5381', 'role' => 'sentinel'),
array('host' => '127.0.0.1', 'port' => '5382', 'role' => 'sentinel'),
);
$this->assertSame($sentinel1, $replication->getSentinelConnection());
$this->assertSame($sentinels, array_intersect_key($sentinels, $reflection->getValue($replication)));
$this->assertEquals($expectedSentinels, $retrievedSentinels);
$master = $replication->getMaster();
$slaves = $replication->getSlaves();
@@ -1447,52 +1450,6 @@ class SentinelReplicationTest extends PredisTestCase
$this->assertEquals($strategy, $unserialized->getReplicationStrategy());
}
/**
* @group disconnected
*/
public function testMethodGetSentinelConnectionAfterSentinelRestart(): void
{
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel&alias=sentinel1');
$sentinel1
->expects($this->exactly(2))
->method('executeCommand')
->with($this->isRedisCommand(
'SENTINEL', array('sentinels', 'svc')
))
->willReturnOnConsecutiveCalls(
$this->throwException(new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')),
array(
array(
'name', '127.0.0.1:5382',
'ip', '127.0.0.1',
'port', '5382',
'runid', 'f53b52d281be5cdd4873700c94846af8dbe47209',
'flags', 'sentinel',
)
)
);
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel&alias=sentinel2');
$sentinel2
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'SENTINEL', array('sentinels', 'svc')
))
->willThrowException(
new Connection\ConnectionException($sentinel2, 'Unknown connection error [127.0.0.1:5382]')
);
$replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2));
try {
$replication->updateSentinels();
} catch (\Predis\ClientException $exception){
$this->assertEquals('No sentinel server available for autodiscovery.', $exception->getMessage());
}
$replication->updateSentinels();
}
// ******************************************************************** //
// ---- HELPER METHODS ------------------------------------------------ //
// ******************************************************************** //
+2 -2
View File
@@ -46,9 +46,9 @@ class ConsumerTest extends PredisTestCase
public function testMonitorConsumerDoesNotWorkOnClusters(): void
{
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Cannot initialize a monitor consumer over cluster connections');
$this->expectExceptionMessage('Cannot initialize a monitor consumer over aggregate connections');
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\ClusterInterface')->getMock();
$cluster = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')->getMock();
$client = new Client($cluster);
new MonitorConsumer($client);
+1 -1
View File
@@ -45,7 +45,7 @@ class ConsumerTest extends PredisTestCase
public function testPubSubConsumerDoesNotWorkOnClusters(): void
{
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Cannot initialize a PUB/SUB consumer over cluster connections');
$this->expectExceptionMessage('Cannot initialize a PUB/SUB consumer over aggregate connections');
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\ClusterInterface')->getMock();
$client = new Client($cluster);
+1 -1
View File
@@ -749,7 +749,7 @@ class MultiExecTest extends PredisTestCase
$tx->set('hoge', 'piyo');
});
$this->assertCount(1, $responses);
$this->assertSame(1, count($responses));
$this->assertSame(0, $client->exists('foo'));
$this->assertSame(1, $client->exists('hoge'));
}