638 Commits

Author SHA1 Message Date
Michael Grunder b0d534e1ca Update tests/RedisTest.php
Co-authored-by: Pavlo Yatsukhnenko <yatsukhnenko@users.noreply.github.com>
2026-06-04 12:16:35 -07:00
Michael Grunder 2a0569ece7 Update tests/TestSuite.php
Co-authored-by: Pavlo Yatsukhnenko <yatsukhnenko@users.noreply.github.com>
2026-06-04 12:16:35 -07:00
Michael Grunder 4a02f3dda6 Update tests/RedisTest.php
Co-authored-by: Pavlo Yatsukhnenko <yatsukhnenko@users.noreply.github.com>
2026-06-04 12:16:35 -07:00
michael-grunder 2c5ef19257 Introduce new RedisCmd based command construction
* Introduce a new `RedisCmd` struct to dynamically append RESP arguments
  such that we don't have to precalculate the number of arguments the
  command will have up front.

  Additionally the new `RedisCmd allows both a `void *` context pointer
  but also can attach a `void (*ctx_dtor)(void*)` destructor so we are
  still able to clean up any allocated context when commands fail.

  This moves the context cleanup out of every individual reply handler
  and into the generic processing wrappers.

* Create a small group of `resp_str` helper functions for lower level
  concatination of RESP protocol data over the wire.

* Lots of small modernization of the codebase such as using
  `zend_string*` instead of (`char *`, `size_t`) pairs.

* Greatly simplify `crosslot` handling logic
2026-06-04 12:16:35 -07:00
michael-grunder 2bf673c64f fix: Don't blindly return LZ4 header length strings
Previously we were only checking if `LZ4_decompress_safe` was returning
> 0 but then blindly returning to the user whatever length the header
specified.

This fix does two things:

* Short circuits on negative length headers
* Fails the decompression if the decompressed length does not match.
2026-05-21 12:13:50 -07:00
Arshid b83af6417b Fix serialization failure handling for anonymous classes (#2838)
* Fix serialization failure handling for anonymous classes
2026-05-11 19:54:02 -07:00
武田 憲太郎 82bf96c3c0 Fix flaky testExists by using deterministic key setup
The test used rand() to decide which keys to create, making it
possible for $mkeys to be empty. This caused EXISTS to receive an
empty array, resulting in a sporadic assertion failure:
(false) !== 0

Observed in #2825 CI and also in an unrelated branch:
- https://github.com/phpredis/phpredis/actions/runs/24132080914
- https://github.com/phpredis/phpredis/actions/runs/23617186872
2026-04-08 13:05:32 -07:00
michael-grunder 10b77a42d6 Implement GCRA command 2026-03-25 11:03:42 -07:00
derrickschoen 409508afa2 fix: Accept null for $seeds in RedisCluster::__construct
The stub declares $seeds as ?array but the C code used format
specifier 'a' (non-nullable) instead of 'a!' in
zend_parse_method_parameters. This caused new RedisCluster(null, null)
to throw TypeError instead of RedisClusterException, contradicting
the declared type signature.

Also treat z_seeds == NULL the same as ZEND_NUM_ARGS() < 2 so that
explicitly passing null falls through to INI-based seed loading,
matching the behaviour when the argument is omitted entirely.

Fixes GH-2810.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 14:35:36 -08:00
michael-grunder b97951cddc Rework TLS context logic
Instead of currying around a `php_stream_context` object, just retain
the context array provided by the user itself like we do with other
connection information like host and port. This lets users reconnect in
a loop without leaking memory.

```php
$redis = new \Redis;
while (true) {
    // Previously each reconnect call would leak the
    // `php_stream_context` structure.
    $redis->connect('tls://127.0.0.1', 9999, 1, null, 0, 0, [
        'stream' => ['verify_peer' => false, 'verify_peer_name' => false],
    ]);

    $redis->ping();

    $redis->close();
}
```
2026-02-18 09:46:55 -08:00
michael-grunder 6f42a3493d Add a regression test for numeric strinig fields and key names
See #2795, #2796
2026-01-13 17:46:31 -08:00
michael-grunder cab0505155 Fix deserializing non-string keys
In `array_zip_values_and_scores` we were blindly calling `Z_STR_P` on
the `zval` assuming it must be a string.

This isn't the case however if the user did something like this:

```php
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$redis->zAdd('zs', 3.14, ['pi', 'is', 'cool']);

// segfault when we try to get `Z_STR_P` from `['pi', 'is', 'cool']`
$redis->zRange('zs', 0, -1, true);
```

Potential fix for #2791
2026-01-09 09:04:38 -08:00
michael-grunder 0debad4c83 Relax the _digest() test for PHP < 8.1
Just make sure the method is sound. We don't really care about specific
exception messages.
2025-11-27 12:23:46 -08:00
michael-grunder 4baad44bcf Handle false from $this->redis->info() in TestSuite::setUp()
Technically `INFO` can return false in the event of a protocol error or
similar. If that happens the tests are unlikely to be useful but we
should at least account for it to avoid an immediate fatal error when
calling helpers like `detectKeyDB` and `detectValkey`.

See #2783
2025-11-25 14:43:28 -08:00
michael-grunder a033e40b7b Fix MSETEX TTL assertions
We want to include the actual TTL itself in our range.
2025-11-18 11:19:52 -08:00
michael-grunder 8b4cfb725f Add a test for XDELEX 2025-11-12 09:14:43 -08:00
michael-grunder bd4989e004 Add a test for MSETEX 2025-11-12 08:41:21 -08:00
michael-grunder 107cc7aacb Make sure _digest throws an exception in PHP < 8.1 2025-11-06 11:00:16 -08:00
michael-grunder 00c62de277 Implement DELEX command 2025-11-06 09:48:09 -08:00
michael-grunder e2dd13ce7b Implement DIGEST and _digest helper (for php >= 8.1)
Redis implemented new CAS semantics which work both with values and the
XXH3 digest of those values.

This commit implements the Redis command itself and a helper which
computes the XXH3 digest locally. Note that we can only be sure to have
the `XXH3` hashing algorithm in PHP >= 8.1 so the `_digest` helper is
limited to PHP 8.1 or newer.
2025-11-06 09:48:09 -08:00
michael-grunder 0abe2c295e Fix HMGET where hash fields have an integer prefix
We were incorrectly ignoring errors when calling `is_numeric_string`,
meaning we would truncate hash fields that had integer prefixes.

```php
$redis->hmget('hash', ['123notaninteger']);
// Would actually execute:
// HMGET hash 123
```

Fixes #2731
2025-10-09 12:11:20 -07:00
Pavlo Yatsukhnenko 7d3b2e4d6d Add hGetWithMeta method 2025-10-06 16:22:59 -07:00
Remi Collet f5db01b781 fix testXGrous expectation for change in redis 8.2.2 2025-10-03 06:03:36 -07:00
michael-grunder 6ce3bd533a Implement VRANGE command and add a test 2025-10-02 11:12:39 -07:00
michael-grunder f24814a423 Fix geosearchstore bypolygon test
In cluster mode the destination and source keys must hash to the same
slot.
2025-09-10 11:56:49 -07:00
michael-grunder b3da0f3bcf Remove a "bad argument" assertion for testWait
It seems like Redis changed what it will do when you send a negative
number of events. Previously this would just return an error but it
seems to return `1` now.

For this reason just remove that assertion so we don't have to use
different logic depending on the version of the server.
2025-09-10 11:33:35 -07:00
michael-grunder 22a2914b09 Add a regression test 2025-09-09 16:30:43 -07:00
michael-grunder 92137ffd3f We actually do return bool in sismember so do the same here 2025-09-01 09:41:12 -07:00
michael-grunder 92dd256f98 Implement VISMEMBER command. 2025-09-01 09:41:12 -07:00
michael-grunder 0b4b4ed2c3 Add a test for VGETATTR 2025-09-01 09:41:12 -07:00
michael-grunder ea11d62aec Test for VSETATTR 2025-09-01 09:41:12 -07:00
michael-grunder 5fe188416d Add a test for VRANDMEMBER 2025-09-01 09:41:12 -07:00
michael-grunder bbae745a93 Add a test for VREM
See #2543
2025-09-01 09:41:12 -07:00
michael-grunder 96378b70fd Implement VEMB and slightly rework VINFO
Unfortunately `VEMB` has a unique `RESP2` reply as far as I can tell,
where it sends the embedding mode (int8, bin, fp32) as a simple string.

This would cause any of PhpRedis' generic reply handlers to turn that
into `true` which isn't useful. For that reason we need a custom reply
handler.

Additionally slightly rework `VINFO` to short circuit and return failure
if we read anything other than a bulk string or an integer reply type.
Otherwise we may get out of sync on the socket.

See #2543
2025-09-01 09:41:12 -07:00
michael-grunder 8f8a49bec2 Tests for VCARD, VDIM, and VINFO. 2025-09-01 09:41:12 -07:00
michael-grunder 0ed0fc0562 Add Redis::REDIS_VECTORSET type.
Redis >= 8.0 has a new type `vectorset` that we should support like all
the other types.
2025-08-28 09:34:07 -07:00
michael-grunder 8d369f4d62 Implement GEOSEARCH[STORE] BYPOLYGON.
Valkey 9.0.0 implemented a new variant of `GEOSEARCH` where you supply
the verticies to an arbitrary polygon.

Since we can't modify the `geosearch` prototype using it is a little
wonky (you need to just pass empty strings for position and unit).

```php
$redis->geosearch('ca:cities', '', [
    -121.90, 39.65, -121.77, 39.65, -121.77, 39.80, -121.90, 39.80
], '');
$redis->geosearchstore('ca:cities', 'dst', '', [
    -121.90, 39.65, -121.77, 39.65, -121.77, 39.80, -121.90, 39.80
], '');
```
2025-08-24 06:37:51 -07:00
michael-grunder b1b0c19142 Implement DELIFEQ command
Implement the command and add a test.
2025-08-24 06:37:28 -07:00
Michael Grunder b83981aaeb Rework HEXPIRE test inclusion + bump Valkey (#2684)
* Rework `HEXPIRE` test inclusion + bump Valkey

* Add a little `haveCommand` helper which uses `COMMAND INFO` to check
  if a given server has a specific command. This way when we bump valkey
  to an official release that supports the commands we will start
  testing.
* Bump Valkey from 7.2.5 to 8.1.3 which is much newer.

* Rework `haveCommand` to explicitly check for the command name

COMMAND INFO will return the command name as one of the first bits of
data so we can check for it that way.

* Fix incorrect logic
2025-08-06 10:08:49 -07:00
michael-grunder 6e5faf4226 Refactor EVAL[SHA] command and add a regression test
* We can make the code simpler by using `zend_empty_array` when no args
  are passed as well as the new argument parsing macros and newer internal
  redis command appending functions that take zend strings.

* Add a regression test for when we execute `EVAL[SHA]` with arguments
  but do not send any keys. This was causing UB in RedisCluster (#2681).
2025-08-05 13:20:36 -07:00
michael-grunder d1d690053f Implement VSIM command
This command is similar to `VADD` in that it's pretty simple but allows
for a great many options.

In it's most basic form:

```php
// To get similarity of a different element
$redis->vsim('myvec', 'some-element');

// To get similarity for a vector of scores
```

As seen above the method attempts to infer element or vector from the
argument passed to $member`. However, since we do serialize the member
when doing `ELE` mode, the user can also specify `ELE` explicitly in the
options array to force an `ELE` search sending serialized values.

```php
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$redis->vsim('myvec', [3.14, 2.71], ['ELE']);
```

See #2543
2025-07-31 08:30:47 -07:00
michael-grunder 286fa63064 Implement VADD command
This is for Redis 8.0's vector sets.

The command itself can be quite complex with all of the various options but
pretty simple using all defaults.

```php
$redis->vadd('myvec', [3.14, 2.17], 'myelement');
```

The implementation takes a default argument `$options` which can be an array in
order to specify the myriad of other knobs users can send. We just do a bit of
validation on inputs (e.g. certain numeric options must be positive) and make
sure the command is constructed in a valid way (e.g. REDUCE <dim> must come
before the floating point values).

By default we deliver `FP32` blobs but allow the user to send `VALUES` in the
options array which will cause PhpRedis to send N individual values. Sending
values is slower but might be nice for debugging (e.g. watching monitor)

See #2543
2025-07-31 00:57:28 -07:00
michael-grunder 2acab399cb Fix the echo liveness check when in sentinel mode.
The current echo liveness check was doing one big complex conditional
trying to incorporate both sentinel's expected ERR no such command
response and non-sentinel's actual bulk reply to ECHO.

This commit refactors the logic to check the echo response into a little
helper with different logic depending on whether or not we're connected
to a sentinel.

Additionally, we add a test to verify that we are in fact reusing
persistent connections when the user requests a persistent connection
with `RedisSentinel`.

Fixes #2148
2025-07-18 08:43:22 -07:00
Michael Grunder ce5b0facc2 Implement HGETEX, HSETEX, HGETDEL, and refactor HMGET (#2667)
* Rework HMGET and implement HGETEX

Instead of using a bespoke NULL terminated `zval**` array for the
context array we can use a `HashTable`. This might be a tiny bit more
expensive but Zend hashtables are quite efficient and this should also
be less error prone.

* Rework our `HashTable` context array to store keys

Instead of sending an array of values we can instead add the fields as
keys to our context array. That way when we combine the keys with the
Redis provided values we can do it in-place and then just give the
HashTable to the user to then do with what they want.

* Implement HGETDEL command.

* Fix edge cases to abide by legacy behavior.

Previously we coerced integer strings into integer keys when zipping
`HMGET` responses. This commit adds logic so we continue to do this and
do not change semantics.

* Implement `HGETDEL` and `HGETEX` for `RedisCluster`.

This commit implements the new commands and reworks the `HMGET` reply
handler to use the new context `HashTable`.

* Fix an edge case where we get zero multiblk elements

* Tests for `HGETEX` and `HGETDEL`

* Minor logic improvement

We don't need to check if `c->reply_len > 0` in the last else block
since we have already determined it must be.

* Implement `HSETEX` for `Redis` and `RedisCluster`

* Use `zval_get_tmp_string` ro populating non-long keys
2025-07-16 16:46:09 -07:00
michael-grunder 152fdda9b1 Fix double -> int truncation warning 2025-05-08 09:19:12 -07:00
michael-grunder 8014000369 Attempt to fix flaky GitHub CI tests.
We often have to rerun the test suite on GitHub actions because of a
hard to reproduce "Read error on connection" exception when getting a
new `RedisCluster` instance.

No one has ever reported this failure outside of GitHub CI and it's not
clear exactly what might be going on.

This commit does two main things:

1. Allows for one failure to construct a new `RedisCluster` instance but
   only if we detect we're running in GitHub CI.

2. Adds much more diagnostic information if we still have a fatal error
   (e.g. we can't connect in two tries, or some other fatal error
   happens). The new info includes the whole callstack before aborting
   as well as an attempt to manually ping the seeds with `redis-cli`.
2025-05-08 09:19:12 -07:00
michael-grunder 7350768cd9 Implement several hash expiration commands
Commands implemented:

`H[P]EXPIRE`
`H[P]TTL`
`H[P]EXPIREAT`
`H[P]EXPIRETIME`
`HPERSIST`
2025-05-07 08:16:14 -07:00
michael-grunder 593ba012ac Check for dragonfly_version in HELLO response
DragonflyDB will report to be Redis but also include `dragonfly_version`
in the hello response, which we can use to identify the fork.

Also fix parsing of the `HELLO` response for `serverName()` and
`serverVersion()`. Starting in Redis 8.0 there seem to always be modules
running, which the previous function was not expecting or parsing.
2025-05-05 09:31:11 -07:00
Michael Grunder 60ca48f3ce Redis Cluster does not have SELECT. (#2644) 2025-04-01 11:33:44 -07:00
Michael Grunder 0445e683e7 Refactor getWithMeta logic (#2643)
* Refactor `getWithMeta`

* Consolidate `getWithMeta()` test.

* Review comments
2025-03-31 12:42:29 -07:00