mirror of
https://github.com/predis/predis.git
synced 2026-08-19 22:51:49 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b5fa92856 | |||
| 076a62e3d3 | |||
| 85dc1752b7 | |||
| 7683215023 | |||
| dd4ddf6504 | |||
| 6468e6cc2e | |||
| 12f6d30502 | |||
| a602fc7425 | |||
| 4ec74cc5fe | |||
| 78fd4cb998 | |||
| 2a8dd6a7f1 | |||
| 7d7b3416a8 | |||
| 99c253733d | |||
| 69bb8fa082 | |||
| e5221fa13b | |||
| 15244c9642 | |||
| 4cc119a03c | |||
| 619890f715 | |||
| b060d43157 | |||
| a5ab2bce6b | |||
| 89f20dc968 | |||
| 6af5394a89 | |||
| c508086b0e | |||
| 3c01ccc94c | |||
| e382bedb27 | |||
| a2449da7c8 | |||
| b3a5bdf940 | |||
| 4c1aada5ae | |||
| 54775a0295 | |||
| 367bd72a0b | |||
| 1d7ccb64ab | |||
| e29ae9065f | |||
| 64fc7774c6 | |||
| 554c36b329 | |||
| be9d477564 | |||
| 0e4e8f41cc | |||
| aa0c30809b | |||
| 20f8faee6f | |||
| aa436263f8 | |||
| 3c8856b05a | |||
| d935f6e5c2 | |||
| 87fcc69e7a | |||
| 407ddeca92 | |||
| 338ba6d73d | |||
| 25db9bfec5 | |||
| 0e9aeee90f |
@@ -1,40 +1,52 @@
|
||||
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']
|
||||
redis-versions: ['3', '4', '5', '6']
|
||||
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
|
||||
redis-versions: ['3', '4', '5', '6', '7']
|
||||
|
||||
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
|
||||
|
||||
+34
-1072
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2020 Daniele Alessandri
|
||||
Copyright (c) 2009-2021 Daniele Alessandri
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
||||
@@ -8,21 +8,12 @@
|
||||
|
||||
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 __2.0__ to __3.2__.
|
||||
- Support for Redis from __3.0__ to __7.0__.
|
||||
- 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).
|
||||
@@ -44,6 +35,10 @@ 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 ###
|
||||
|
||||
@@ -169,40 +164,6 @@ 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
|
||||
@@ -268,38 +229,22 @@ 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' => 'myservice'];
|
||||
$options = ['replication' => 'sentinel', 'service' => 'mymaster'];
|
||||
|
||||
$client = new Predis\Client($sentinels, $options);
|
||||
```
|
||||
|
||||
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.
|
||||
If the master and slave nodes are configured to require an authentication from clients, a password
|
||||
must be provided via the global `parameters` client option. This option can also be used to specify
|
||||
a different database index. The client options array would then look like this:
|
||||
|
||||
```php
|
||||
$options = [
|
||||
'replication' => 'sentinel',
|
||||
'service' => 'myservice',
|
||||
'service' => 'mymaster',
|
||||
'parameters' => [
|
||||
'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
|
||||
]
|
||||
'password' => $secretpassword,
|
||||
'database' => 10,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.0 || ^9.0"
|
||||
"phpunit/phpunit": "^8.0 || ~9.4.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
|
||||
|
||||
+3
-4
@@ -40,7 +40,7 @@ use Predis\Transaction\MultiExec as MultiExecTransaction;
|
||||
*/
|
||||
class Client implements ClientInterface, \IteratorAggregate
|
||||
{
|
||||
const VERSION = '2.0.0-dev';
|
||||
const VERSION = '2.0.2';
|
||||
|
||||
/** @var OptionsInterface */
|
||||
private $options;
|
||||
@@ -209,9 +209,7 @@ class Client implements ClientInterface, \IteratorAggregate
|
||||
throw new \InvalidArgumentException("Cannot find a connection by $selector matching `$value`");
|
||||
}
|
||||
|
||||
$client = new static($connection, $this->getOptions());
|
||||
|
||||
return $client;
|
||||
return new static($connection, $this->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,6 +513,7 @@ class Client implements ClientInterface, \IteratorAggregate
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
$clients = array();
|
||||
|
||||
+152
-148
@@ -15,6 +15,7 @@ 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.
|
||||
@@ -24,154 +25,157 @@ use Predis\Connection\ConnectionInterface;
|
||||
* and more friendly interface to ease programming which is described in the
|
||||
* following list of methods:
|
||||
*
|
||||
* @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)
|
||||
* @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)
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
|
||||
@@ -239,9 +239,8 @@ class HashRing implements DistributorInterface, HashGeneratorInterface
|
||||
public function get($value)
|
||||
{
|
||||
$hash = $this->hash($value);
|
||||
$node = $this->getByHash($hash);
|
||||
|
||||
return $node;
|
||||
return $this->getByHash($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,9 +40,8 @@ class PredisStrategy extends ClusterStrategy
|
||||
{
|
||||
$key = $this->extractKeyTag($key);
|
||||
$hash = $this->distributor->hash($key);
|
||||
$slot = $this->distributor->getSlot($hash);
|
||||
|
||||
return $slot;
|
||||
return $this->distributor->getSlot($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,9 +41,8 @@ class RedisStrategy extends ClusterStrategy
|
||||
public function getSlotByKey($key)
|
||||
{
|
||||
$key = $this->extractKeyTag($key);
|
||||
$slot = $this->hashGenerator->hash($key) & 0x3FFF;
|
||||
|
||||
return $slot;
|
||||
return $this->hashGenerator->hash($key) & 0x3FFF;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -125,6 +125,7 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($slot)
|
||||
{
|
||||
return isset($this->slots[$slot]);
|
||||
@@ -137,6 +138,7 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($slot)
|
||||
{
|
||||
if (isset($this->slots[$slot])) {
|
||||
@@ -152,6 +154,7 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($slot, $connection)
|
||||
{
|
||||
if (!static::isValid($slot)) {
|
||||
@@ -168,6 +171,7 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($slot)
|
||||
{
|
||||
unset($this->slots[$slot]);
|
||||
@@ -178,6 +182,7 @@ class SlotMap implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return count($this->slots);
|
||||
@@ -188,6 +193,7 @@ 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($this->match) > 0) {
|
||||
if (strlen(strval($this->match)) > 0) {
|
||||
$options['MATCH'] = $this->match;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
@@ -148,6 +149,7 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
@@ -156,6 +158,7 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -164,6 +167,7 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
tryFetch: {
|
||||
@@ -184,6 +188,7 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
|
||||
@@ -128,6 +128,7 @@ class ListKey implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
@@ -137,6 +138,7 @@ class ListKey implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
@@ -145,6 +147,7 @@ class ListKey implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -153,6 +156,7 @@ class ListKey implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
@@ -169,6 +173,7 @@ class ListKey implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
|
||||
@@ -92,7 +92,7 @@ abstract class Command implements CommandInterface
|
||||
*/
|
||||
public static function normalizeArguments(array $arguments)
|
||||
{
|
||||
if (count($arguments) === 1 && is_array($arguments[0])) {
|
||||
if (count($arguments) === 1 && isset($arguments[0]) && is_array($arguments[0])) {
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ interface CommandInterface
|
||||
/**
|
||||
* Parses a raw response and returns a PHP object.
|
||||
*
|
||||
* @param string $data Binary string containing the whole response.
|
||||
* @param string|array|null $data Binary string containing the whole response.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
@@ -197,7 +197,14 @@ class KeyPrefixProcessor implements ProcessorInterface
|
||||
if ($command instanceof PrefixableCommandInterface) {
|
||||
$command->prefixKeys($this->prefix);
|
||||
} elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) {
|
||||
call_user_func($this->commands[$commandID], $command, $this->prefix);
|
||||
$callable = $this->commands[$commandID];
|
||||
|
||||
if (is_string($callable) && strpos($callable, 'static::') === 0) {
|
||||
$callable = explode('::', $this->commands[$commandID]);
|
||||
$callable[0] = $this;
|
||||
}
|
||||
|
||||
call_user_func($callable, $command, $this->prefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
/**
|
||||
* Returns an iterator over the list of command processor in the chain.
|
||||
*
|
||||
* @return \ArrayIterator
|
||||
* @return \Traversable<int, ProcessorInterface>
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
@@ -91,6 +91,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($index)
|
||||
{
|
||||
return isset($this->processors[$index]);
|
||||
@@ -99,6 +100,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($index)
|
||||
{
|
||||
return $this->processors[$index];
|
||||
@@ -107,6 +109,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($index, $processor)
|
||||
{
|
||||
if (!$processor instanceof ProcessorInterface) {
|
||||
@@ -121,6 +124,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($index)
|
||||
{
|
||||
unset($this->processors[$index]);
|
||||
|
||||
@@ -51,9 +51,8 @@ final class RawCommand implements CommandInterface
|
||||
public static function create($commandID /* [ $arg, ... */)
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
$command = new static(array_shift($arguments), $arguments);
|
||||
|
||||
return $command;
|
||||
return new static(array_shift($arguments), $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,10 @@ class SENTINEL extends RedisCommand
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
switch (strtolower($this->getArgument(0))) {
|
||||
$argument = $this->getArgument(0);
|
||||
$argument = is_null($argument) ? null : strtolower($argument);
|
||||
|
||||
switch ($argument) {
|
||||
case 'masters':
|
||||
case 'slaves':
|
||||
return self::processMastersOrSlaves($data);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,21 @@ 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 $innerException Inner exception for wrapping the original error.
|
||||
* @param \Exception|null $innerException Inner exception for wrapping the original error.
|
||||
*/
|
||||
public function __construct(
|
||||
NodeConnectionInterface $connection,
|
||||
$message = null,
|
||||
$code = null,
|
||||
$message = "",
|
||||
$code = 0,
|
||||
\Exception $innerException = null
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
parent::__construct(
|
||||
is_null($message) ? '' : $message,
|
||||
is_null($code) ? 0 : $code,
|
||||
$innerException
|
||||
);
|
||||
|
||||
parent::__construct($message, $code, $innerException);
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,7 +126,7 @@ abstract class AbstractConnection implements NodeConnectionInterface
|
||||
* @param string $message Error message.
|
||||
* @param int $code Error code.
|
||||
*/
|
||||
protected function onConnectionError($message, $code = null)
|
||||
protected function onConnectionError($message, $code = 0)
|
||||
{
|
||||
CommunicationException::handle(
|
||||
new ConnectionException($this, "$message [{$this->getParameters()}]", $code)
|
||||
|
||||
@@ -138,9 +138,7 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
);
|
||||
}
|
||||
|
||||
$node = $this->distributor->getBySlot($slot);
|
||||
|
||||
return $node;
|
||||
return $this->distributor->getBySlot($slot);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,9 +187,7 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
public function getConnectionByKey($key)
|
||||
{
|
||||
$hash = $this->strategy->getSlotByKey($key);
|
||||
$node = $this->distributor->getBySlot($hash);
|
||||
|
||||
return $node;
|
||||
return $this->distributor->getBySlot($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,6 +204,7 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return count($this->pool);
|
||||
@@ -216,6 +213,7 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->pool);
|
||||
|
||||
@@ -22,6 +22,8 @@ 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).
|
||||
@@ -54,6 +56,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
private $strategy;
|
||||
private $connections;
|
||||
private $retryLimit = 5;
|
||||
private $retryInterval = 10;
|
||||
|
||||
/**
|
||||
* @param FactoryInterface $connections Optional connection factory.
|
||||
@@ -82,6 +85,26 @@ 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}
|
||||
*/
|
||||
@@ -207,6 +230,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
private function queryClusterNodeForSlotMap(NodeConnectionInterface $connection)
|
||||
{
|
||||
$retries = 0;
|
||||
$retryAfter = $this->retryInterval;
|
||||
$command = RawCommand::create('CLUSTER', 'SLOTS');
|
||||
|
||||
RETRY_COMMAND: {
|
||||
@@ -226,7 +250,10 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -442,9 +469,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
}
|
||||
|
||||
$this->move($connection, $slot);
|
||||
$response = $this->executeCommand($command);
|
||||
|
||||
return $response;
|
||||
return $this->executeCommand($command);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -465,9 +490,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
}
|
||||
|
||||
$connection->executeCommand(RawCommand::create('ASKING'));
|
||||
$response = $connection->executeCommand($command);
|
||||
|
||||
return $response;
|
||||
return $connection->executeCommand($command);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -486,24 +509,40 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
private function retryCommandOnFailure(CommandInterface $command, $method)
|
||||
{
|
||||
$failure = false;
|
||||
$retries = 0;
|
||||
$retryAfter = $this->retryInterval;
|
||||
|
||||
RETRY_COMMAND: {
|
||||
try {
|
||||
$response = $this->getConnectionByCommand($command)->$method($command);
|
||||
} catch (ConnectionException $exception) {
|
||||
$connection = $exception->getConnection();
|
||||
$connection->disconnect();
|
||||
|
||||
$this->remove($connection);
|
||||
if ($response instanceof ErrorResponse) {
|
||||
$message = $response->getMessage();
|
||||
|
||||
if ($failure) {
|
||||
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) {
|
||||
throw $exception;
|
||||
} elseif ($this->useClusterSlots) {
|
||||
$this->askSlotMap();
|
||||
}
|
||||
|
||||
$failure = true;
|
||||
++$retries;
|
||||
|
||||
goto RETRY_COMMAND;
|
||||
}
|
||||
@@ -545,6 +584,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return count($this->pool);
|
||||
@@ -553,6 +593,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
if ($this->slotmap->isEmpty()) {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
namespace Predis\Connection;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Predis\Command\RawCommand;
|
||||
|
||||
/**
|
||||
@@ -120,28 +119,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -155,37 +132,6 @@ 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.
|
||||
*
|
||||
@@ -198,19 +144,11 @@ class Factory implements FactoryInterface
|
||||
if (is_string($parameters)) {
|
||||
$parameters = Parameters::parse($parameters);
|
||||
} else {
|
||||
$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']);
|
||||
$parameters = $parameters ?: array();
|
||||
}
|
||||
|
||||
if ($this->defaults) {
|
||||
$parameters = $this->applyDefaultParameters($parameters);
|
||||
$parameters += $this->defaults;
|
||||
}
|
||||
|
||||
return new Parameters($parameters);
|
||||
|
||||
@@ -26,6 +26,14 @@ 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.
|
||||
*/
|
||||
|
||||
@@ -66,9 +66,9 @@ class PhpiredisSocketConnection extends AbstractConnection
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
|
||||
parent::__destruct();
|
||||
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,9 +227,7 @@ class PhpiredisSocketConnection extends AbstractConnection
|
||||
$protocol = SOL_TCP;
|
||||
}
|
||||
|
||||
$socket = @socket_create($domain, SOCK_STREAM, $protocol);
|
||||
|
||||
if (!is_resource($socket)) {
|
||||
if (false === $socket = @socket_create($domain, SOCK_STREAM, $protocol)) {
|
||||
$this->emitSocketError();
|
||||
}
|
||||
|
||||
@@ -344,7 +342,9 @@ class PhpiredisSocketConnection extends AbstractConnection
|
||||
public function disconnect()
|
||||
{
|
||||
if ($this->isConnected()) {
|
||||
phpiredis_reader_reset($this->reader);
|
||||
socket_close($this->getResource());
|
||||
|
||||
parent::disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,19 @@ class PhpiredisStreamConnection extends StreamConnection
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
|
||||
parent::__destruct();
|
||||
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
phpiredis_reader_reset($this->reader);
|
||||
|
||||
parent::disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
|
||||
@@ -69,6 +70,11 @@ class SentinelReplication implements ReplicationInterface
|
||||
*/
|
||||
protected $sentinels = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $sentinelIndex = 0;
|
||||
|
||||
/**
|
||||
* @var NodeConnectionInterface
|
||||
*/
|
||||
@@ -151,14 +157,14 @@ class SentinelReplication implements ReplicationInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time to wait (in seconds) before fetching a new configuration
|
||||
* Sets the time to wait (in milliseconds) before fetching a new configuration
|
||||
* from one of the sentinels.
|
||||
*
|
||||
* @param float $seconds Time to wait before the next attempt.
|
||||
* @param float $milliseconds Time to wait before the next attempt.
|
||||
*/
|
||||
public function setRetryWait($seconds)
|
||||
public function setRetryWait($milliseconds)
|
||||
{
|
||||
$this->retryWait = (float) $seconds;
|
||||
$this->retryWait = (float) $milliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,8 +247,6 @@ 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)
|
||||
@@ -256,19 +260,21 @@ class SentinelReplication implements ReplicationInterface
|
||||
}
|
||||
|
||||
if (is_array($parameters)) {
|
||||
// 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';
|
||||
// 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;
|
||||
|
||||
if (!isset($parameters['timeout'])) {
|
||||
$parameters['timeout'] = $this->sentinelTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
$connection = $this->connectionFactory->create($parameters);
|
||||
|
||||
return $connection;
|
||||
return $this->connectionFactory->create($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,11 +287,13 @@ class SentinelReplication implements ReplicationInterface
|
||||
public function getSentinelConnection()
|
||||
{
|
||||
if (!$this->sentinelConnection) {
|
||||
if (!$this->sentinels) {
|
||||
if ($this->sentinelIndex >= count($this->sentinels)) {
|
||||
$this->sentinelIndex = 0;
|
||||
throw new \Predis\ClientException('No sentinel server available for autodiscovery.');
|
||||
}
|
||||
|
||||
$sentinel = array_shift($this->sentinels);
|
||||
$sentinel = $this->sentinels[$this->sentinelIndex];
|
||||
++$this->sentinelIndex;
|
||||
$this->sentinelConnection = $this->createSentinelConnection($sentinel);
|
||||
}
|
||||
|
||||
@@ -306,6 +314,7 @@ 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();
|
||||
|
||||
@@ -536,13 +545,17 @@ 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
|
||||
* @throws RoleException|ConnectionException
|
||||
*/
|
||||
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]");
|
||||
}
|
||||
|
||||
@@ -151,9 +151,7 @@ class StreamConnection extends AbstractConnection
|
||||
}
|
||||
}
|
||||
|
||||
$resource = $this->createStreamSocket($parameters, $address, $flags);
|
||||
|
||||
return $resource;
|
||||
return $this->createStreamSocket($parameters, $address, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,9 +181,7 @@ class StreamConnection extends AbstractConnection
|
||||
}
|
||||
}
|
||||
|
||||
$resource = $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags);
|
||||
|
||||
return $resource;
|
||||
return $this->createStreamSocket($parameters, "unix://{$parameters->path}", $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,7 +360,7 @@ class StreamConnection extends AbstractConnection
|
||||
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
$arglen = strlen($argument);
|
||||
$arglen = strlen(strval($argument));
|
||||
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace Predis\Monitor;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
use Predis\Connection\AggregateConnectionInterface;
|
||||
use Predis\Connection\Cluster\ClusterInterface;
|
||||
use Predis\NotSupportedException;
|
||||
|
||||
/**
|
||||
@@ -56,9 +56,9 @@ class Consumer implements \Iterator
|
||||
*/
|
||||
private function assertClient(ClientInterface $client)
|
||||
{
|
||||
if ($client->getConnection() instanceof AggregateConnectionInterface) {
|
||||
if ($client->getConnection() instanceof ClusterInterface) {
|
||||
throw new NotSupportedException(
|
||||
'Cannot initialize a monitor consumer over aggregate connections.'
|
||||
'Cannot initialize a monitor consumer over cluster connections.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ class Consumer implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
// NOOP
|
||||
@@ -101,6 +102,7 @@ class Consumer implements \Iterator
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->getValue();
|
||||
@@ -109,6 +111,7 @@ class Consumer implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -117,6 +120,7 @@ class Consumer implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
++$this->position;
|
||||
@@ -127,6 +131,7 @@ class Consumer implements \Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Predis\Protocol;
|
||||
use Predis\CommunicationException;
|
||||
|
||||
/**
|
||||
* Exception used to indentify errors encountered while parsing the Redis wire
|
||||
* Exception used to identify errors encountered while parsing the Redis wire
|
||||
* protocol.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
|
||||
@@ -95,9 +95,7 @@ class ResponseReader implements ResponseReaderInterface
|
||||
$this->onProtocolError($connection, "Unknown response prefix: '$prefix'");
|
||||
}
|
||||
|
||||
$payload = $this->handlers[$prefix]->handle($connection, substr($header, 1));
|
||||
|
||||
return $payload;
|
||||
return $this->handlers[$prefix]->handle($connection, substr($header, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -151,6 +151,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
// NOOP
|
||||
@@ -162,6 +163,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->getValue();
|
||||
@@ -170,6 +172,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -178,6 +181,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
if ($this->valid()) {
|
||||
@@ -192,6 +196,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
$isValid = $this->isFlagSet(self::STATUS_VALID);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Predis\PubSub;
|
||||
use Predis\ClientException;
|
||||
use Predis\ClientInterface;
|
||||
use Predis\Command\Command;
|
||||
use Predis\Connection\AggregateConnectionInterface;
|
||||
use Predis\Connection\Cluster\ClusterInterface;
|
||||
use Predis\NotSupportedException;
|
||||
|
||||
/**
|
||||
@@ -62,9 +62,9 @@ class Consumer extends AbstractConsumer
|
||||
*/
|
||||
private function checkCapabilities(ClientInterface $client)
|
||||
{
|
||||
if ($client->getConnection() instanceof AggregateConnectionInterface) {
|
||||
if ($client->getConnection() instanceof ClusterInterface) {
|
||||
throw new NotSupportedException(
|
||||
'Cannot initialize a PUB/SUB consumer over aggregate connections.'
|
||||
'Cannot initialize a PUB/SUB consumer over cluster connections.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ class ReplicationStrategy
|
||||
}
|
||||
|
||||
if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
|
||||
$sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
|
||||
$argument = $command->getArgument(0);
|
||||
$sha1 = $eval ? sha1(strval($argument)) : $argument;
|
||||
|
||||
if (isset($this->readonlySHA1[$sha1])) {
|
||||
if (true === $readonly = $this->readonlySHA1[$sha1]) {
|
||||
|
||||
@@ -34,6 +34,7 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
// NOOP
|
||||
@@ -42,6 +43,7 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
@@ -50,6 +52,7 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -58,6 +61,7 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
if (++$this->position < $this->size) {
|
||||
@@ -68,6 +72,7 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->position < $this->size;
|
||||
@@ -82,6 +87,7 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return $this->size;
|
||||
|
||||
@@ -61,6 +61,7 @@ class MultiBulkTuple extends MultiBulk implements \OuterIterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getInnerIterator()
|
||||
{
|
||||
return $this->iterator;
|
||||
|
||||
@@ -59,7 +59,7 @@ class Status implements ResponseInterface
|
||||
*
|
||||
* @param string $payload Status response payload.
|
||||
*
|
||||
* @return string
|
||||
* @return self
|
||||
*/
|
||||
public static function get($payload)
|
||||
{
|
||||
|
||||
@@ -54,6 +54,7 @@ class Handler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function open($save_path, $session_id)
|
||||
{
|
||||
// NOOP
|
||||
@@ -63,6 +64,7 @@ class Handler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function close()
|
||||
{
|
||||
// NOOP
|
||||
@@ -72,6 +74,7 @@ class Handler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function gc($maxlifetime)
|
||||
{
|
||||
// NOOP
|
||||
@@ -81,6 +84,7 @@ class Handler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function read($session_id)
|
||||
{
|
||||
if ($data = $this->client->get($session_id)) {
|
||||
@@ -92,6 +96,7 @@ class Handler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function write($session_id, $session_data)
|
||||
{
|
||||
$this->client->setex($session_id, $this->ttl, $session_data);
|
||||
@@ -102,6 +107,7 @@ class Handler implements \SessionHandlerInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function destroy($session_id)
|
||||
{
|
||||
$this->client->del($session_id);
|
||||
|
||||
@@ -27,9 +27,10 @@ class AbortedMultiExecException extends PredisException
|
||||
* @param string $message Error message.
|
||||
* @param int $code Error code.
|
||||
*/
|
||||
public function __construct(MultiExec $transaction, $message, $code = null)
|
||||
public function __construct(MultiExec $transaction, $message, $code = 0)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
parent::__construct($message, is_null($code) ? 0 : $code);
|
||||
|
||||
$this->transaction = $transaction;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use Predis\ClientException;
|
||||
use Predis\ClientInterface;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\CommunicationException;
|
||||
use Predis\Connection\AggregateConnectionInterface;
|
||||
use Predis\Connection\Cluster\ClusterInterface;
|
||||
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 AggregateConnectionInterface) {
|
||||
if ($client->getConnection() instanceof ClusterInterface) {
|
||||
throw new NotSupportedException(
|
||||
'Cannot initialize a MULTI/EXEC transaction over aggregate connections.'
|
||||
'Cannot initialize a MULTI/EXEC transaction over cluster connections.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Util\Test as TestUtil;
|
||||
use Predis\Client;
|
||||
use Predis\Command;
|
||||
use Predis\Connection;
|
||||
@@ -315,7 +316,10 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
|
||||
*/
|
||||
protected function getRequiredRedisServerVersion(): ?string
|
||||
{
|
||||
$annotations = $this->getAnnotations();
|
||||
$annotations = TestUtil::parseTestMethodAnnotations(
|
||||
get_class($this),
|
||||
$this->getName(false)
|
||||
);
|
||||
|
||||
if (isset($annotations['method']['requiresRedisVersion'], $annotations['method']['group']) &&
|
||||
!empty($annotations['method']['requiresRedisVersion']) &&
|
||||
|
||||
@@ -236,7 +236,7 @@ class SlotMapTest extends PredisTestCase
|
||||
{
|
||||
$slotmap = new SlotMap();
|
||||
|
||||
$this->assertSame(0, count($slotmap));
|
||||
$this->assertCount(0, $slotmap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,13 +247,13 @@ class SlotMapTest extends PredisTestCase
|
||||
$slotmap = new SlotMap();
|
||||
|
||||
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
|
||||
$this->assertSame(5461, count($slotmap));
|
||||
$this->assertCount(5461, $slotmap);
|
||||
|
||||
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
|
||||
$this->assertSame(10923, count($slotmap));
|
||||
$this->assertCount(10923, $slotmap);
|
||||
|
||||
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
|
||||
$this->assertSame(16384, count($slotmap));
|
||||
$this->assertCount(16384, $slotmap);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,13 +106,34 @@ 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 yhe request with a list of the ACL categories associated
|
||||
// command in the 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
|
||||
|
||||
@@ -104,7 +104,7 @@ class CONFIG_Test extends PredisCommandTestCase
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertIsArray($configs = $redis->config('GET', 'dbfilename'));
|
||||
$this->assertEquals(1, count($configs));
|
||||
$this->assertCount(1, $configs);
|
||||
$this->assertArrayHasKey('dbfilename', $configs);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,13 @@ class CONFIG_Test extends PredisCommandTestCase
|
||||
public function testThrowsExceptionWhenSettingUnknownConfiguration(): void
|
||||
{
|
||||
$this->expectException('Predis\Response\ServerException');
|
||||
$this->expectExceptionMessage('ERR Unsupported CONFIG parameter: foo');
|
||||
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'");
|
||||
}
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ BUFFER;
|
||||
$redis = $this->getClient();
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertInternalType('array', $info = $redis->executeCommand($command));
|
||||
$this->assertIsArray($info = $redis->executeCommand($command));
|
||||
$this->assertArrayHasKey('redis_version', $info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +92,19 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class MOVE_Test extends PredisCommandTestCase
|
||||
public function testThrowsExceptionOnInvalidDatabases(): void
|
||||
{
|
||||
$this->expectException('Predis\Response\ServerException');
|
||||
$this->expectExceptionMessage('ERR index out of range');
|
||||
$this->expectExceptionMessageMatches('/ERR.*out of range/');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class SELECT_Test extends PredisCommandTestCase
|
||||
public function testThrowsExceptionOnUnexpectedDatabaseName(): void
|
||||
{
|
||||
$this->expectException('Predis\Response\ServerException');
|
||||
$this->expectExceptionMessage('ERR invalid DB index');
|
||||
$this->expectExceptionMessageMatches('/ERR.*(invalid DB index|value is not an integer or out of range)/');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
|
||||
@@ -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->assertEquals(
|
||||
count($unordered),
|
||||
$this->assertCount(
|
||||
$redis->sort('list:unordered', array(
|
||||
'store' => 'list:ordered',
|
||||
))
|
||||
)),
|
||||
$unordered
|
||||
);
|
||||
|
||||
$this->assertEquals(array(1, 2, 3, 10, 30, 100), $redis->lrange('list:ordered', 0, -1));
|
||||
|
||||
@@ -101,6 +101,22 @@ 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
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ class RedisFactoryTest extends PredisTestCase
|
||||
$this->assertTrue($factory->supports('mock'));
|
||||
$this->assertTrue($factory->supports('MOCK'));
|
||||
|
||||
$this->assertSame(get_class($command), $factory->getCommandClass('mock'));
|
||||
$this->assertInstanceOf($factory->getCommandClass('mock'), $command);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,7 @@ class PredisClusterTest extends PredisTestCase
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
$this->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $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->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $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->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $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->assertSame(1, count($cluster));
|
||||
$this->assertCount(1, $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->assertSame(1, count($cluster));
|
||||
$this->assertCount(1, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,11 +128,11 @@ class RedisClusterTest extends PredisTestCase
|
||||
$cluster->add($connection2);
|
||||
$cluster->add($connection3);
|
||||
|
||||
$this->assertSame(3, count($cluster));
|
||||
$this->assertCount(3, $cluster);
|
||||
|
||||
$cluster->remove($connection3);
|
||||
|
||||
$this->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -977,7 +977,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1033,7 +1033,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1071,7 +1071,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1121,7 +1121,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame(3, count($cluster));
|
||||
$this->assertCount(3, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1259,7 +1259,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
$cluster->add($connection1);
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($cmdGET));
|
||||
$this->assertSame(2, count($cluster));
|
||||
$this->assertCount(2, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1301,4 +1301,144 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,108 +44,8 @@ class FactoryTest extends PredisTestCase
|
||||
));
|
||||
|
||||
$this->assertSame($defaults, $factory->getDefaultParameters());
|
||||
}
|
||||
|
||||
/**
|
||||
* @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',
|
||||
));
|
||||
$parameters = array('database' => 10, 'persistent' => true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,7 +195,7 @@ class FactoryTest extends PredisTestCase
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testCreateConnectionWithDefaultParametersDoNotOverrideExplicitInputParameters(): void
|
||||
public function testCreateConnectionWithArrayParametersAndDefaults(): void
|
||||
{
|
||||
$factory = new Factory();
|
||||
|
||||
@@ -323,218 +223,6 @@ 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?password=secret',
|
||||
'tcp://127.0.0.1:5381?alias=sentinel1&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');
|
||||
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel');
|
||||
$sentinel3 = $this->getMockSentinelConnection('tcp://127.0.0.1:5383?role=sentinel');
|
||||
$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');
|
||||
|
||||
$replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2, $sentinel3));
|
||||
|
||||
@@ -305,16 +305,15 @@ class SentinelReplicationTest extends PredisTestCase
|
||||
// TODO: sorry for the smell...
|
||||
$reflection = new \ReflectionProperty($replication, 'sentinels');
|
||||
$reflection->setAccessible(true);
|
||||
$retrievedSentinels = $reflection->getValue($replication);
|
||||
|
||||
$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'),
|
||||
$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'),
|
||||
);
|
||||
|
||||
$this->assertSame($sentinel1, $replication->getSentinelConnection());
|
||||
$this->assertEquals($expectedSentinels, $retrievedSentinels);
|
||||
$this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,7 +321,7 @@ class SentinelReplicationTest extends PredisTestCase
|
||||
*/
|
||||
public function testMethodUpdateSentinelsRemovesCurrentSentinelAndRetriesNextOneOnFailure(): void
|
||||
{
|
||||
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
|
||||
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel&alias=sentinel1');
|
||||
$sentinel1
|
||||
->expects($this->once())
|
||||
->method('executeCommand')
|
||||
@@ -333,7 +332,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');
|
||||
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel&alias=sentinel2');
|
||||
$sentinel2
|
||||
->expects($this->once())
|
||||
->method('executeCommand')
|
||||
@@ -358,15 +357,14 @@ class SentinelReplicationTest extends PredisTestCase
|
||||
// TODO: sorry for the smell...
|
||||
$reflection = new \ReflectionProperty($replication, 'sentinels');
|
||||
$reflection->setAccessible(true);
|
||||
$retrievedSentinels = $reflection->getValue($replication);
|
||||
|
||||
$expectedSentinels = array(
|
||||
array('scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => '5382', 'role' => 'sentinel'),
|
||||
array('host' => '127.0.0.1', 'port' => '5383', 'role' => 'sentinel'),
|
||||
$expected = array(
|
||||
array('host' => '127.0.0.1', 'port' => '5382'),
|
||||
array('host' => '127.0.0.1', 'port' => '5383'),
|
||||
);
|
||||
|
||||
$this->assertSame($sentinel2, $replication->getSentinelConnection());
|
||||
$this->assertEquals($expectedSentinels, $retrievedSentinels);
|
||||
$this->assertSame($expected, array_intersect_key($expected, $reflection->getValue($replication)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,7 +395,7 @@ class SentinelReplicationTest extends PredisTestCase
|
||||
*/
|
||||
public function testMethodQuerySentinelFetchesMasterNodeSlaveNodesAndSentinelNodes(): void
|
||||
{
|
||||
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
|
||||
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel&alias=sentinel1');
|
||||
$sentinel1
|
||||
->expects($this->exactly(3))
|
||||
->method('executeCommand')
|
||||
@@ -444,7 +442,7 @@ class SentinelReplicationTest extends PredisTestCase
|
||||
)
|
||||
);
|
||||
|
||||
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel');
|
||||
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel&alias=sentinel2');
|
||||
|
||||
$master = $this->getMockConnection('tcp://127.0.0.1:6381?role=master');
|
||||
$slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?role=slave');
|
||||
@@ -456,15 +454,14 @@ class SentinelReplicationTest extends PredisTestCase
|
||||
// TODO: sorry for the smell...
|
||||
$reflection = new \ReflectionProperty($replication, 'sentinels');
|
||||
$reflection->setAccessible(true);
|
||||
$retrievedSentinels = $reflection->getValue($replication);
|
||||
|
||||
$expectedSentinels = array(
|
||||
array('scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => '5381', 'role' => 'sentinel'),
|
||||
array('host' => '127.0.0.1', 'port' => '5382', 'role' => 'sentinel'),
|
||||
$sentinels = array(
|
||||
array('host' => '127.0.0.1', 'port' => '5381'),
|
||||
array('host' => '127.0.0.1', 'port' => '5382'),
|
||||
);
|
||||
|
||||
$this->assertSame($sentinel1, $replication->getSentinelConnection());
|
||||
$this->assertEquals($expectedSentinels, $retrievedSentinels);
|
||||
$this->assertSame($sentinels, array_intersect_key($sentinels, $reflection->getValue($replication)));
|
||||
|
||||
$master = $replication->getMaster();
|
||||
$slaves = $replication->getSlaves();
|
||||
@@ -1450,6 +1447,52 @@ 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 ------------------------------------------------ //
|
||||
// ******************************************************************** //
|
||||
|
||||
@@ -46,9 +46,9 @@ class ConsumerTest extends PredisTestCase
|
||||
public function testMonitorConsumerDoesNotWorkOnClusters(): void
|
||||
{
|
||||
$this->expectException('Predis\NotSupportedException');
|
||||
$this->expectExceptionMessage('Cannot initialize a monitor consumer over aggregate connections');
|
||||
$this->expectExceptionMessage('Cannot initialize a monitor consumer over cluster connections');
|
||||
|
||||
$cluster = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')->getMock();
|
||||
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\ClusterInterface')->getMock();
|
||||
$client = new Client($cluster);
|
||||
|
||||
new MonitorConsumer($client);
|
||||
|
||||
@@ -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 aggregate connections');
|
||||
$this->expectExceptionMessage('Cannot initialize a PUB/SUB consumer over cluster connections');
|
||||
|
||||
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\ClusterInterface')->getMock();
|
||||
$client = new Client($cluster);
|
||||
|
||||
@@ -749,7 +749,7 @@ class MultiExecTest extends PredisTestCase
|
||||
$tx->set('hoge', 'piyo');
|
||||
});
|
||||
|
||||
$this->assertSame(1, count($responses));
|
||||
$this->assertCount(1, $responses);
|
||||
$this->assertSame(0, $client->exists('foo'));
|
||||
$this->assertSame(1, $client->exists('hoge'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user