Compare commits

..

1 Commits

Author SHA1 Message Date
vladvildanov 59b60a403e Changed default dialect to 2 2025-02-24 17:13:57 +02:00
652 changed files with 5989 additions and 23129 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[codespell]
skip=./.git,./src/Command/Redis/Search/FTSUGGET.php,./examples/Commands/Search/ft_sug_add_get_del_len.php
skip=./.git
check-hidden=
check-filenames=
builtin=clear,rare,informal,usage,code,names
+2 -2
View File
@@ -4,7 +4,7 @@ services:
redis-official:
image: ${REDIS_IMAGE_NAME:-redis:7.4}
container_name: redis-official
container_name: redis-standalone
healthcheck:
test: [ "CMD", "redis-cli", "PING" ]
interval: 10s
@@ -37,7 +37,7 @@ services:
- standalone
redis-cluster:
image: ${REDIS_IMAGE_NAME:-redislabs/client-libs-test:7.4.2}
image: ${REDIS_CLUSTER_IMAGE_NAME:-redislabs/client-libs-test:7.4.2}
container_name: redis-cluster
environment:
- REDIS_CLUSTER=yes
-2
View File
@@ -1,8 +1,6 @@
name-template: 'v$NEXT_MINOR_VERSION'
tag-template: 'v$NEXT_MINOR_VERSION'
change-template: '- $TITLE (#$NUMBER)'
filter-by-comitish: true
commitish: v3.x
autolabeler:
- label: 'maintenance'
-29
View File
@@ -1,29 +0,0 @@
matrix:
- name: Markdown
expect_match: false
apsell:
lang: en
d: en_US
ignore-case: true
dictionary:
wordlists:
- .github/wordlist.txt
output: wordlist.dic
pipeline:
- pyspelling.filters.markdown:
markdown_extensions:
- markdown.extensions.extra:
- pyspelling.filters.html:
comments: false
attributes:
- alt
ignores:
- ':matches(code, pre)'
- code
- pre
- blockquote
- img
sources:
- 'README.md'
- 'FAQ.md'
- 'docs/**'
-53
View File
@@ -1,53 +0,0 @@
ACLs
Autoloading
CAS
Customizable
ElastiCache
FPM
IANA
Lua
PSR
Packagist
PhpRedis
Predis
README
Redis
SHA
SSL
TCP
OSS
CLI
TLS
URI
autoload
autoloader
autoloading
backend
backends
behaviour
customizable
dataset
de
DevOPS
extensibility
keyspace
keyspaces
localhost
namespace
pipelining
pluggable
rebalanced
rebalancing
redis
runtime
sharding
stunnel
variadic
commitish
Kubernetes
programmatically
unhandled
invalidations
Sharded
sharded
Valkey
+18 -25
View File
@@ -7,7 +7,6 @@ on:
branches:
- main
- v2.**
- v3.**
pull_request: null
permissions: {}
@@ -144,31 +143,25 @@ jobs:
echo "EXPECTED=${EXPECTED}"
test "${CURRENT}" = "${EXPECTED}"
changelog:
name: Changelog
spelling:
name: Spelling
needs:
- byte_level
- syntax_errors
runs-on: ubuntu-latest
timeout-minutes: 5
if: >-
github.event_name == 'pull_request' &&
!contains(github.event.head_commit.message, 'nochangelog') &&
!contains(github.event.head_commit.message, 'no-changelog') &&
!contains(github.event.head_commit.message, 'no changelog') &&
!contains(github.event.pull_request.labels.*.name, 'no-changelog')
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Checkout repository
uses: actions/checkout@v3
- name: Check for CHANGELOG entry
env:
TARGET: ${{ github.event.pull_request.base.ref }}
run: |
FILES_CHANGED=$(git diff --name-only origin/$TARGET...HEAD | grep -E 'CHANGELOG\.md' -c)
if [ "$FILES_CHANGED" != "1" ]; then
echo "CHANGELOG.md was not updated";
exit 1;
fi;
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-codespell
- name: Install codespell
run: pip install --user 'codespell>=2.2'
- name: Search for misspellings
run: $(python -m site --user-base)/bin/codespell
-9
View File
@@ -4,24 +4,15 @@ on:
push:
branches:
- main
- v2.**
- v3.**
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
update_release_draft:
permissions:
contents: write
pull-requests: write
name: Update release draft
runs-on: ubuntu-latest
-14
View File
@@ -1,14 +0,0 @@
name: spellcheck
on:
pull_request:
jobs:
check-spelling:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Check Spelling
uses: rojopolis/spellcheck-github-actions@0.33.1
with:
config_path: .github/spellcheck-settings.yml
task_name: Markdown
+22 -70
View File
@@ -5,7 +5,6 @@ on:
branches:
- main
- v2.**
- v3.**
pull_request:
concurrency:
@@ -28,6 +27,7 @@ jobs:
- '8.4'
redis:
- '4.0'
- '6.2'
- '7.2'
- '7.4'
- '8.0'
@@ -38,20 +38,14 @@ jobs:
run: |
# Mapping of original redis versions to client test containers
declare -A redis_clients_version_mapping=(
["8.0"]="8.0-RC2-pre"
["8.0"]="8.0-M04-pre"
["7.4"]="7.4.2"
["7.2"]="7.2.7"
)
# Mapping of redis version to stack version
declare -A redis_stack_version_mapping=(
["7.4"]="rs-7.4.0-v3"
["7.2"]="rs-7.2.0-v15"
["6.2"]="6.2.17"
)
if [[ -v redis_clients_version_mapping[${{ matrix.redis }}] ]]; then
echo "REDIS_IMAGE_NAME=redislabs/client-libs-test:${redis_clients_version_mapping[${{ matrix.redis }}]}" >> $GITHUB_ENV
echo "REDIS_STACK_IMAGE_NAME=redislabs/client-libs-test:${redis_stack_version_mapping[${{ matrix.redis }}]}" >> $GITHUB_ENV
echo "DOCKER_SERVICE=redis-clients" >> $GITHUB_ENV
redis_major_version=$(echo "${{ matrix.redis }}" | grep -oP '^\d+')
@@ -59,11 +53,7 @@ jobs:
# Some configuration options available since Redis > 7
if (( redis_major_version < 7 )); then
echo "REDIS_EXTRA_ARGS="--tls-auth-clients optional --save ''"" >> $GITHUB_ENV
else
# Since 8.0 modules are bundled with core
echo "REDIS_STACK_SERVER_PORT=6379" >> $GITHUB_ENV
fi
else
echo "REDIS_IMAGE_NAME=redis:${{ matrix.redis }}" >> $GITHUB_ENV
echo "DOCKER_SERVICE=redis-official" >> $GITHUB_ENV
@@ -79,17 +69,15 @@ jobs:
services: ${{ env.DOCKER_SERVICE }}
- name: Start Redis stack image
id: stack_infra
uses: hoverkraft-tech/compose-action@v2.0.1
if: ${{ matrix.redis >= '7.2' && matrix.redis < '8.0' }}
if: ${{ matrix.redis == '7.4' }}
with:
compose-file: .github/docker-compose.yml
services: redis-stack
- name: Start Redis cluster image
id: cluster_infra
uses: hoverkraft-tech/compose-action@v2.0.1
if: ${{ matrix.redis > '4.0' }}
if: ${{ matrix.redis == '7.4' }}
with:
compose-file: .github/docker-compose.yml
services: redis-cluster
@@ -99,7 +87,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: relay
coverage: ${{ (matrix.php == '8.4' && matrix.redis == '7.4') && 'xdebug' || 'none' }}
coverage: ${{ (matrix.php == '8.1' && matrix.redis == '7.4') && 'xdebug' || 'none' }}
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
@@ -108,79 +96,43 @@ jobs:
composer-options: ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }}
- name: Run tests
if: ${{ matrix.php != '8.4' || matrix.redis != '7.4' }}
if: ${{ matrix.php != '8.1' || matrix.redis != '7.4' }}
run: vendor/bin/phpunit
- name: Run tests with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit --coverage-php build/cov/coverage-predis.cov --coverage-filter ./src
if: ${{ matrix.php == '8.1' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit --coverage-clover build/logs/clover-default.xml --coverage-filter ./src
- name: Run tests using Relay
if: ${{ matrix.php != '8.4' && matrix.redis >= '7.2' }}
if: ${{ matrix.php != '8.4' && matrix.redis >= '6.2' }}
run: vendor/bin/phpunit -c phpunit.relay.xml
- name: Run tests using Relay with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit -c phpunit.relay.xml --coverage-php build/cov/coverage-relay.cov --coverage-filter ./src
if: ${{ matrix.php == '8.1' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit -c phpunit.relay.xml --coverage-clover build/logs/clover-relay.xml --coverage-filter ./src
- name: Run stack tests
if: ${{ (matrix.php != '8.4' || matrix.redis != '7.4') && matrix.redis >= '7.2' }}
if: ${{ matrix.redis == '7.4' }}
run: vendor/bin/phpunit --group realm-stack
- name: Run stack tests with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' && steps.stack_infra.conclusion == 'success' }}
run: vendor/bin/phpunit --group realm-stack --coverage-php build/cov/coverage-stack.cov --coverage-filter ./src
env:
REDIS_STACK_SERVER_PORT: 6479
- name: Run stack tests using Relay
if: ${{ matrix.php != '8.4' && matrix.redis == '7.4' }}
if: ${{ matrix.redis == '7.4' }}
run: vendor/bin/phpunit --group realm-stack -c phpunit.relay.xml
- name: Run stack tests using Relay with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit --group realm-stack -c phpunit.relay.xml --coverage-php build/cov/coverage-stack-relay.cov --coverage-filter ./src
env:
REDIS_STACK_SERVER_PORT: 6479
- name: Run tests against cluster
if: ${{ (matrix.php != '8.4' || matrix.redis != '7.4') && steps.cluster_infra.conclusion == 'success' }}
if: ${{ matrix.redis == '7.4' }}
run: |
vendor/bin/phpunit --group cluster
- name: Run tests against cluster with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' && steps.cluster_infra.conclusion == 'success' }}
run: |
vendor/bin/phpunit --group cluster --coverage-php build/cov/coverage-cluster.cov --coverage-filter ./src
- name: Run tests against cluster using Relay
if: ${{ matrix.php != '8.4' && matrix.redis == '7.4' }}
if: ${{ matrix.redis == '7.4' }}
run: |
vendor/bin/phpunit -c phpunit.relay.xml --group cluster
- name: Run tests against cluster using Relay with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: |
vendor/bin/phpunit -c phpunit.relay.xml --group cluster --coverage-php build/cov/coverage-cluster-relay.cov --coverage-filter ./src
- name: Merge coverage reports
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: php vendor/bin/phpcov merge --clover build/logs/clover.xml build/cov
- name: Send coverage to Coveralls
uses: coverallsapp/github-action@v2
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true
finish:
name: Finish Coverall
needs: predis
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ env.COVERALLS_REPO_TOKEN && matrix.php == '8.1' && matrix.redis == '7.4' }}
run: |
wget "https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.3/php-coveralls.phar"
php ./php-coveralls.phar -v
+32 -44
View File
@@ -1,49 +1,8 @@
## Changelog
## v3.0.0 (2025-05-02)
### Added
- ⚠️ Added support for transactions for clustered connections (#1497)
- Added support for RESP3 communication protocol (#1047)
- Added support for RESP3 push notifications (#1316)
- Added support for Sharded Pub/Sub (#1303)
- Added support for `XGROUP` commands (#1324)
- Added support for `XREADGROUP` command (#1327)
- Added support for `XAUTOCLAIM` command (#1328)
- Added support for `XINFO` commands (#1331)
- Added support for Redis 7.4 `XREAD` and `HSCAN` interface changes (#1452)
### Changed
- ⚠️ Refactored Relay integration (#1423)
- ⚠️ Changed interface of `CLIENT` command (#1337)
- ⚠️ Changed interfaces for commands to support Redis 6.2 — 7.0 arguments (#1330)
- ⚠️ Changed default search `DIALECT` to `2` (#1516)
- ⚠️ Implemented PSR-7 compatible stream abstraction (#1450)
- ⚠️ Improved pipeline abstractions (#1438)
- ⚠️ Improved connection handshake (#1431)
## Unreleased
### Fixed
- Fixed single persistent connection using the same resource (#1512)
## v2.4.0 (2025-04-30)
### Added
- Added new hash-field expiration commands (#1520)
- Added missing `FT._LIST` and `BITFIELD_RO` commands (#1521)
### Changed
- Update `WATCH` command to accept `string|string[]` (#1476)
- Optimize cluster slotmap with compact slot range object (#1493)
### Fixed
- Fixed `EVAL_RO` cluster support (#1449)
- Fixed PHP 8.4 compatibility with `stream_context_set_option()` (#1503)
- Prevent named arguments runtime failure (#1509)
- Mark `GEOSEARCH` as read-only to ensure execution on replica (#1481)
- Fixed protocol loss during redis cluster `MOVED` / `ASK` (#1530)
### Maintenance
- Added CI testing with Redis 8.0 (#1510)
- Added test coverage for compatibility with Redis 8.0 (#1513)
- Use parallel on PHP-CS-Fixer (#1489)
- Fixed PHP 8.4 compatibility with `stream_context_set_option()`
## v2.3.0 (2024-11-21)
### Added
@@ -59,7 +18,36 @@
- Filter out available replicas based on link status flag (#1440)
- Respect `prefix` for `ZPOPMIN`, `ZPOPMAX`, `ZMSCORE`, `LMOVE`, `BLMOVE`, `SMISMEMBER` and `GEOSEARCH` (#1451, #1453, #1455, #1468)
- Updated test infrastructure (#1510)
-
### Fixed
- Fixed Relay support when using Redis Cluster (#1397)
- Fixed `cmsincrby()` type annotation (#1333)
- Fixed `set()` type annotation (#1394)
- Fixed operator precedence in connection factory (#1405)
- Fixed a bug with `null` arguments in `set()` method (#1470, #1471)
## v2.2.2 (2023-09-13)
### Added
- Added `client_info` client parameter
- Added support for `CLUSTER` container command
### Fixed
- Fixed `EXPIRETIME` not using `prefix`
- Disabled `CLIENT SETINFO` calls by default
## v2.2.1 (2023-08-15)
### Added
- Added support for `WAITAOF` command (#1357)
- Added support for `SHUTDOWN` command (#1359)
- Added support for `FUNCTION` command (#1332)
- Added support for new optional `PEXPIRE`, `PEXPIREAT` and `COMMAND`
- Added missing Redis Stack commands to `KeyPrefixProcessor` (#1358)
### Changed
- Set client name and version when establishing a connection (#1347)
## v2.2.0 (2023-06-14)
Predis v2.2.0 introduces official support for [Redis Stack](https://redis.io/docs/stack/) as well as a [Relay](https://github.com/cachewerk/relay) integration for substantially [faster read performance](https://github.com/predis/predis/wiki/Using-Relay).
+57 -9
View File
@@ -81,17 +81,65 @@ An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which m
## When should I use Relay? ##
If you care about performance, __always__. [Relay][relay] is free to use.
If you care about performance, __always__. [Relay](https://github.com/cachewerk/relay) is free to use.
## When should I use PhpRedis? ###
Predis is fast enough when Redis is located on the same machine as PHP.
Predis is fast enough when Redis is located on the same machine as PHP, more on that later.
[PhpRedis][phpredis] and [Relay][relay] perform significantly better when network I/O is involved,
due to its ability to compress data by ~75%. Fewer bytes and received sent over the network
[means faster operations][performance], and potentially cost savings when network traffic isn't
free (e.g. AWS ElastiCache Inter-AZ transfer costs).
[PhpRedis](https://github.com/phpredis/phpredis) (and Relay) perform significantly better when
network I/O is involved, due to their ability to compress data by ~75%. Fewer bytes and received
sent over the network [means faster operations](https://akalongman.medium.com/phpredis-vs-predis-comparison-on-real-production-data-a819b48cbadb),
and potentially cost savings when network traffic isn't free (e.g. AWS ElastiCache Inter-AZ transfer costs).
[phpredis]: https://github.com/phpredis/phpredis
[relay]: [https://github.com/phpredis/phpredis](https://github.com/cachewerk/relay)
[performance]: https://akalongman.medium.com/phpredis-vs-predis-comparison-on-real-production-data-a819b48cbadb
## Predis is a pure-PHP implementation: it can not be fast enough! ##
It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you a
couple of easy numbers with a simple test that uses a single client and is executed by PHP 5.5.6
against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6600:
```
21000 SET/sec using 12 bytes for both key and value.
21000 GET/sec while retrieving the very same values.
0.130 seconds to fetch 30000 keys using _KEYS *_.
```
How does it compare with [__PhpRedis__](http://github.com/phpredis/phpredis), a nice C extension
providing an efficient client for Redis?
```
30100 SET/sec using 12 bytes for both key and value
29400 GET/sec while retrieving the very same values
0.035 seconds to fetch 30000 keys using "KEYS *"".
```
Wow __PhpRedis__ seems much faster! Well, we are comparing a C extension with a pure-PHP library so
lower numbers are quite expected but there is a fundamental flaw in them: is this really how you are
going to use Redis in your application? Are you really going to send thousands of commands using a
for-loop on each page request using a single client instance? If so... well I guess you are probably
doing something wrong. Also, if you need to `SET` or `GET` multiple keys you should definitely use
commands such as `MSET` and `MGET`. You can also use pipelining to get more performances when this
technique can be used.
There is one more thing: we have tested the overhead of Predis by connecting on a localhost instance
of Redis but how these numbers change when we hit the physical network by connecting to remote Redis
instances?
```
Using Predis:
3200 SET/sec using 12 bytes for both key and value
3200 GET/sec while retrieving the very same values
0.132 seconds to fetch 30000 keys using "KEYS *".
Using PhpRedis:
3500 SET/sec using 12 bytes for both key and value
3500 GET/sec while retrieving the very same values
0.045 seconds to fetch 30000 keys using "KEYS *".
```
There you go, you get almost the same average numbers and the reason is simple: network latency is a
real performance killer and you cannot do (almost) anything about that. As a disclaimer, remember
that we are measuring the overhead of client libraries implementations and the effects of network
round-trip times, so we are not really measuring how fast Redis is. Redis shines best with thousands
of concurrent clients doing requests! Also, actual performances should be measured according to how
your application will use Redis.
+1 -183
View File
@@ -14,7 +14,7 @@ More details about this project can be found on the [frequently asked questions]
## Main features ##
- Support for Redis from __3.0__ to __8.0__.
- Support for Redis from __3.0__ to __7.4__.
- 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).
@@ -159,7 +159,6 @@ when needed. The client options supported by default in Predis are:
- `aggregate`: configures the client with a custom aggregate connection (callable).
- `parameters`: list of default connection parameters for aggregate connections.
- `commands`: specifies a command factory instance to use through the library.
- `readTimeout`: (cluster only) Timeout between read operations while loop over connections.
Users can also provide custom options with values or callable objects (for lazy initialization) that
are stored in the options container for later use through the library.
@@ -201,26 +200,6 @@ $options = ['cluster' => 'redis'];
$client = new Predis\Client($parameters, $options);
```
#### Redis Gears with cluster ####
Since Redis v7.2, Redis Gears module is a part of Redis Stack bundle. Client supports a variety of
Redis Gears commands that can be used with OSS cluster API. Currently, before using any Redis
Gears commands against OSS cluster Redis server needs to be aware of cluster topology.
`REDISGEARS_2.REFRESHCLUSTER` command should be called against **each master node** (read replicas
should be ignored) **on cluster creation and each time cluster topology changes**.
In most cases this actions should be performed from the CLI interface by the administrator, DevOPS
or even Kubernetes, depends on your infrastructure managing process. However, client provides an API
to do this programmatically.
```php
/** @var \Predis\Connection\Cluster\ClusterInterface $connection */
$connection->executeCommandOnEachNode(
new \Predis\Command\RawCommand('REDISGEARS_2.REFRESHCLUSTER')
);
```
#### Replication ####
The client can be configured to operate in a single master / multiple slaves setup to provide better
@@ -336,25 +315,6 @@ This abstraction can perform check-and-set operations thanks to `WATCH` and `UNW
automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example
of a transaction using CAS you can see [the following example](examples/transaction_using_cas.php).
#### Support for clustered connections ####
Since Predis v3.0 transactions could be used with clustered connections. However, it has some limitations due to the
fact that Redis doesn't support distributed transactions. All keys in the transaction context should operate on the same
hash slot, due to this limitation it's recommended to use `{}` syntax to make sure that all keys will be mapped to the same hash
slot. Apart from it no additional configuration needed on a client side.
```php
$redis = $this->getClient();
$response = $redis->transaction(function (MultiExec $tx) {
$tx->set('{foo}foo', 'value');
$tx->set('{foo}bar', 'value');
$tx->set('{foo}baz', 'value');
});
// ['OK', 'OK', 'OK']
```
### Adding new commands ###
@@ -465,149 +425,7 @@ $client = new Predis\Client('tcp://127.0.0.1', [
For a more in-depth insight on how to create new connection backends you can refer to the actual
implementation of the standard connection classes available in the `Predis\Connection` namespace.
## RESP3 ##
### Connection ###
To establish the connection using the [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md) protocol, you need to set parameter `protocol => 3`. The default protocol is RESP2.
You can pass parameter as configuration option in array or as a query parameter in `redis_url`
```php
// Configuration option
$client = new \Predis\Client(['protocol' => 3]);
// Redis URL
$client = new \Predis\Client('redis://localhost:6379?protocol=3');
// ["proto" => "3"]
$client->executeRaw(['HELLO']);
```
### Command responses ###
RESP3 protocol introduce a variety of new [response types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#resp3-types),
so on the client-side we have more explicit understanding on data types we retrieve from server. Here's some examples to show the difference
between RESP2 and RESP3 responses.
#### Float responses ####
``` php
// RESP2 connection
$client = new \Predis\Client();
$client->geoadd('my_geo', 11.111, 22.222, 'member1');
// [[0 => string(20) "11.11099988222122192", 1 => string(20) "22.22200052541037252"]]
// RESP2 returns float values as simple strings.
var_dump($client->geopos('my_geo', ['member1']));
// RESP3 connection
$client = new \Predis\Client(['protocol' => 3]);
// [[0 => float(11.110999882221222), 1 => float(22.222000525410373)]]
// RESP3 introduces new double type, that corresponds to PHP float.
var_dump($client->geopos('my_geo', ['member1']));
```
#### Aggregate types ####
In RESP3 new aggregate type [Map](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#map-type)
was introduced, that represents the sequence of field-value pairs. So it simplifies parsing, since we don't need to specify
parsing strategy per command (RESP2) and instead relies on the type defined by protocol (RESP3).
In most cases RESP2 responses shouldn't differ from RESP3, since we added additional parsing for those
command that return field-value pairs. However, since RESP2 requires additional parsing, it could be that some commands
had lack of it and return unhandled responses. In this case there would be difference like this:
```php
$client = new \Predis\Client();
// RESP2: ['field', 'value]
$client->commandThatReturnsFieldValuePair('key');
$client = new \Predis\Client(['protocol' => 3]);
// RESP3: ['field' => 'value]
$client->commandThatReturnsFieldValuePair('key');
```
Feel free to open PR or GitHub issue if you face those protocol mismatching.
### Push notifications ###
RESP3 introduce a concept of [push connection](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#push-type),
is the one where server could send asynchronous data to client which was not explicitly requested. Predis 3.0 provides
an API to establish this kind of connection as separate blocking process (worker) and invoke callbacks depends on push
notification message type.
#### Consumer ####
First of all, you need to set up a consumer connection and provide an optional callback that will be executed before
event loop will be started. It allows you to subscribe on channels, enable keys invalidations tracking or enable monitor
connection, any Redis command to let server know that you want to receive push notification within this connection.
```php
// Make sure that RESP3 protocol enabled and read_write_timeout set 0,
// so connection won't be killed by timeout.
$client = new Predis\Client(['read_write_timeout' => 0, 'protocol' => 3]);
// Create push notifications consumer.
// Provides callback where current consumer subscribes to few channels before
// enter the loop.
$push = $client->push(static function (ClientInterface $client) {
$response = $client->subscribe('channel', 'control');
$status = ($response[2] === 1) ? 'OK' : 'FAILED';
echo "Channel subscription status: {$status}\n";
});
```
#### Dispatcher loop ####
Dispatcher object allows you to attach a callback to given push notification type and run the actual worker process that
listen for incoming push notifications. To be able to stop blocking process in runtime you can specify a condition and
call `$dispatcher->stop()` method from given callback. In this example we're waiting for specific message `terminate`
within `control` channel that we subscribed to before entering the loop.
```php
// Storage for incoming notifications.
$messages = [];
// Create dispatcher for push notifications.
$dispatcher = new Predis\Consumer\Push\DispatcherLoop($push);
$dispatcher->attachCallback(
PushResponseInterface::MESSAGE_DATA_TYPE,
static function (array $payload, DispatcherLoopInterface $dispatcher) {
global $messages;
[$channel, $message] = $payload;
if ($channel === 'control' && $message === 'terminate') {
echo "Terminating notification consumer.\n";
$dispatcher->stop();
return;
}
$messages[] = $message;
echo "Received message: {$message}\n";
}
);
// Run consumer loop with attached callbacks.
$dispatcher->run();
// Count all messages that were received during consumer loop.
$messagesCount = count($messages);
echo "We received: {$messagesCount} messages\n";
```
This example shows a simple script to count all incoming messages from push notifications that we receive from
subscribed channels until stop condition will be met. Examples available in `examples/` folder.
### Sharded pub/sub ###
From Redis 7.0, sharded Pub/Sub is introduced in which shard channels are assigned to slots by the same algorithm used
to assign keys to slots.
Predis 3.0 provides an API that allows to use pub/sub for Cluster connections using sharded pub/sub from Redis.
You don't need to specify any additional configuration to enable sharded pub/sub, it will be automatically enabled if
Cluster connection is using.
Implementation looks pretty much the same as Push notification, so you need to set up consumer
and run it over Dispatcher loop object. All examples available in `examples/` folder.
## Development ##
+1 -1
View File
@@ -1 +1 @@
3.0.0
2.3.1-dev
+2 -4
View File
@@ -22,14 +22,12 @@
}
],
"require": {
"php": "^7.2 || ^8.0",
"psr/http-message": "^1.0|^2.0"
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^8.0 || ~9.4.4",
"phpunit/phpcov": "^6.0 || ^8.0"
"phpunit/phpunit": "^8.0 || ^9.4"
},
"suggest": {
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
+1 -1
View File
@@ -30,7 +30,7 @@ $client = new Predis\Client($single_server + ['read_write_timeout' => 0]);
$pubsub = $client->pubSubLoop();
// Create a dispatcher loop instance and attach a bunch of callbacks.
$dispatcher = new Predis\Consumer\PubSub\DispatcherLoop($pubsub);
$dispatcher = new Predis\PubSub\DispatcherLoop($pubsub);
// Demonstrate how to use a callable class as a callback for the dispatcher loop.
class EventsListener implements Countable
-49
View File
@@ -1,49 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\ClientInterface;
use Predis\Consumer\Push\PushResponseInterface;
require __DIR__ . '/shared.php';
// 1. Create client with RESP3 protocol specified. Push notifications allowed only in RESP3 mode.
$client = new Predis\Client($single_server + ['read_write_timeout' => 0, 'protocol' => 3]);
// 2. Create push notifications consumer. Provides callback where current consumer subscribes to few channels before enter the loop.
$push = $client->push(static function (ClientInterface $client) {
$response = $client->subscribe('channel', 'control');
$status = ($response[2] === 1) ? 'OK' : 'FAILED';
echo "Channel subscription status: {$status}\n";
});
// 3. Run consumer that will handle message data type push notifications. And stops if certain message will be sent to control channel.
// Send following commands via redis-cli to test:
//
// PUBLISH channel message1
// PUBLISH channel message2
// PUBLISH channel message3
// PUBLISH control terminate
// Data types should be changed in near future. Instead of Message data type it should be one of kind data types.
foreach ($push as $notification) {
if ((null !== $notification) && $notification->getDataType() === PushResponseInterface::MESSAGE_DATA_TYPE) {
if ($notification[1] === 'control' && $notification[2] === 'terminate') {
echo "Terminating notification consumer.\n";
$push->stop();
break;
}
$message = $notification[2];
echo "Received message: {$message}\n";
}
}
@@ -1,68 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\ClientInterface;
use Predis\Consumer\DispatcherLoopInterface;
use Predis\Consumer\Push\DispatcherLoop;
use Predis\Consumer\Push\PushResponseInterface;
require __DIR__ . '/shared.php';
// 1. Create client with RESP3 protocol specified. Push notifications allowed only in RESP3 mode.
$client = new Predis\Client($single_server + ['read_write_timeout' => 0, 'protocol' => 3]);
// 2. Create push notifications consumer. Provides callback where current consumer subscribes to few channels before enter the loop.
$push = $client->push(static function (ClientInterface $client) {
$response = $client->subscribe('channel', 'control');
$status = ($response[2] === 1) ? 'OK' : 'FAILED';
echo "Channel subscription status: {$status}\n";
});
// 3. Storage for upcoming notifications.
$messages = [];
// 4. Create dispatcher for push notifications.
$dispatcher = new DispatcherLoop($push);
// 5. Attach callback for message data type. Print every message and store them in storage.
// Send following commands via redis-cli to test:
//
// PUBLISH channel message1
// PUBLISH channel message2
// PUBLISH channel message3
// PUBLISH control terminate
// Data types should be changed in near future. Instead of Message data type it should be one of kind data types.
$dispatcher->attachCallback(
PushResponseInterface::MESSAGE_DATA_TYPE,
static function (array $payload, DispatcherLoopInterface $dispatcher) {
global $messages;
[$channel, $message] = $payload;
if ($channel === 'control' && $message === 'terminate') {
echo "Terminating notification consumer.\n";
$dispatcher->stop();
return;
}
$messages[] = $message;
echo "Received message: {$message}\n";
}
);
// 6. Run consumer loop with attached callbacks.
$dispatcher->run();
// 7. Count all messages that were received during consumer loop.
$messagesCount = count($messages);
echo "We received: {$messagesCount} messages\n";
-91
View File
@@ -1,91 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/shared.php';
// This is a basic example on how to use the Predis\DispatcherLoop class in sharded pub/sub mode.
//
// Both channels belong to different shards. So in sharded mode we can subscribe
// and receive messages from different shard channels.
//
// To see this example in action you can just use redis-cli and publish some
// messages to the '{channels}_events' and 'control' channel, e.g.:
// ./redis-cli
// SPUBLISH {channels}_events first
// SPUBLISH {channels}_events second
// SPUBLISH {channels}_events third
// SPUBLISH control terminate_dispatcher
// 1. Create client and setup RW timeout to 0.
$client = new Client(
[
'tcp://127.0.0.1:6372?read_write_timeout=0',
'tcp://127.0.0.1:6373?read_write_timeout=0',
'tcp://127.0.0.1:6374?read_write_timeout=0',
], [
'cluster' => 'redis',
]);
// 2. Run pub/sub loop.
$pubSub = $client->pubSubLoop();
// 3. Create a dispatcher loop instance and attach a bunch of callbacks.
$dispatcher = new Predis\Consumer\PubSub\DispatcherLoop($pubSub);
// 4. Demonstrate how to use a callable class as a callback for the dispatcher loop.
class EventsListener implements Countable
{
private $events;
public function __construct()
{
$this->events = [];
}
public function count()
{
return count($this->events);
}
public function getEvents()
{
return $this->events;
}
public function __invoke($payload, $dispatcher)
{
$this->events[] = $payload;
}
}
// 5. Attach our callable class to the dispatcher.
$dispatcher->attachCallback('{channels}_events', $events = new EventsListener());
// 6. Attach a function to control the dispatcher loop termination with a message.
$dispatcher->attachCallback('control', function ($payload, $dispatcher) {
if ($payload === 'terminate_dispatcher') {
$dispatcher->stop();
}
});
// 7. Run the dispatcher loop until the callback attached to the 'control' channel
// receives 'terminate_dispatcher' as a message.
$dispatcher->run();
// Display our achievements!
echo "We received {$events->count()} messages!", PHP_EOL;
// Say goodbye :-)
echo 'Goodbye from Redis!', PHP_EOL;
-64
View File
@@ -1,64 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Predis\Client;
require __DIR__ . '/shared.php';
// 1. Create client and setup RW timeout to 0.
$client = new Client(
[
'tcp://127.0.0.1:6372?read_write_timeout=0',
'tcp://127.0.0.1:6373?read_write_timeout=0',
'tcp://127.0.0.1:6374?read_write_timeout=0',
], [
'cluster' => 'redis',
]);
// 2. Run pub/sub loop. Sharded channels belongs to different shards.
$pubSub = $client->pubSubLoop();
$pubSub->ssubscribe('{channels}_notifications');
$pubSub->ssubscribe('control_channel');
// Start processing the pubsup messages. Open a terminal and use redis-cli
// to push messages to the channels. Examples:
// ./redis-cli SPUBLISH {channels}_notifications "this is a test"
// ./redis-cli SPUBLISH control_channel quit_loop
foreach ($pubSub as $message) {
switch ($message->kind) {
case 'ssubscribe':
echo "Subscribed to {$message->channel}", PHP_EOL;
break;
case 'message':
if ($message->channel == 'control_channel') {
if ($message->payload == 'quit_loop') {
echo 'Aborting pubsub loop...', PHP_EOL;
$pubSub->sunsubscribe();
} else {
echo "Received an unrecognized command: {$message->payload}.", PHP_EOL;
}
} else {
echo "Received the following message from {$message->channel}:",
PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL;
}
break;
}
}
// Always unset the pubsub consumer instance when you are done! The
// class destructor will take care of cleanups and prevent protocol
// desynchronizations between the client and the server.
unset($pubsub);
// Say goodbye :-)
echo 'Goodbye from Redis!', PHP_EOL;
+6
View File
@@ -12,6 +12,9 @@ parameters:
- message: "#^Cannot cast Predis\\\\Connection\\\\ConnectionInterface to string\\.$#"
count: 1
path: src/Client.php
# phpiredis extension is not installed
- "#^Function phpiredis_\\w+ not found\\.$#"
- "#^Constant PHPIREDIS_#"
# "new static" in non-final classes
- message: "#^Unsafe usage of new static\\(\\)\\.$#"
count: 3
@@ -26,6 +29,9 @@ parameters:
- message: "#^Call to an undefined method Predis\\\\Command\\\\FactoryInterface\\:\\:undefine\\(\\)\\.$#"
count: 1
path: src/Configuration/Option/Commands.php
- message: "#^Access to an undefined property Predis\\\\Configuration\\\\OptionsInterface\\:\\:\\$parameters\\.$#"
count: 1
path: src/Configuration/Option/Connections.php
- message: "#^Access to an undefined property Predis\\\\Connection\\\\ParametersInterface\\:\\:\\$weight\\.$#"
count: 1
path: src/Connection/Cluster/PredisCluster.php
+3 -6
View File
@@ -17,18 +17,15 @@
</testsuite>
</testsuites>
<logging>
<log type="coverage-php" target="build/cov/coverage.cov"/>
</logging>
<groups>
<exclude>
<group>relay-incompatible</group>
<group>relay-resp3</group>
<group>realm-webdis</group>
<group>realm-stack</group>
<group>ext-curl</group>
<group>ext-phpiredis</group>
<group>cluster</group>
<group>gears</group>
</exclude>
</groups>
@@ -41,9 +38,9 @@
<php>
<const name="REDIS_SERVER_HOST" value="127.0.0.1" />
<const name="REDIS_SERVER_PORT" value="6379" />
<const name="REDIS_STACK_SERVER_PORT" value="6479" />
<const name="REDIS_SERVER_DBNUM" value="0" />
<env name="USE_RELAY" value="true" />
<env name="REDIS_STACK_SERVER_PORT" value="6479" />
<!-- Redis Cluster -->
<!-- Only master nodes endpoints included -->
+11 -6
View File
@@ -17,18 +17,23 @@
</testsuite>
</testsuites>
<logging>
<log type="coverage-php" target="build/cov/coverage.cov"/>
</logging>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
<groups>
<exclude>
<group>realm-webdis</group>
<group>realm-stack</group>
<group>ext-relay</group>
<group>ext-curl</group>
<group>ext-phpiredis</group>
<group>cluster</group>
<group>gears</group>
<group>gears-cluster</group>
<!-- <group>connected</group> -->
<!-- <group>disconnected</group> -->
<!-- <group>commands</group> -->
@@ -45,9 +50,9 @@
<php>
<const name="REDIS_SERVER_HOST" value="127.0.0.1" />
<const name="REDIS_SERVER_PORT" value="6379" />
<const name="REDIS_STACK_SERVER_PORT" value="6479" />
<const name="REDIS_SERVER_DBNUM" value="0" />
<env name="USE_RELAY" value="false" />
<env name="REDIS_STACK_SERVER_PORT" value="6479" />
<!-- Redis Cluster -->
<!-- Only master nodes endpoints included -->
+6 -23
View File
@@ -16,9 +16,9 @@ use ArrayIterator;
use InvalidArgumentException;
use IteratorAggregate;
use Predis\Command\CommandInterface;
use Predis\Command\Container\ContainerFactory;
use Predis\Command\Container\ContainerInterface;
use Predis\Command\RawCommand;
use Predis\Command\Redis\Container\ContainerFactory;
use Predis\Command\Redis\Container\ContainerInterface;
use Predis\Command\ScriptCommand;
use Predis\Configuration\Options;
use Predis\Configuration\OptionsInterface;
@@ -26,15 +26,14 @@ use Predis\Connection\ConnectionInterface;
use Predis\Connection\Parameters;
use Predis\Connection\ParametersInterface;
use Predis\Connection\RelayConnection;
use Predis\Consumer\PubSub\Consumer as PubSubConsumer;
use Predis\Consumer\PubSub\RelayConsumer as RelayPubSubConsumer;
use Predis\Consumer\Push\Consumer as PushConsumer;
use Predis\Monitor\Consumer as MonitorConsumer;
use Predis\Pipeline\Atomic;
use Predis\Pipeline\FireAndForget;
use Predis\Pipeline\Pipeline;
use Predis\Pipeline\RelayAtomic;
use Predis\Pipeline\RelayPipeline;
use Predis\PubSub\Consumer as PubSubConsumer;
use Predis\PubSub\RelayConsumer as RelayPubSubConsumer;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
use Predis\Response\ResponseInterface;
use Predis\Response\ServerException;
@@ -54,7 +53,7 @@ use Traversable;
*/
class Client implements ClientInterface, IteratorAggregate
{
public const VERSION = '3.0.0';
public const VERSION = '2.3.1-dev';
/** @var OptionsInterface */
private $options;
@@ -380,7 +379,6 @@ class Client implements ClientInterface, IteratorAggregate
public function executeCommand(CommandInterface $command)
{
$response = $this->connection->executeCommand($command);
$parameters = $this->connection->getParameters();
if ($response instanceof ResponseInterface) {
if ($response instanceof ErrorResponseInterface) {
@@ -390,11 +388,7 @@ class Client implements ClientInterface, IteratorAggregate
return $response;
}
if ($parameters->protocol === 2) {
return $command->parseResponse($response);
}
return $command->parseResp3Response($response);
return $command->parseResponse($response);
}
/**
@@ -555,17 +549,6 @@ class Client implements ClientInterface, IteratorAggregate
return $this->sharedContextFactory('createPubSub', func_get_args());
}
/**
* Creates new push notifications consumer.
*
* @param callable|null $preLoopCallback Callback that should be called on client before enter a loop.
* @return PushConsumer
*/
public function push(?callable $preLoopCallback = null): PushConsumer
{
return new PushConsumer($this, $preLoopCallback);
}
/**
* Actual publish/subscribe context initializer method.
*
+17 -28
View File
@@ -38,13 +38,12 @@ use Predis\Command\Argument\TimeSeries\MGetArguments;
use Predis\Command\Argument\TimeSeries\MRangeArguments;
use Predis\Command\Argument\TimeSeries\RangeArguments;
use Predis\Command\CommandInterface;
use Predis\Command\Container\ACL;
use Predis\Command\Container\CLIENT;
use Predis\Command\Container\FUNCTIONS;
use Predis\Command\Container\Json\JSONDEBUG;
use Predis\Command\Container\Search\FTCONFIG;
use Predis\Command\Container\Search\FTCURSOR;
use Predis\Command\Container\XGROUP;
use Predis\Command\Redis\Container\ACL;
use Predis\Command\Redis\Container\CLUSTER;
use Predis\Command\Redis\Container\FunctionContainer;
use Predis\Command\Redis\Container\Json\JSONDEBUG;
use Predis\Command\Redis\Container\Search\FTCONFIG;
use Predis\Command\Redis\Container\Search\FTCURSOR;
/**
* Interface defining a client-side context such as a pipeline or transaction.
@@ -60,8 +59,8 @@ use Predis\Command\Container\XGROUP;
* @method $this move($key, $db)
* @method $this object($subcommand, $key)
* @method $this persist($key)
* @method $this pexpire($key, $milliseconds, string $option = null)
* @method $this pexpireat($key, $timestamp, string $option = null)
* @method $this pexpire($key, $milliseconds)
* @method $this pexpireat($key, $timestamp)
* @method $this pttl($key)
* @method $this randomkey()
* @method $this rename($key, $target)
@@ -84,7 +83,6 @@ use Predis\Command\Container\XGROUP;
* @method $this bitcount(string $key, $start = null, $end = null, string $index = 'byte')
* @method $this bitop($operation, $destkey, $key)
* @method $this bitfield($key, $subcommand, ...$subcommandArg)
* @method $this bitfield_ro(string $key, ?array $encodingOffsetMap = null)
* @method $this bitpos($key, $bit, $start = null, $end = null, string $index = 'byte')
* @method $this blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1)
* @method $this bzpopmax(array $keys, int $timeout)
@@ -113,7 +111,6 @@ use Predis\Command\Container\XGROUP;
* @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1)
* @method $this fcall(string $function, array $keys, ...$args)
* @method $this fcall_ro(string $function, array $keys, ...$args)
* @method $this ft_list()
* @method $this ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
* @method $this ftaliasadd(string $alias, string $index)
* @method $this ftaliasdel(string $alias)
@@ -165,9 +162,7 @@ use Predis\Command\Container\XGROUP;
* @method $this hpexpireat(string $key, int $unixTimeMilliseconds, array $fields, string $flag = null)
* @method $this hpexpiretime(string $key, array $fields)
* @method $this hget($key, $field)
* @method $this hgetex(string $key, array $fields, string $modifier = HGETEX::NULL)
* @method $this hgetall($key)
* @method $this hgetdel(string $key, array $fields)
* @method $this hincrby($key, $field, $increment)
* @method $this hincrbyfloat($key, $field, $increment)
* @method $this hkeys($key)
@@ -177,7 +172,6 @@ use Predis\Command\Container\XGROUP;
* @method $this hrandfield(string $key, int $count = 1, bool $withValues = false)
* @method $this hscan($key, $cursor, ?array $options = null)
* @method $this hset($key, $field, $value)
* @method $this hsetex(string $key, array $fieldValueMap, string $setModifier = HSETEX::SET_NULL, string $ttlModifier = HSETEX::TTL_NULL, int|bool $ttlModifierValue = false)
* @method $this hsetnx($key, $field, $value)
* @method $this httl(string $key, array $fields)
* @method $this hpttl(string $key, array $fields)
@@ -241,9 +235,6 @@ use Predis\Command\Container\XGROUP;
* @method $this srandmember($key, $count = null)
* @method $this srem($key, $member)
* @method $this sscan($key, $cursor, ?array $options = null)
* @method $this ssubscribe(string ...$shardChannels)
* @method $this subscribe(string ...$channels)
* @method $this sunsubscribe(?string ...$shardChannels = null)
* @method $this sunion(array|string $keys)
* @method $this sunionstore($destination, array|string $keys)
* @method $this tdigestadd(string $key, float ...$value)
@@ -324,7 +315,6 @@ use Predis\Command\Container\XGROUP;
* @method $this multi()
* @method $this unwatch()
* @method $this waitaof(int $numLocal, int $numReplicas, int $timeout)
* @method $this unsubscribe(string ...$channels)
* @method $this watch($key)
* @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method $this eval_ro(string $script, array $keys, ...$argument)
@@ -338,18 +328,18 @@ use Predis\Command\Container\XGROUP;
* @method $this select($database)
* @method $this bgrewriteaof()
* @method $this bgsave()
* @method $this client($subcommand, $argument = null)
* @method $this config($subcommand, $argument = null)
* @method $this dbsize()
* @method $this flushall()
* @method $this flushdb()
* @method $this info(string ...$section = null)
* @method $this info($section = null)
* @method $this lastsave()
* @method $this save()
* @method $this slaveof($host, $port)
* @method $this slowlog($subcommand, $argument = null)
* @method $this spublish(string $shardChannel, string $message)
* @method $this time()
* @method $this command($subcommand, $argument = null)
* @method $this command()
* @method $this geoadd($key, $longitude, $latitude, $member)
* @method $this geohash($key, array $members)
* @method $this geopos($key, array $members)
@@ -360,13 +350,12 @@ use Predis\Command\Container\XGROUP;
* @method $this geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
*
* Container commands
* @property CLIENT $client
* @property FUNCTIONS $function
* @property FTCONFIG $ftconfig
* @property FTCURSOR $ftcursor
* @property JSONDEBUG $jsondebug
* @property ACL $acl
* @property XGROUP $xgroup
* @property CLUSTER $cluster
* @property FunctionContainer $function
* @property FTCONFIG $ftconfig
* @property FTCURSOR $ftcursor
* @property JSONDEBUG $jsondebug
* @property ACL $acl
*/
interface ClientContextInterface
{
+32 -45
View File
@@ -38,15 +38,13 @@ use Predis\Command\Argument\TimeSeries\MGetArguments;
use Predis\Command\Argument\TimeSeries\MRangeArguments;
use Predis\Command\Argument\TimeSeries\RangeArguments;
use Predis\Command\CommandInterface;
use Predis\Command\Container\ACL;
use Predis\Command\Container\CLIENT;
use Predis\Command\Container\FUNCTIONS;
use Predis\Command\Container\Json\JSONDEBUG;
use Predis\Command\Container\Search\FTCONFIG;
use Predis\Command\Container\Search\FTCURSOR;
use Predis\Command\Container\XGROUP;
use Predis\Command\Container\XINFO;
use Predis\Command\FactoryInterface;
use Predis\Command\Redis\Container\ACL;
use Predis\Command\Redis\Container\CLUSTER;
use Predis\Command\Redis\Container\FunctionContainer;
use Predis\Command\Redis\Container\Json\JSONDEBUG;
use Predis\Command\Redis\Container\Search\FTCONFIG;
use Predis\Command\Redis\Container\Search\FTCURSOR;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Response\Status;
@@ -70,8 +68,8 @@ use Predis\Response\Status;
* @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, string $option = null)
* @method int pexpireat(string $key, int $timestamp, string $option = null)
* @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)
@@ -82,8 +80,8 @@ use Predis\Response\Status;
* @method int ttl(string $key)
* @method mixed type(string $key)
* @method int append(string $key, $value)
* @method mixed bfadd(string $key, $item)
* @method mixed bfexists(string $key, $item)
* @method int bfadd(string $key, $item)
* @method int bfexists(string $key, $item)
* @method array bfinfo(string $key, string $modifier = '')
* @method array bfinsert(string $key, int $capacity = -1, float $error = -1, int $expansion = -1, bool $noCreate = false, bool $nonScaling = false, string ...$item)
* @method Status bfloadchunk(string $key, int $iterator, $data)
@@ -94,17 +92,16 @@ use Predis\Response\Status;
* @method int bitcount(string $key, $start = null, $end = null, string $index = 'byte')
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
* @method array|null bitfield_ro(string $key, ?array $encodingOffsetMap = null)
* @method int bitpos(string $key, $bit, $start = null, $end = null, string $index = 'byte')
* @method array blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1)
* @method array bzpopmax(array $keys, int $timeout)
* @method array bzpopmin(array $keys, int $timeout)
* @method array bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
* @method mixed cfadd(string $key, $item)
* @method mixed cfaddnx(string $key, $item)
* @method int cfadd(string $key, $item)
* @method int cfaddnx(string $key, $item)
* @method int cfcount(string $key, $item)
* @method mixed cfdel(string $key, $item)
* @method mixed cfexists(string $key, $item)
* @method int cfdel(string $key, $item)
* @method int cfexists(string $key, $item)
* @method Status cfloadchunk(string $key, int $iterator, $data)
* @method int cfmexists(string $key, ...$item)
* @method array cfinfo(string $key)
@@ -123,7 +120,6 @@ use Predis\Response\Status;
* @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1)
* @method mixed fcall(string $function, array $keys, ...$args)
* @method mixed fcall_ro(string $function, array $keys, ...$args)
* @method array ft_list()
* @method array ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
* @method Status ftaliasadd(string $alias, string $index)
* @method Status ftaliasdel(string $alias)
@@ -175,9 +171,7 @@ use Predis\Response\Status;
* @method array|null hpexpireat(string $key, int $unixTimeMilliseconds, array $fields, string $flag = null)
* @method array|null hpexpiretime(string $key, array $fields)
* @method string|null hget(string $key, string $field)
* @method array|null hgetex(string $key, array $fields, string $modifier = HGETEX::NULL, int|bool $modifierValue = false)
* @method array hgetall(string $key)
* @method array hgetdel(string $key, array $fields)
* @method int hincrby(string $key, string $field, int $increment)
* @method string hincrbyfloat(string $key, string $field, int|float $increment)
* @method array hkeys(string $key)
@@ -187,7 +181,6 @@ use Predis\Response\Status;
* @method array hrandfield(string $key, int $count = 1, bool $withValues = false)
* @method array hscan(string $key, $cursor, ?array $options = null)
* @method int hset(string $key, string $field, string $value)
* @method int hsetex(string $key, array $fieldValueMap, string $setModifier = HSETEX::SET_NULL, string $ttlModifier = HSETEX::TTL_NULL, int|bool $ttlModifierValue = false)
* @method int hsetnx(string $key, string $field, string $value)
* @method array|null httl(string $key, array $fields)
* @method array|null hpttl(string $key, array $fields)
@@ -202,8 +195,8 @@ use Predis\Response\Status;
* @method array jsonarrtrim(string $key, string $path, int $start, int $stop)
* @method int jsondel(string $key, string $path = '$')
* @method int jsonforget(string $key, string $path = '$')
* @method mixed jsonget(string $key, string $indent = '', string $newline = '', string $space = '', string ...$paths)
* @method mixed jsonnumincrby(string $key, string $path, int $value)
* @method string jsonget(string $key, string $indent = '', string $newline = '', string $space = '', string ...$paths)
* @method string jsonnumincrby(string $key, string $path, int $value)
* @method Status jsonmerge(string $key, string $path, string $value)
* @method array jsonmget(array $keys, string $path)
* @method Status jsonmset(string ...$keyPathValue)
@@ -250,12 +243,9 @@ use Predis\Response\Status;
* @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 array ssubscribe(string ...$shardChannels)
* @method array subscribe(string ...$channels)
* @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 array sunsubscribe(?string ...$shardChannels = null)
* @method int touch(string[]|string $keyOrKeys, string ...$keys = null)
* @method Status tdigestadd(string $key, float ...$value)
* @method array tdigestbyrank(string $key, int ...$rank)
@@ -263,10 +253,10 @@ use Predis\Response\Status;
* @method array tdigestcdf(string $key, int ...$value)
* @method Status tdigestcreate(string $key, int $compression = 0)
* @method array tdigestinfo(string $key)
* @method mixed tdigestmax(string $key)
* @method string tdigestmax(string $key)
* @method Status tdigestmerge(string $destinationKey, array $sourceKeys, int $compression = 0, bool $override = false)
* @method string[] tdigestquantile(string $key, float ...$quantile)
* @method mixed tdigestmin(string $key)
* @method string tdigestmin(string $key)
* @method array tdigestrank(string $key, float ...$value)
* @method Status tdigestreset(string $key)
* @method array tdigestrevrank(string $key, float ...$value)
@@ -294,15 +284,13 @@ use Predis\Response\Status;
* @method array tsqueryindex(string ...$filterExpression)
* @method array tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method array tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method string xadd(string $key, array $dictionary, string $id = '*', array $options = null)
* @method array xautoclaim(string $key, string $group, string $consumer, int $minIdleTime, string $start, ?int $count = null, bool $justId = false)
* @method string xadd(string $key, array $dictionary, string $id = '*', ?array $options = null)
* @method int xdel(string $key, string ...$id)
* @method int xlen(string $key)
* @method array xrevrange(string $key, string $end, string $start, ?int $count = null)
* @method array xrange(string $key, string $start, string $end, ?int $count = null)
* @method string xtrim(string $key, array|string $strategy, string $threshold, ?array $options = null)
* @method array|null xread(int $count = null, int $block = null, array $streams = null, string ...$id)
* @method array xreadgroup(string $group, string $consumer, ?int $count = null, ?int $blockMs = null, bool $noAck = false, string ...$keyOrId)
* @method string xtrim(string $key, array|string $strategy, string $threshold, array $options = null)
* @method int zadd(string $key, array $membersAndScoresDictionary)
* @method int zcard(string $key)
* @method string zcount(string $key, int|string $min, int|string $max)
@@ -345,7 +333,6 @@ use Predis\Response\Status;
* @method array|null exec()
* @method mixed multi()
* @method mixed unwatch()
* @method array unsubscribe(string ...$channels)
* @method array waitaof(int $numLocal, int $numReplicas, int $timeout)
* @method mixed watch(string[]|string $keyOrKeys)
* @method mixed eval(string $script, int $numkeys, string ...$keyOrArg = null)
@@ -360,18 +347,18 @@ use Predis\Response\Status;
* @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(string ...$section = null)
* @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 int spublish(string $shardChannel, string $message)
* @method array time()
* @method array command($subcommand, $argument = null)
* @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)
@@ -382,14 +369,14 @@ use Predis\Response\Status;
* @method int geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
*
* Container commands
* @property CLIENT $client
* @property FUNCTIONS $function
* @property FTCONFIG $ftconfig
* @property FTCURSOR $ftcursor
* @property JSONDEBUG $jsondebug
* @property ACL $acl
* @property XGROUP $xgroup
* @property XINFO $xinfo
* @property CLUSTER $cluster
* @property FunctionContainer $function
* @property FTCONFIG $ftconfig
* @property FTCURSOR $ftcursor
* @property JSONDEBUG $jsondebug
* @property ACL $acl
*
* @no-named-arguments
*/
interface ClientInterface
{
+8 -25
View File
@@ -177,11 +177,6 @@ abstract class ClusterStrategy implements StrategyInterface
'GEORADIUS' => [$this, 'getKeyFromGeoradiusCommands'],
'GEORADIUSBYMEMBER' => [$this, 'getKeyFromGeoradiusCommands'],
/* sharded pubsub */
'SSUBSCRIBE' => $getKeyFromAllArguments,
'SUNSUBSCRIBE' => [$this, 'getKeyFromSUnsubscribeCommand'],
'SPUBLISH' => $getKeyFromFirstArgument,
/* cluster */
'CLUSTER' => [$this, 'getFakeKey'],
];
@@ -412,24 +407,6 @@ abstract class ClusterStrategy implements StrategyInterface
return $arguments[0];
}
/**
* Extracts key from SUNSUBSCRIBE command if it's given.
*
* @param CommandInterface $command
* @return string
*/
protected function getKeyFromSUnsubscribeCommand(CommandInterface $command): ?string
{
$arguments = $command->getArguments();
// SUNSUBSCRIBE command could be called without arguments, so it doesn't matter on each node it will be called.
if (empty($arguments)) {
return 'fake';
}
return $this->getKeyFromAllArguments($command);
}
/**
* Extracts the key from EVAL and EVALSHA commands.
*
@@ -470,9 +447,13 @@ abstract class ClusterStrategy implements StrategyInterface
}
/**
* {@inheritdoc}
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys Array of keys.
*
* @return bool
*/
public function checkSameSlotForKeys(array $keys): bool
protected function checkSameSlotForKeys(array $keys)
{
if (!$count = count($keys)) {
return false;
@@ -486,6 +467,8 @@ abstract class ClusterStrategy implements StrategyInterface
if ($currentSlot !== $nextSlot) {
return false;
}
$currentSlot = $nextSlot;
}
return true;
+42
View File
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster\Hash;
use Predis\NotSupportedException;
/**
* Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
*
* @deprecated 2.1.2
*/
class PhpiredisCRC16 implements HashGeneratorInterface
{
public function __construct()
{
if (!function_exists('phpiredis_utils_crc16')) {
// @codeCoverageIgnoreStart
throw new NotSupportedException(
'This hash generator requires a compatible version of ext-phpiredis'
);
// @codeCoverageIgnoreEnd
}
}
/**
* {@inheritdoc}
*/
public function hash($value)
{
return phpiredis_utils_crc16($value);
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
/**
* Represents the gap between slot ranges.
*/
class NullSlotRange extends SlotRange
{
public function __construct(int $start, int $end)
{
parent::__construct($start, $end, '');
}
/**
* {@inheritDoc}
*/
public function toArray(): array
{
return [];
}
/**
* {@inheritDoc}
*/
public function count(): int
{
return 0;
}
}
+3 -1
View File
@@ -46,7 +46,7 @@ class PredisStrategy extends ClusterStrategy
/**
* {@inheritdoc}
*/
public function checkSameSlotForKeys(array $keys): bool
protected function checkSameSlotForKeys(array $keys)
{
if (!$count = count($keys)) {
return false;
@@ -60,6 +60,8 @@ class PredisStrategy extends ClusterStrategy
if ($currentKey !== $nextKey) {
return false;
}
$currentKey = $nextKey;
}
return true;
-209
View File
@@ -1,209 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use OutOfBoundsException;
use Predis\Connection\NodeConnectionInterface;
use ReturnTypeWillChange;
use Traversable;
/**
* Slot map for redis-cluster.
*/
class SimpleSlotMap implements ArrayAccess, IteratorAggregate, Countable
{
private $slots = [];
/**
* Checks if the given slot is valid.
*
* @param int $slot Slot index.
*
* @return bool
*/
public static function isValid($slot)
{
return $slot >= 0x0000 && $slot <= 0x3FFF;
}
/**
* Checks if the given slot range is valid.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return bool
*/
public static function isValidRange($first, $last)
{
return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last;
}
/**
* Resets the slot map.
*/
public function reset()
{
$this->slots = [];
}
/**
* Checks if the slot map is empty.
*
* @return bool
*/
public function isEmpty()
{
return empty($this->slots);
}
/**
* Returns the current slot map as a dictionary of $slot => $node.
*
* The order of the slots in the dictionary is not guaranteed.
*
* @return array
*/
public function toArray()
{
return $this->slots;
}
/**
* Returns the list of unique nodes in the slot map.
*
* @return array
*/
public function getNodes()
{
return array_keys(array_flip($this->slots));
}
/**
* Assigns the specified slot range to a node.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
* @param NodeConnectionInterface|string $connection ID or connection instance.
*
* @throws OutOfBoundsException
*/
public function setSlots($first, $last, $connection)
{
if (!static::isValidRange($first, $last)) {
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
}
$this->slots += array_fill($first, $last - $first + 1, (string) $connection);
}
/**
* Returns the specified slot range.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return array
*/
public function getSlots($first, $last)
{
if (!static::isValidRange($first, $last)) {
throw new OutOfBoundsException("Invalid slot range $first-$last");
}
return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null));
}
/**
* Checks if the specified slot is assigned.
*
* @param int $slot Slot index.
*
* @return bool
*/
#[ReturnTypeWillChange]
public function offsetExists($slot)
{
return isset($this->slots[$slot]);
}
/**
* Returns the node assigned to the specified slot.
*
* @param int $slot Slot index.
*
* @return string|null
*/
#[ReturnTypeWillChange]
public function offsetGet($slot)
{
return $this->slots[$slot] ?? null;
}
/**
* Assigns the specified slot to a node.
*
* @param int $slot Slot index.
* @param NodeConnectionInterface|string $connection ID or connection instance.
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetSet($slot, $connection)
{
if (!static::isValid($slot)) {
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
}
$this->slots[(int) $slot] = (string) $connection;
}
/**
* Returns the node assigned to the specified slot.
*
* @param int $slot Slot index.
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetUnset($slot)
{
unset($this->slots[$slot]);
}
/**
* Returns the current number of assigned slots.
*
* @return int
*/
#[ReturnTypeWillChange]
public function count()
{
return count($this->slots);
}
/**
* Returns an iterator over the slot map.
*
* @return Traversable<int, string>
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->slots);
}
}
+17 -225
View File
@@ -22,16 +22,11 @@ use ReturnTypeWillChange;
use Traversable;
/**
* Compact slot map for redis-cluster.
* Slot map for redis-cluster.
*/
class SlotMap implements ArrayAccess, IteratorAggregate, Countable
{
/**
* Slot ranges list.
*
* @var SlotRange[]
*/
private $slotRanges = [];
private $slots = [];
/**
* Checks if the given slot is valid.
@@ -42,7 +37,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public static function isValid($slot)
{
return $slot >= 0 && $slot <= SlotRange::MAX_SLOTS;
return $slot >= 0x0000 && $slot <= 0x3FFF;
}
/**
@@ -55,7 +50,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public static function isValidRange($first, $last)
{
return SlotRange::isValidRange($first, $last);
return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last;
}
/**
@@ -63,7 +58,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function reset()
{
$this->slotRanges = [];
$this->slots = [];
}
/**
@@ -73,7 +68,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function isEmpty()
{
return empty($this->slotRanges);
return empty($this->slots);
}
/**
@@ -85,13 +80,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function toArray()
{
return array_reduce(
$this->slotRanges,
function ($carry, $slotRange) {
return $carry + $slotRange->toArray();
},
[]
);
return $this->slots;
}
/**
@@ -101,22 +90,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function getNodes()
{
return array_unique(array_map(
function ($slotRange) {
return $slotRange->getConnection();
},
$this->slotRanges
));
}
/**
* Returns the list of slot ranges.
*
* @return SlotRange[]
*/
public function getSlotRanges()
{
return $this->slotRanges;
return array_keys(array_flip($this->slots));
}
/**
@@ -134,31 +108,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
}
$targetSlotRange = new SlotRange($first, $last, (string) $connection);
// Get gaps of slot ranges list.
$gaps = $this->getGaps($this->slotRanges);
$results = $this->slotRanges;
foreach ($gaps as $gap) {
if (!$gap->hasIntersectionWith($targetSlotRange)) {
continue;
}
// Get intersection of the gap and target slot range.
$results[] = new SlotRange(
max($gap->getStart(), $targetSlotRange->getStart()),
min($gap->getEnd(), $targetSlotRange->getEnd()),
$targetSlotRange->getConnection()
);
}
$this->sortSlotRanges($results);
$results = $this->compactSlotRanges($results);
$this->slotRanges = $results;
$this->slots += array_fill($first, $last - $first + 1, (string) $connection);
}
/**
@@ -167,7 +117,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return array<int, string>
* @return array
*/
public function getSlots($first, $last)
{
@@ -175,28 +125,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot range $first-$last");
}
$placeHolder = new NullSlotRange($first, $last);
$intersections = [];
foreach ($this->slotRanges as $slotRange) {
if (!$placeHolder->hasIntersectionWith($slotRange)) {
continue;
}
$intersections[] = new SlotRange(
max($placeHolder->getStart(), $slotRange->getStart()),
min($placeHolder->getEnd(), $slotRange->getEnd()),
$slotRange->getConnection()
);
}
return array_reduce(
$intersections,
function ($carry, $slotRange) {
return $carry + $slotRange->toArray();
},
[]
);
return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null));
}
/**
@@ -209,7 +138,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetExists($slot)
{
return $this->findRangeBySlot($slot) !== false;
return isset($this->slots[$slot]);
}
/**
@@ -222,9 +151,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetGet($slot)
{
$found = $this->findRangeBySlot($slot);
return $found ? $found->getConnection() : null;
return $this->slots[$slot] ?? null;
}
/**
@@ -242,8 +169,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
}
$this->offsetUnset($slot);
$this->setSlots($slot, $slot, $connection);
$this->slots[(int) $slot] = (string) $connection;
}
/**
@@ -256,26 +182,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetUnset($slot)
{
if (!static::isValid($slot)) {
throw new OutOfBoundsException("Invalid slot $slot");
}
$results = [];
foreach ($this->slotRanges as $slotRange) {
if (!$slotRange->hasSlot($slot)) {
$results[] = $slotRange;
}
if (static::isValidRange($slotRange->getStart(), $slot - 1)) {
$results[] = new SlotRange($slotRange->getStart(), $slot - 1, $slotRange->getConnection());
}
if (static::isValidRange($slot + 1, $slotRange->getEnd())) {
$results[] = new SlotRange($slot + 1, $slotRange->getEnd(), $slotRange->getConnection());
}
}
$this->slotRanges = $results;
unset($this->slots[$slot]);
}
/**
@@ -286,12 +193,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function count()
{
return array_sum(array_map(
function ($slotRange) {
return $slotRange->count();
},
$this->slotRanges
));
return count($this->slots);
}
/**
@@ -302,116 +204,6 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->toArray());
}
/**
* Find the slot range which contains the specific slot index.
*
* @param int $slot Slot index.
*
* @return SlotRange|false The slot range object or false if not found.
*/
protected function findRangeBySlot(int $slot)
{
foreach ($this->slotRanges as $slotRange) {
if ($slotRange->hasSlot($slot)) {
return $slotRange;
}
}
return false;
}
/**
* Get gaps between sorted slot ranges with NullSlotRange object.
*
* @param SlotRange[] $slotRanges
*
* @return SlotRange[]
*/
protected function getGaps(array $slotRanges)
{
if (empty($slotRanges)) {
return [
new NullSlotRange(0, SlotRange::MAX_SLOTS),
];
}
$gaps = [];
$count = count($slotRanges);
$i = 0;
foreach ($slotRanges as $key => $slotRange) {
$start = $slotRange->getStart();
$end = $slotRange->getEnd();
if (static::isValidRange($i, $start - 1)) {
$gaps[] = new NullSlotRange($i, $start - 1);
}
$i = $end + 1;
if ($key === $count - 1) {
if (static::isValidRange($i, SlotRange::MAX_SLOTS)) {
$gaps[] = new NullSlotRange($i, SlotRange::MAX_SLOTS);
}
}
}
return $gaps;
}
/**
* Sort slot ranges by start index.
*
* @param SlotRange[] $slotRanges
*
* @return void
*/
protected function sortSlotRanges(array &$slotRanges)
{
usort(
$slotRanges,
function (SlotRange $a, SlotRange $b) {
if ($a->getStart() == $b->getStart()) {
return 0;
}
return $a->getStart() < $b->getStart() ? -1 : 1;
}
);
}
/**
* Compact adjacent slot ranges with the same connection.
*
* @param SlotRange[] $slotRanges
*
* @return SlotRange[]
*/
protected function compactSlotRanges(array $slotRanges)
{
if (empty($slotRanges)) {
return [];
}
$compacted = [];
$count = count($slotRanges);
$i = 0;
$carry = $slotRanges[0];
while ($i < $count) {
$next = $slotRanges[$i + 1] ?? null;
if (
!is_null($next)
&& ($carry->getEnd() + 1) === $next->getStart()
&& $carry->getConnection() === $next->getConnection()
) {
$carry = new SlotRange($carry->getStart(), $next->getEnd(), $carry->getConnection());
} else {
$compacted[] = $carry;
$carry = $next;
}
$i++;
}
return array_values($compacted);
return new ArrayIterator($this->slots);
}
}
-145
View File
@@ -1,145 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Countable;
use OutOfBoundsException;
/**
* Represents a range of slots in a Redis cluster.
*/
class SlotRange implements Countable
{
/**
* Maximum number of slots in a Redis cluster is 16384.
*/
public const MAX_SLOTS = 0x3FFF;
/**
* Starting slot of the range.
*
* @var int
*/
protected $start;
/**
* Ending slot of the range.
*
* @var int
*/
protected $end;
/**
* Connection to the server hosting this slot range.
*
* @var string
*/
protected $connection;
public function __construct(int $start, int $end, string $connection)
{
if (!static::isValidRange($start, $end)) {
throw new OutOfBoundsException("Invalid slot range $start-$end for `$connection`");
}
$this->start = $start;
$this->end = $end;
$this->connection = $connection;
}
/**
* Checks if a slot range is valid.
*
* @param int $first
* @param int $last
*
* @return bool
*/
public static function isValidRange($first, $last)
{
return $first >= 0x0000 && $first <= self::MAX_SLOTS && $last >= 0x0000 && $last <= self::MAX_SLOTS && $first <= $last;
}
/**
* Returns the start slot index of this range.
*
* @return int
*/
public function getStart()
{
return $this->start;
}
/**
* Returns the end slot index of this range.
*
* @return int
*/
public function getEnd()
{
return $this->end;
}
/**
* Returns the connection to the server hosting this slot range.
*
* @return string
*/
public function getConnection()
{
return $this->connection;
}
/**
* Checks if the specific slot is contained in this range.
*
* @param int $slot
*
* @return bool
*/
public function hasSlot(int $slot)
{
return $this->start <= $slot && $this->end >= $slot;
}
/**
* Returns an array of connection strings for each slot in this range.
*
* @return string[]
*/
public function toArray(): array
{
return array_fill($this->start, $this->end - $this->start + 1, $this->connection);
}
/**
* Returns the number of slots in this range.
*
* @return int
*/
public function count(): int
{
return $this->end - $this->start + 1;
}
/**
* Checks if this range has an intersection with the given slot range.
*
* @param SlotRange $slotRange
*
* @return bool
*/
public function hasIntersectionWith(SlotRange $slotRange): bool
{
return $this->start <= $slotRange->getEnd() && $this->end >= $slotRange->getStart();
}
}
-8
View File
@@ -49,12 +49,4 @@ interface StrategyInterface
* @return DistributorInterface
*/
public function getDistributor();
/**
* Checks if the specified array of keys will generate the same hash.
*
* @param array $keys
* @return bool
*/
public function checkSameSlotForKeys(array $keys): bool;
}
@@ -1,49 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Argument\Stream;
use Predis\Command\Argument\ArrayableArgument;
class XInfoStreamOptions implements ArrayableArgument
{
/**
* @var array
*/
protected $options = [];
/**
* Modifier provides a more verbose reply.
* The COUNT option can be used to limit the number of stream and PEL entries that are returned.
*
* @param int|null $count
* @return self
*/
public function full(?int $count = null): self
{
$this->options[] = 'FULL';
if (null !== $count) {
array_push($this->options, 'COUNT', $count);
}
return $this;
}
/**
* {@inheritDoc}
*/
public function toArray(): array
{
return $this->options;
}
}
-74
View File
@@ -12,9 +12,6 @@
namespace Predis\Command;
use Predis\ClientConfiguration;
use UnexpectedValueException;
/**
* Base class for Redis commands.
*/
@@ -83,14 +80,6 @@ abstract class Command implements CommandInterface
return $data;
}
/**
* {@inheritdoc}
*/
public function parseResp3Response($data)
{
return $data;
}
/**
* Normalizes the arguments array passed to a Redis command.
*
@@ -134,67 +123,4 @@ abstract class Command implements CommandInterface
return $argument !== false && $argument !== null;
});
}
/**
* {@inheritDoc}
*/
public function serializeCommand(): string
{
$commandID = $this->getId();
$arguments = $this->getArguments();
$cmdlen = strlen($commandID);
$reqlen = count($arguments) + 1;
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
foreach ($arguments as $argument) {
$arglen = strlen(strval($argument));
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
}
return $buffer;
}
/**
* {@inheritDoc}
*/
public static function deserializeCommand(string $serializedCommand): CommandInterface
{
if ($serializedCommand[0] !== '*') {
throw new UnexpectedValueException('Invalid serializing format');
}
$commandArray = explode("\r\n", $serializedCommand);
$commandId = $commandArray[2];
$classPath = __NAMESPACE__ . '\Redis\\';
// Check if given command is a module command.
if (count($commandIdArray = explode('.', $commandId)) > 1) {
// Fetch module configuration to resolve namespace.
$moduleConfiguration = array_filter(
ClientConfiguration::getModules(),
static function ($module) use ($commandIdArray) {
return $module['commandPrefix'] === $commandIdArray[0];
}
);
$commandClass = strtoupper($commandIdArray[0] . $commandIdArray[1]);
$classPath .= array_shift($moduleConfiguration)['name'] . '\\' . $commandClass;
} else {
$classPath .= $commandIdArray[0];
}
$command = new $classPath();
$arguments = [];
for ($i = 4, $iMax = count($commandArray); $i < $iMax; $i++) {
$arguments[] = $commandArray[$i];
++$i;
}
$command->setArguments($arguments);
return $command;
}
}
-23
View File
@@ -77,27 +77,4 @@ interface CommandInterface
* @return mixed
*/
public function parseResponse($data);
/**
* Parses RESP3 protocol response and returns a PHP object.
*
* @param mixed $data
* @return mixed
*/
public function parseResp3Response($data);
/**
* Returns RESP-formatted representation of command.
*
* @return string
*/
public function serializeCommand(): string;
/**
* Creates command object from given serialized representation.
*
* @param string $serializedCommand
* @return static
*/
public static function deserializeCommand(string $serializedCommand): CommandInterface;
}
-32
View File
@@ -1,32 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
use Predis\Response\Status;
/**
* @method string getName()
* @method Status kill(...$arguments)
* @method string list(string $type = null, int ...$clientId)
* @method Status noEvict(bool $enable = null)
* @method Status noTouch(bool $enable = null)
* @method Status setInfo(string $modifier = null, string $value = null)
* @method Status setName(string $connectionName)
*/
class CLIENT extends AbstractContainer
{
public function getContainerCommandId(): string
{
return 'CLIENT';
}
}
-30
View File
@@ -1,30 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
use Predis\Response\Status;
/**
* @method Status create(string $key, string $group, string $id, bool $mkStream = false, ?string $entriesRead = null)
* @method int createConsumer(string $key, string $group, string $consumer)
* @method int delConsumer(string $key, string $group, string $consumer)
* @method int destroy(string $key, string $group)
* @method Status setId(string $key, string $group, string $id, ?string $entriesRead = null)
*/
class XGROUP extends AbstractContainer
{
public function getContainerCommandId(): string
{
return 'xgroup';
}
}
-28
View File
@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
use Predis\Command\Argument\Stream\XInfoStreamOptions;
/**
* @method array consumers(string $key, string $group)
* @method array groups(string $key)
* @method array stream(string $key, XInfoStreamOptions $options = null)
*/
class XINFO extends AbstractContainer
{
public function getContainerCommandId(): string
{
return 'XINFO';
}
}
-111
View File
@@ -1,111 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command;
abstract class PrefixableCommand extends Command implements PrefixableCommandInterface
{
/**
* {@inheritDoc}
*/
abstract public function getId();
/**
* {@inheritDoc}
*/
abstract public function prefixKeys($prefix);
/**
* Applies prefix for all arguments.
*
* @param string $prefix
* @return void
*/
public function applyPrefixForAllArguments(string $prefix): void
{
$this->setArguments(
array_map(static function ($key) use ($prefix) {
return $prefix . $key;
}, $this->getArguments())
);
}
/**
* Applies prefix for first argument.
*
* @param string $prefix
* @return void
*/
public function applyPrefixForFirstArgument(string $prefix): void
{
$arguments = $this->getArguments();
$arguments[0] = $prefix . $arguments[0];
$this->setArguments($arguments);
}
/**
* Applies prefix for interleaved arguments.
*
* @param string $prefix
* @return void
*/
public function applyPrefixForInterleavedArgument(string $prefix): void
{
if ($arguments = $this->getArguments()) {
$length = count($arguments);
for ($i = 0; $i < $length; $i += 2) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$this->setRawArguments($arguments);
}
}
/**
* Applies prefix for all keys except last one.
*
* @param string $prefix
* @return void
*/
public function applyPrefixSkippingLastArgument(string $prefix): void
{
if ($arguments = $this->getArguments()) {
$length = count($arguments);
for ($i = 0; $i < $length - 1; ++$i) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$this->setRawArguments($arguments);
}
}
/**
* Applies prefix for all keys except first one.
*
* @param string $prefix
* @return void
*/
public function applyPrefixSkippingFirstArgument(string $prefix): void
{
if ($arguments = $this->getArguments()) {
$length = count($arguments);
for ($i = 1; $i < $length; ++$i) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$this->setRawArguments($arguments);
}
}
}
@@ -31,6 +31,284 @@ class KeyPrefixProcessor implements ProcessorInterface
public function __construct($prefix)
{
$this->prefix = $prefix;
$prefixFirst = static::class . '::first';
$prefixFirstTwo = static::class . '::firstTwo';
$prefixAll = static::class . '::all';
$prefixInterleaved = static::class . '::interleaved';
$prefixSkipFirst = static::class . '::skipFirst';
$prefixSkipLast = static::class . '::skipLast';
$prefixSort = static::class . '::sort';
$prefixEvalKeys = static::class . '::evalKeys';
$prefixZsetStore = static::class . '::zsetStore';
$prefixMigrate = static::class . '::migrate';
$prefixGeoradius = static::class . '::georadius';
$this->commands = [
/* ---------------- Redis 1.2 ---------------- */
'EXISTS' => $prefixAll,
'DEL' => $prefixAll,
'TYPE' => $prefixFirst,
'KEYS' => $prefixFirst,
'RENAME' => $prefixAll,
'RENAMENX' => $prefixAll,
'EXPIRE' => $prefixFirst,
'EXPIREAT' => $prefixFirst,
'TTL' => $prefixFirst,
'MOVE' => $prefixFirst,
'SORT' => $prefixSort,
'DUMP' => $prefixFirst,
'RESTORE' => $prefixFirst,
'SET' => $prefixFirst,
'SETNX' => $prefixFirst,
'MSET' => $prefixInterleaved,
'MSETNX' => $prefixInterleaved,
'GET' => $prefixFirst,
'MGET' => $prefixAll,
'GETSET' => $prefixFirst,
'INCR' => $prefixFirst,
'INCRBY' => $prefixFirst,
'DECR' => $prefixFirst,
'DECRBY' => $prefixFirst,
'RPUSH' => $prefixFirst,
'LPUSH' => $prefixFirst,
'LLEN' => $prefixFirst,
'LRANGE' => $prefixFirst,
'LTRIM' => $prefixFirst,
'LINDEX' => $prefixFirst,
'LSET' => $prefixFirst,
'LREM' => $prefixFirst,
'LPOP' => $prefixFirst,
'RPOP' => $prefixFirst,
'RPOPLPUSH' => $prefixAll,
'SADD' => $prefixFirst,
'SREM' => $prefixFirst,
'SPOP' => $prefixFirst,
'SMOVE' => $prefixSkipLast,
'SCARD' => $prefixFirst,
'SISMEMBER' => $prefixFirst,
'SINTER' => $prefixAll,
'SINTERSTORE' => $prefixAll,
'SUNION' => $prefixAll,
'SUNIONSTORE' => $prefixAll,
'SDIFF' => $prefixAll,
'SDIFFSTORE' => $prefixAll,
'SMEMBERS' => $prefixFirst,
'SMISMEMBER' => $prefixFirst,
'SRANDMEMBER' => $prefixFirst,
'ZADD' => $prefixFirst,
'ZINCRBY' => $prefixFirst,
'ZREM' => $prefixFirst,
'ZRANGE' => $prefixFirst,
'ZREVRANGE' => $prefixFirst,
'ZRANGEBYSCORE' => $prefixFirst,
'ZCARD' => $prefixFirst,
'ZSCORE' => $prefixFirst,
'ZREMRANGEBYSCORE' => $prefixFirst,
/* ---------------- Redis 2.0 ---------------- */
'SETEX' => $prefixFirst,
'APPEND' => $prefixFirst,
'SUBSTR' => $prefixFirst,
'BLPOP' => $prefixSkipLast,
'BRPOP' => $prefixSkipLast,
'ZUNIONSTORE' => $prefixZsetStore,
'ZINTERSTORE' => $prefixZsetStore,
'ZCOUNT' => $prefixFirst,
'ZRANK' => $prefixFirst,
'ZREVRANK' => $prefixFirst,
'ZREMRANGEBYRANK' => $prefixFirst,
'HSET' => $prefixFirst,
'HSETNX' => $prefixFirst,
'HMSET' => $prefixFirst,
'HINCRBY' => $prefixFirst,
'HGET' => $prefixFirst,
'HMGET' => $prefixFirst,
'HDEL' => $prefixFirst,
'HEXISTS' => $prefixFirst,
'HLEN' => $prefixFirst,
'HKEYS' => $prefixFirst,
'HVALS' => $prefixFirst,
'HGETALL' => $prefixFirst,
'SUBSCRIBE' => $prefixAll,
'UNSUBSCRIBE' => $prefixAll,
'PSUBSCRIBE' => $prefixAll,
'PUNSUBSCRIBE' => $prefixAll,
'PUBLISH' => $prefixFirst,
/* ---------------- Redis 2.2 ---------------- */
'PERSIST' => $prefixFirst,
'STRLEN' => $prefixFirst,
'SETRANGE' => $prefixFirst,
'GETRANGE' => $prefixFirst,
'SETBIT' => $prefixFirst,
'GETBIT' => $prefixFirst,
'RPUSHX' => $prefixFirst,
'LPUSHX' => $prefixFirst,
'LINSERT' => $prefixFirst,
'BRPOPLPUSH' => $prefixSkipLast,
'ZREVRANGEBYSCORE' => $prefixFirst,
'WATCH' => $prefixAll,
/* ---------------- Redis 2.6 ---------------- */
'PTTL' => $prefixFirst,
'PEXPIRE' => $prefixFirst,
'PEXPIREAT' => $prefixFirst,
'PSETEX' => $prefixFirst,
'INCRBYFLOAT' => $prefixFirst,
'BITOP' => $prefixSkipFirst,
'BITCOUNT' => $prefixFirst,
'HINCRBYFLOAT' => $prefixFirst,
'EVAL' => $prefixEvalKeys,
'EVALSHA' => $prefixEvalKeys,
'MIGRATE' => $prefixMigrate,
/* ---------------- Redis 2.8 ---------------- */
'SSCAN' => $prefixFirst,
'ZSCAN' => $prefixFirst,
'HSCAN' => $prefixFirst,
'PFADD' => $prefixFirst,
'PFCOUNT' => $prefixAll,
'PFMERGE' => $prefixAll,
'ZLEXCOUNT' => $prefixFirst,
'ZRANGEBYLEX' => $prefixFirst,
'ZREMRANGEBYLEX' => $prefixFirst,
'ZREVRANGEBYLEX' => $prefixFirst,
'BITPOS' => $prefixFirst,
/* ---------------- Redis 3.2 ---------------- */
'HSTRLEN' => $prefixFirst,
'BITFIELD' => $prefixFirst,
'GEOADD' => $prefixFirst,
'GEOHASH' => $prefixFirst,
'GEOPOS' => $prefixFirst,
'GEODIST' => $prefixFirst,
'GEORADIUS' => $prefixGeoradius,
'GEORADIUSBYMEMBER' => $prefixGeoradius,
/* ---------------- Redis 5.0 ---------------- */
'XADD' => $prefixFirst,
'XRANGE' => $prefixFirst,
'XREVRANGE' => $prefixFirst,
'XDEL' => $prefixFirst,
'XLEN' => $prefixFirst,
'XACK' => $prefixFirst,
'XTRIM' => $prefixFirst,
'ZPOPMIN' => $prefixFirst,
'ZPOPMAX' => $prefixFirst,
/* ---------------- Redis 6.2 ---------------- */
'GETDEL' => $prefixFirst,
'ZMSCORE' => $prefixFirst,
'LMOVE' => $prefixFirstTwo,
'BLMOVE' => $prefixFirstTwo,
'GEOSEARCH' => $prefixFirst,
/* ---------------- Redis 7.0 ---------------- */
'EXPIRETIME' => $prefixFirst,
/* RedisJSON */
'JSON.ARRAPPEND' => $prefixFirst,
'JSON.ARRINDEX' => $prefixFirst,
'JSON.ARRINSERT' => $prefixFirst,
'JSON.ARRLEN' => $prefixFirst,
'JSON.ARRPOP' => $prefixFirst,
'JSON.ARRTRIM' => $prefixFirst,
'JSON.CLEAR' => $prefixFirst,
'JSON.DEBUG MEMORY' => $prefixFirst,
'JSON.DEL' => $prefixFirst,
'JSON.FORGET' => $prefixFirst,
'JSON.GET' => $prefixFirst,
'JSON.MGET' => $prefixAll,
'JSON.NUMINCRBY' => $prefixFirst,
'JSON.OBJKEYS' => $prefixFirst,
'JSON.OBJLEN' => $prefixFirst,
'JSON.RESP' => $prefixFirst,
'JSON.SET' => $prefixFirst,
'JSON.STRAPPEND' => $prefixFirst,
'JSON.STRLEN' => $prefixFirst,
'JSON.TOGGLE' => $prefixFirst,
'JSON.TYPE' => $prefixFirst,
/* RedisBloom */
'BF.ADD' => $prefixFirst,
'BF.EXISTS' => $prefixFirst,
'BF.INFO' => $prefixFirst,
'BF.INSERT' => $prefixFirst,
'BF.LOADCHUNK' => $prefixFirst,
'BF.MADD' => $prefixFirst,
'BF.MEXISTS' => $prefixFirst,
'BF.RESERVE' => $prefixFirst,
'BF.SCANDUMP' => $prefixFirst,
'CF.ADD' => $prefixFirst,
'CF.ADDNX' => $prefixFirst,
'CF.COUNT' => $prefixFirst,
'CF.DEL' => $prefixFirst,
'CF.EXISTS' => $prefixFirst,
'CF.INFO' => $prefixFirst,
'CF.INSERT' => $prefixFirst,
'CF.INSERTNX' => $prefixFirst,
'CF.LOADCHUNK' => $prefixFirst,
'CF.MEXISTS' => $prefixFirst,
'CF.RESERVE' => $prefixFirst,
'CF.SCANDUMP' => $prefixFirst,
'CMS.INCRBY' => $prefixFirst,
'CMS.INFO' => $prefixFirst,
'CMS.INITBYDIM' => $prefixFirst,
'CMS.INITBYPROB' => $prefixFirst,
'CMS.QUERY' => $prefixFirst,
'TDIGEST.ADD' => $prefixFirst,
'TDIGEST.BYRANK' => $prefixFirst,
'TDIGEST.BYREVRANK' => $prefixFirst,
'TDIGEST.CDF' => $prefixFirst,
'TDIGEST.CREATE' => $prefixFirst,
'TDIGEST.INFO' => $prefixFirst,
'TDIGEST.MAX' => $prefixFirst,
'TDIGEST.MIN' => $prefixFirst,
'TDIGEST.QUANTILE' => $prefixFirst,
'TDIGEST.RANK' => $prefixFirst,
'TDIGEST.RESET' => $prefixFirst,
'TDIGEST.REVRANK' => $prefixFirst,
'TDIGEST.TRIMMED_MEAN' => $prefixFirst,
'TOPK.ADD' => $prefixFirst,
'TOPK.INCRBY' => $prefixFirst,
'TOPK.INFO' => $prefixFirst,
'TOPK.LIST' => $prefixFirst,
'TOPK.QUERY' => $prefixFirst,
'TOPK.RESERVE' => $prefixFirst,
/* RediSearch */
'FT.AGGREGATE' => $prefixFirst,
'FT.ALTER' => $prefixFirst,
'FT.CREATE' => $prefixFirst,
'FT.CURSOR DEL' => $prefixFirst,
'FT.CURSOR READ' => $prefixFirst,
'FT.DROPINDEX' => $prefixFirst,
'FT.EXPLAIN' => $prefixFirst,
'FT.INFO' => $prefixFirst,
'FT.PROFILE' => $prefixFirst,
'FT.SEARCH' => $prefixFirst,
'FT.SPELLCHECK' => $prefixFirst,
'FT.SYNDUMP' => $prefixFirst,
'FT.SYNUPDATE' => $prefixFirst,
'FT.TAGVALS' => $prefixFirst,
/* Redis TimeSeries */
'TS.ADD' => $prefixFirst,
'TS.ALTER' => $prefixFirst,
'TS.CREATE' => $prefixFirst,
'TS.DECRBY' => $prefixFirst,
'TS.DEL' => $prefixFirst,
'TS.GET' => $prefixFirst,
'TS.INCRBY' => $prefixFirst,
'TS.INFO' => $prefixFirst,
'TS.MGET' => $prefixFirst,
'TS.MRANGE' => $prefixFirst,
'TS.MREVRANGE' => $prefixFirst,
'TS.QUERYINDEX' => $prefixFirst,
'TS.RANGE' => $prefixFirst,
'TS.REVRANGE' => $prefixFirst,
];
}
/**
@@ -107,4 +385,225 @@ class KeyPrefixProcessor implements ProcessorInterface
{
return $this->getPrefix();
}
/**
* Applies the specified prefix only the first argument.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function first(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix only to the first two arguments.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function firstTwo(CommandInterface $command, $prefix)
{
$arguments = $command->getArguments();
$length = min(count($arguments), 2);
for ($i = 0; $i < $length; $i++) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
/**
* Applies the specified prefix to all the arguments.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function all(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
foreach ($arguments as &$key) {
$key = "$prefix$key";
}
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix only to even arguments in the list.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function interleaved(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$length = count($arguments);
for ($i = 0; $i < $length; $i += 2) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to all the arguments but the first one.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function skipFirst(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$length = count($arguments);
for ($i = 1; $i < $length; ++$i) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to all the arguments but the last one.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function skipLast(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$length = count($arguments);
for ($i = 0; $i < $length - 1; ++$i) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to the keys of a SORT command.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function sort(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
if (($count = count($arguments)) > 1) {
for ($i = 1; $i < $count; ++$i) {
switch (strtoupper($arguments[$i])) {
case 'BY':
case 'STORE':
$arguments[$i] = "$prefix{$arguments[++$i]}";
break;
case 'GET':
$value = $arguments[++$i];
if ($value !== '#') {
$arguments[$i] = "$prefix$value";
}
break;
case 'LIMIT':
$i += 2;
break;
}
}
}
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to the keys of an EVAL-based command.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function evalKeys(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
for ($i = 2; $i < $arguments[1] + 2; ++$i) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function zsetStore(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
$length = ((int) $arguments[1]) + 2;
for ($i = 2; $i < $length; ++$i) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to the key of a MIGRATE command.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function migrate(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[2] = "$prefix{$arguments[2]}";
$command->setRawArguments($arguments);
}
}
/**
* Applies the specified prefix to the key of a GEORADIUS command.
*
* @param CommandInterface $command Command instance.
* @param string $prefix Prefix string.
*/
public static function georadius(CommandInterface $command, $prefix)
{
if ($arguments = $command->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
$startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
if (($count = count($arguments)) > $startIndex) {
for ($i = $startIndex; $i < $count; ++$i) {
switch (strtoupper($arguments[$i])) {
case 'STORE':
case 'STOREDIST':
$arguments[$i] = "$prefix{$arguments[++$i]}";
break;
}
}
}
$command->setRawArguments($arguments);
}
}
}
-71
View File
@@ -12,9 +12,6 @@
namespace Predis\Command;
use Predis\ClientConfiguration;
use UnexpectedValueException;
/**
* Class representing a generic Redis command.
*
@@ -123,72 +120,4 @@ final class RawCommand implements CommandInterface
{
return $data;
}
/**
* {@inheritdoc}
*/
public function parseResp3Response($data)
{
return $data;
}
/**
* {@inheritDoc}
*/
public function serializeCommand(): string
{
$commandID = $this->getId();
$arguments = $this->getArguments();
$cmdlen = strlen($commandID);
$reqlen = count($arguments) + 1;
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
foreach ($arguments as $argument) {
$arglen = strlen(strval($argument));
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
}
return $buffer;
}
public static function deserializeCommand(string $serializedCommand): CommandInterface
{
if ($serializedCommand[0] !== '*') {
throw new UnexpectedValueException('Invalid serializing format');
}
$commandArray = explode("\r\n", $serializedCommand);
$commandId = $commandArray[2];
$classPath = __NAMESPACE__ . '\Redis\\';
// Check if given command is a module command.
if (count($commandIdArray = explode('.', $commandId)) > 1) {
// Fetch module configuration to resolve namespace.
$moduleConfiguration = array_filter(
ClientConfiguration::getModules(),
static function ($module) use ($commandIdArray) {
return $module['commandPrefix'] === $commandIdArray[0];
}
);
$commandClass = strtoupper($commandIdArray[0] . $commandIdArray[1]);
$classPath .= array_shift($moduleConfiguration)['name'] . '\\' . $commandClass;
} else {
$classPath .= $commandIdArray[0];
}
$command = new $classPath();
$arguments = [];
for ($i = 4, $iMax = count($commandArray); $i < $iMax; $i++) {
$arguments[] = $commandArray[$i];
++$i;
}
$command->setArguments($arguments);
return $command;
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/append
@@ -26,9 +26,4 @@ class APPEND extends RedisCommand
{
return 'APPEND';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
@@ -40,13 +40,4 @@ abstract class BZPOPBase extends RedisCommand
return array_combine([$key], [[$data[0] => $data[1]]]);
}
/**
* @param $data
* @return array|false|mixed|null[]|string|null
*/
public function parseResp3Response($data)
{
return $this->parseResponse($data);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\BitByte;
/**
@@ -31,9 +31,4 @@ class BITCOUNT extends RedisCommand
{
return 'BITCOUNT';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/bitfield
@@ -26,9 +26,4 @@ class BITFIELD extends RedisCommand
{
return 'BITFIELD';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
-44
View File
@@ -1,44 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* 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;
class BITFIELD_RO extends RedisCommand
{
/**
* @return string
*/
public function getId()
{
return 'BITFIELD_RO';
}
/**
* @param array $arguments
* @return void
*/
public function setArguments(array $arguments)
{
$processedArguments = [$arguments[0]];
if (array_key_exists(1, $arguments) && is_array($arguments[1])) {
// Convert encoding => offset, into GET, encoding, offset
array_walk($arguments[1], function ($value, $key) use (&$processedArguments) {
array_push($processedArguments, 'GET', $key, $value);
});
}
parent::setArguments($processedArguments);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/bitop
@@ -40,9 +40,4 @@ class BITOP extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixSkippingFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\BitByte;
/**
@@ -31,9 +31,4 @@ class BITPOS extends RedisCommand
{
return 'BITPOS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/blpop
@@ -39,9 +39,4 @@ class BLPOP extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixSkippingLastArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/brpop
@@ -39,9 +39,4 @@ class BRPOP extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixSkippingLastArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/brpoplpush
@@ -26,9 +26,4 @@ class BRPOPLPUSH extends RedisCommand
{
return 'BRPOPLPUSH';
}
public function prefixKeys($prefix)
{
$this->applyPrefixSkippingLastArgument($prefix);
}
}
-80
View File
@@ -30,77 +30,6 @@ class CLIENT extends RedisCommand
return 'CLIENT';
}
public function setArguments(array $arguments)
{
switch ($arguments[0]) {
case 'LIST':
$this->setListArguments($arguments);
break;
case 'NOEVICT':
$arguments[0] = 'NO-EVICT';
$this->setNoTouchArguments($arguments);
break;
case 'NOTOUCH':
$arguments[0] = 'NO-TOUCH';
$this->setNoTouchArguments($arguments);
break;
case 'SETINFO':
$this->setSetInfoArguments($arguments);
break;
default:
parent::setArguments($arguments);
}
}
private function setListArguments(array $arguments): void
{
$processedArguments = [$arguments[0]];
if (array_key_exists(1, $arguments) && null !== $arguments[1]) {
array_push($processedArguments, 'TYPE', strtoupper($arguments[1]));
}
if (array_key_exists(2, $arguments)) {
array_push($processedArguments, 'ID', $arguments[2]);
}
if (count($arguments) > 3) {
for ($i = 3, $iMax = count($arguments); $i < $iMax; $i++) {
$processedArguments[] = $arguments[$i];
}
}
parent::setArguments($processedArguments);
}
private function setNoTouchArguments(array $arguments): void
{
$processedArguments = [$arguments[0]];
if (array_key_exists(1, $arguments) && null !== $arguments[1]) {
$modifier = ($arguments[1]) ? 'ON' : 'OFF';
$processedArguments[] = $modifier;
}
parent::setArguments($processedArguments);
}
private function setSetInfoArguments(array $arguments): void
{
$processedArguments = [$arguments[0]];
if (
array_key_exists(1, $arguments)
&& null !== $arguments[1]
&& array_key_exists(2, $arguments)
&& null !== $arguments[2]
) {
array_push($processedArguments, strtoupper($arguments[1]), $arguments[2]);
}
parent::setArguments($processedArguments);
}
/**
* {@inheritdoc}
*/
@@ -143,13 +72,4 @@ class CLIENT extends RedisCommand
return $clients;
}
/**
* @param $data
* @return array|mixed|string|null
*/
public function parseResp3Response($data)
{
return $this->parseResponse($data);
}
}
@@ -10,12 +10,11 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
namespace Predis\Command\Redis\Container;
use Predis\Response\Status;
/**
* @method array cat(string $category = null)
* @method Status dryRun(string $username, string $command, ...$arguments)
* @method array getUser(string $username)
* @method Status setUser(string $username, string ...$rules)
@@ -10,7 +10,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
namespace Predis\Command\Redis\Container;
use Predis\ClientInterface;
@@ -10,7 +10,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
namespace Predis\Command\Redis\Container;
use Predis\Response\Status;
@@ -10,7 +10,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
namespace Predis\Command\Redis\Container;
use Predis\ClientConfiguration;
use Predis\ClientInterface;
@@ -18,7 +18,7 @@ use UnexpectedValueException;
class ContainerFactory
{
private const CONTAINER_NAMESPACE = "Predis\Command\Container";
private const CONTAINER_NAMESPACE = "Predis\Command\Redis\Container";
/**
* Mappings for class names that corresponds to PHP reserved words.
@@ -26,7 +26,7 @@ class ContainerFactory
* @var array
*/
private static $specialMappings = [
'FUNCTION' => FUNCTIONS::class,
'FUNCTION' => FunctionContainer::class,
];
/**
@@ -10,7 +10,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
namespace Predis\Command\Redis\Container;
interface ContainerInterface
{
@@ -10,7 +10,7 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container;
namespace Predis\Command\Redis\Container;
use Predis\Response\Status;
@@ -19,12 +19,12 @@ use Predis\Response\Status;
* @method string dump()
* @method Status flush(?string $mode = null)
* @method Status kill()
* @method array list(string $libraryNamePattern = null, bool $withCode = false)
* @method array list(?string $libraryNamePattern = null, bool $withCode = false)
* @method string load(string $functionCode, bool $replace = 'false')
* @method Status restore(string $value, string $policy = null)
* @method Status restore(string $value, ?string $policy = null)
* @method array stats()
*/
class FUNCTIONS extends AbstractContainer
class FunctionContainer extends AbstractContainer
{
public function getContainerCommandId(): string
{
@@ -10,9 +10,9 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container\Json;
namespace Predis\Command\Redis\Container\Json;
use Predis\Command\Container\AbstractContainer;
use Predis\Command\Redis\Container\AbstractContainer;
/**
* @method array memory(string $key, string $path)
@@ -10,9 +10,9 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container\Search;
namespace Predis\Command\Redis\Container\Search;
use Predis\Command\Container\AbstractContainer;
use Predis\Command\Redis\Container\AbstractContainer;
use Predis\Response\Status;
/**
@@ -10,10 +10,10 @@
* file that was distributed with this source code.
*/
namespace Predis\Command\Container\Search;
namespace Predis\Command\Redis\Container\Search;
use Predis\Command\Argument\Search\CursorArguments;
use Predis\Command\Container\AbstractContainer;
use Predis\Command\Redis\Container\AbstractContainer;
use Predis\Response\Status;
/**
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/decr
@@ -26,9 +26,4 @@ class DECR extends RedisCommand
{
return 'DECR';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/decrby
@@ -26,9 +26,4 @@ class DECRBY extends RedisCommand
{
return 'DECRBY';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/del
@@ -36,9 +36,4 @@ class DEL extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForAllArguments($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/dump
@@ -26,9 +26,4 @@ class DUMP extends RedisCommand
{
return 'DUMP';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -12
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/eval
@@ -36,15 +36,4 @@ class EVAL_ extends RedisCommand
{
return sha1($this->getArgument(0));
}
public function prefixKeys($prefix)
{
if ($arguments = $this->getArguments()) {
for ($i = 2; $i < $arguments[1] + 2; ++$i) {
$arguments[$i] = "$prefix{$arguments[$i]}";
}
$this->setRawArguments($arguments);
}
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/exists
@@ -26,9 +26,4 @@ class EXISTS extends RedisCommand
{
return 'EXISTS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForAllArguments($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\Expire\ExpireOptions;
/**
@@ -33,9 +33,4 @@ class EXPIRE extends RedisCommand
{
return 'EXPIRE';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\Expire\ExpireOptions;
/**
@@ -32,9 +32,4 @@ class EXPIREAT extends RedisCommand
{
return 'EXPIREAT';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -16
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\Keys;
/**
@@ -30,19 +30,4 @@ class FCALL extends RedisCommand
{
return 'FCALL';
}
public function prefixKeys($prefix)
{
$arguments = $this->getArguments();
if (isset($arguments[1])) {
$numkeys = $arguments[1];
for ($i = 2; $i < $numkeys + 2; $i++) {
$arguments[$i] = $prefix . $arguments[$i];
}
}
$this->setRawArguments($arguments);
}
}
+15 -95
View File
@@ -13,6 +13,8 @@
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Strategy\StrategyResolverInterface;
use Predis\Command\Strategy\SubcommandStrategyResolver;
/**
* @see https://redis.io/commands/?name=function
@@ -22,6 +24,16 @@ use Predis\Command\Command as RedisCommand;
*/
class FUNCTIONS extends RedisCommand
{
/**
* @var StrategyResolverInterface
*/
private $strategyResolver;
public function __construct()
{
$this->strategyResolver = new SubcommandStrategyResolver();
}
public function getId()
{
return 'FUNCTION';
@@ -29,102 +41,10 @@ class FUNCTIONS extends RedisCommand
public function setArguments(array $arguments)
{
switch ($arguments[0]) {
case 'FLUSH':
$this->setFlushArguments($arguments);
break;
case 'LIST':
$this->setListArguments($arguments);
break;
case 'LOAD':
$this->setLoadArguments($arguments);
break;
case 'RESTORE':
$this->setRestoreArguments($arguments);
break;
default:
parent::setArguments($arguments);
}
$strategy = $this->strategyResolver->resolve('functions', strtolower($arguments[0]));
$arguments = $strategy->processArguments($arguments);
parent::setArguments($arguments);
$this->filterArguments();
}
/**
* @param array $arguments
* @return void
*/
private function setFlushArguments(array $arguments): void
{
$processedArguments = [$arguments[0]];
if (array_key_exists(1, $arguments) && null !== $arguments[1]) {
$processedArguments[] = strtoupper($arguments[1]);
}
parent::setArguments($processedArguments);
}
/**
* @param array $arguments
* @return void
*/
private function setListArguments(array $arguments): void
{
$processedArguments = [$arguments[0]];
if (array_key_exists(1, $arguments) && null !== $arguments[1]) {
array_push($processedArguments, 'LIBRARYNAME', $arguments[1]);
}
if (array_key_exists(2, $arguments) && true === $arguments[2]) {
$processedArguments[] = 'WITHCODE';
}
parent::setArguments($processedArguments);
}
/**
* @param array $arguments
* @return void
*/
private function setLoadArguments(array $arguments): void
{
if (count($arguments) <= 2) {
parent::setArguments($arguments);
return;
}
$processedArguments = [$arguments[0]];
$replace = array_pop($arguments);
if (is_bool($replace) && $replace) {
$processedArguments[] = 'REPLACE';
} elseif (!is_bool($replace)) {
$processedArguments[] = $replace;
}
$processedArguments[] = $arguments[1];
parent::setArguments($processedArguments);
}
/**
* @param array $arguments
* @return void
*/
private function setRestoreArguments(array $arguments): void
{
$processedArguments = [$arguments[0], $arguments[1]];
if (array_key_exists(2, $arguments) && null !== $arguments[2]) {
$processedArguments[] = strtoupper($arguments[2]);
}
parent::setArguments($processedArguments);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/geoadd
@@ -40,9 +40,4 @@ class GEOADD extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/geodist
@@ -26,9 +26,4 @@ class GEODIST extends RedisCommand
{
return 'GEODIST';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/geohash
@@ -39,9 +39,4 @@ class GEOHASH extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/geopos
@@ -39,9 +39,4 @@ class GEOPOS extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -22
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @deprecated As of Redis version 6.2.0, this command is regarded as deprecated.
@@ -74,25 +74,4 @@ class GEORADIUS extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
if ($arguments = $this->getArguments()) {
$arguments[0] = "$prefix{$arguments[0]}";
$startIndex = $this->getId() === 'GEORADIUS' ? 5 : 4;
if (($count = count($arguments)) > $startIndex) {
for ($i = $startIndex; $i < $count; ++$i) {
switch (strtoupper($arguments[$i])) {
case 'STORE':
case 'STOREDIST':
$arguments[$i] = "$prefix{$arguments[++$i]}";
break;
}
}
}
$this->setRawArguments($arguments);
}
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/get
@@ -26,9 +26,4 @@ class GET extends RedisCommand
{
return 'GET';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/getbit
@@ -26,9 +26,4 @@ class GETBIT extends RedisCommand
{
return 'GETBIT';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/getrange
@@ -26,9 +26,4 @@ class GETRANGE extends RedisCommand
{
return 'GETRANGE';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/getset
@@ -26,9 +26,4 @@ class GETSET extends RedisCommand
{
return 'GETSET';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hdel
@@ -36,9 +36,4 @@ class HDEL extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hexists
@@ -26,9 +26,4 @@ class HEXISTS extends RedisCommand
{
return 'HEXISTS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hget
@@ -26,9 +26,4 @@ class HGET extends RedisCommand
{
return 'HGET';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hgetall
@@ -44,9 +44,4 @@ class HGETALL extends RedisCommand
return $result;
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
-35
View File
@@ -1,35 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* 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;
class HGETDEL extends RedisCommand
{
public function getId()
{
return 'HGETDEL';
}
/**
* @param array $arguments
* @return void
*/
public function setArguments(array $arguments)
{
$processedArguments = [$arguments[0], 'FIELDS', count($arguments[1])];
$processedArguments = array_merge($processedArguments, $arguments[1]);
parent::setArguments($processedArguments);
}
}
-81
View File
@@ -1,81 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* 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;
use UnexpectedValueException;
class HGETEX extends RedisCommand
{
public const NULL = '';
public const EX = 'ex';
public const PX = 'px';
public const EXAT = 'exat';
public const PXAT = 'pxat';
public const PERSIST = 'persist';
/**
* @var string[]
*/
private static $modifierEnum = [
self::EX => 'EX',
self::PX => 'PX',
self::EXAT => 'EXAT',
self::PXAT => 'PXAT',
self::PERSIST => 'PERSIST',
];
public function getId()
{
return 'HGETEX';
}
public function setArguments(array $arguments)
{
$processedArguments = [$arguments[0]];
// Only required arguments
if (!array_key_exists(2, $arguments) || $arguments[2] == '') {
array_push($processedArguments, 'FIELDS', count($arguments[1]));
$processedArguments = array_merge($processedArguments, $arguments[1]);
parent::setArguments($processedArguments);
return;
}
if (!in_array(strtoupper($arguments[2]), self::$modifierEnum)) {
$enumValues = implode(', ', array_keys(self::$modifierEnum));
throw new UnexpectedValueException("Modifier argument accepts only: {$enumValues} values");
}
// PERSIST requires no additional value
if (strtoupper($arguments[2]) === self::$modifierEnum['persist']) {
$processedArguments[] = self::$modifierEnum['persist'];
array_push($processedArguments, 'FIELDS', count($arguments[1]));
$processedArguments = array_merge($processedArguments, $arguments[1]);
parent::setArguments($processedArguments);
return;
}
if (!array_key_exists(3, $arguments) || !is_int($arguments[3])) {
throw new UnexpectedValueException('Modifier value is missing or incorrect type');
}
// Order matters so FIELDS should be at the end
array_push($processedArguments, self::$modifierEnum[strtolower($arguments[2])], $arguments[3], 'FIELDS', count($arguments[1]));
$processedArguments = array_merge($processedArguments, $arguments[1]);
parent::setArguments($processedArguments);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hincrby
@@ -26,9 +26,4 @@ class HINCRBY extends RedisCommand
{
return 'HINCRBY';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hincrbyfloat
@@ -26,9 +26,4 @@ class HINCRBYFLOAT extends RedisCommand
{
return 'HINCRBYFLOAT';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hkeys
@@ -26,9 +26,4 @@ class HKEYS extends RedisCommand
{
return 'HKEYS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hlen
@@ -26,9 +26,4 @@ class HLEN extends RedisCommand
{
return 'HLEN';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hmget
@@ -36,9 +36,4 @@ class HMGET extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hmset
@@ -46,9 +46,4 @@ class HMSET extends RedisCommand
parent::setArguments($arguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -15
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hscan
@@ -95,18 +95,4 @@ class HSCAN extends RedisCommand
return $data;
}
/**
* @param $data
* @return array|mixed|string|null
*/
public function parseResp3Response($data)
{
return $this->parseResponse($data);
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hset
@@ -26,9 +26,4 @@ class HSET extends RedisCommand
{
return 'HSET';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
-117
View File
@@ -1,117 +0,0 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* 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;
use UnexpectedValueException;
class HSETEX extends RedisCommand
{
public const TTL_NULL = '';
public const TTL_EX = 'ex';
public const TTL_PX = 'px';
public const TTL_EXAT = 'exat';
public const TTL_PXAT = 'pxat';
public const TTL_KEEP_TTL = 'keepttl';
public const SET_NULL = '';
public const SET_FNX = 'fnx';
public const SET_FXX = 'fxx';
/**
* @var string[]
*/
private static $ttlModifierEnum = [
self::TTL_EX => 'EX',
self::TTL_PX => 'PX',
self::TTL_EXAT => 'EXAT',
self::TTL_PXAT => 'PXAT',
self::TTL_KEEP_TTL => 'KEEPTTL',
];
/**
* @var string[]
*/
private static $setModifierEnum = [
self::SET_FNX => 'FNX',
self::SET_FXX => 'FXX',
];
public function getId()
{
return 'HSETEX';
}
public function setArguments(array $arguments)
{
$processedArguments = [$arguments[0]];
$flatArray = [];
// Convert key => value, into key, value
array_walk($arguments[1], function ($value, $key) use (&$flatArray) {
array_push($flatArray, $key, $value);
});
// Only required arguments
if (!array_key_exists(2, $arguments)) {
array_push($processedArguments, 'FIELDS', count($flatArray) / 2);
$processedArguments = array_merge($processedArguments, $flatArray);
parent::setArguments($processedArguments);
return;
}
if ($arguments[2] !== '') {
if (!in_array(strtoupper($arguments[2]), self::$setModifierEnum)) {
$enumValues = implode(', ', array_keys(self::$setModifierEnum));
throw new UnexpectedValueException("Modifier argument accepts only: {$enumValues} values");
}
$processedArguments[] = self::$setModifierEnum[strtolower($arguments[2])];
}
// Required + set modifier
if (!array_key_exists(3, $arguments) || $arguments[3] == '') {
array_push($processedArguments, 'FIELDS', count($flatArray) / 2);
$processedArguments = array_merge($processedArguments, $flatArray);
parent::setArguments($processedArguments);
return;
}
if (!in_array(strtoupper($arguments[3]), self::$ttlModifierEnum)) {
$enumValues = implode(', ', array_keys(self::$ttlModifierEnum));
throw new UnexpectedValueException("Modifier argument accepts only: {$enumValues} values");
}
// KEEPTTL requires no additional value
if (strtoupper($arguments[3]) === self::$ttlModifierEnum[self::TTL_KEEP_TTL]) {
$processedArguments[] = self::$ttlModifierEnum[self::TTL_KEEP_TTL];
array_push($processedArguments, 'FIELDS', count($flatArray) / 2);
$processedArguments = array_merge($processedArguments, $flatArray);
parent::setArguments($processedArguments);
return;
}
if (!array_key_exists(4, $arguments) || !is_int($arguments[4])) {
throw new UnexpectedValueException('Modifier value is missing or incorrect type');
}
// Order matters so FIELDS should be at the end
array_push($processedArguments, self::$ttlModifierEnum[strtolower($arguments[3])], $arguments[4], 'FIELDS', count($flatArray) / 2);
$processedArguments = array_merge($processedArguments, $flatArray);
parent::setArguments($processedArguments);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hsetnx
@@ -26,9 +26,4 @@ class HSETNX extends RedisCommand
{
return 'HSETNX';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hstrlen
@@ -26,9 +26,4 @@ class HSTRLEN extends RedisCommand
{
return 'HSTRLEN';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/hvals
@@ -26,9 +26,4 @@ class HVALS extends RedisCommand
{
return 'HVALS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
+1 -6
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Command as RedisCommand;
/**
* @see http://redis.io/commands/incr
@@ -26,9 +26,4 @@ class INCR extends RedisCommand
{
return 'INCR';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

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