diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b9d8ebea --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: / + schedule: + interval: "monthly" + groups: + github-actions: + patterns: + - "*" + + - package-ecosystem: "composer" + directory: / + schedule: + interval: "monthly" + groups: + composer-minor: + update-types: + - "minor" + - "patch" diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index ab148da5..9b8cde24 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -73,7 +73,7 @@ jobs: run: composer validate --no-interaction --strict - name: Install dependencies - uses: ramsey/composer-install@4.0.0 + uses: ramsey/composer-install@v4 with: dependency-versions: highest @@ -110,7 +110,7 @@ jobs: uses: greut/eclint-action@v0 - name: Install dependencies - uses: ramsey/composer-install@4.0.0 + uses: ramsey/composer-install@v4 with: dependency-versions: highest diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 7939b1a2..989149a5 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -8,7 +8,7 @@ jobs: - name: Checkout uses: actions/checkout@v6 - name: Check Spelling - uses: rojopolis/spellcheck-github-actions@0.33.1 + uses: rojopolis/spellcheck-github-actions@0.60.0 with: config_path: .github/spellcheck-settings.yml task_name: Markdown diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4dc1c998..2c817a21 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,7 +42,7 @@ jobs: run: | # Mapping of original redis versions to client test containers declare -A redis_clients_version_mapping=( - ["8.8"]="custom-26235535976-debian" + ["8.8"]="8.8.0" ["8.6"]="8.6.1" ["8.4"]="8.4.0" ["8.2"]="8.2.2" @@ -125,7 +125,7 @@ jobs: coverage: ${{ (matrix.php == '8.4' && matrix.redis == '8.0') && 'xdebug' || 'none' }} - name: Install Composer dependencies - uses: ramsey/composer-install@4.0.0 + uses: ramsey/composer-install@v4 with: dependency-versions: highest composer-options: ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ae327d..d9bde89b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,24 @@ ## Changelog -## Unreleased +## v3.5.0 (2026-06-02) ### Added +- Added support for `XNACK` command (#1666) +- Added support for `INCREX` command (#1674) +- Added support for `UNLINK` command (#1680) +- Added support for `AR*` array commands (#1672) - Handle Redis Cluster `-READONLY` responses failover events (#1656) -- Added FPHA argument for JSON.SET command (#1661) -- Added new COUNT aggregator for Sorted Set commands (#1668) -- Added XNACK support (#1666) -- Added support for multiple aggregators for TS range commands (#1670) -- Added testing for subkey notification channels (#1671) -- Added support for new array commands (#1672) -- Added support for INCREX command (#1674) +- Added `FPHA` argument for `JSON.SET` command (#1661) +- Added new `COUNT` aggregator for Sorted Set commands (#1668) +- Added support for multiple aggregators for `TS.range` commands (#1670) ### Changed - Include command name in unsupported container command error messages (#1653) -- Updated arguments name for GCRA command (#1667) -- Updated INCREX arguments (#1677) ### Fixed - Fixed handling of gap slots in `SlotMap::offsetUnset()` (#1660) -- Fixed ZRANGE to include 6.2 arguments (#1662) -- Fixed Sentinel retry to narrow retryable exceptions to CommunicationException (#1665) -- Fixed SENTINEL SLAVES RESP3 incompatible response (#1676) +- Fixed `ZRANGE` to include 6.2 arguments (#1662) +- Fixed Sentinel retry to narrow retryable exceptions to `CommunicationException` (#1665) +- Fixed `SENTINEL SLAVES` RESP3 incompatible response (#1676) ## v3.4.2 (2026-03-09) ### Changed diff --git a/VERSION b/VERSION index 4f357096..e5b82034 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.4.3-dev \ No newline at end of file +3.5.0 \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index f9bcdc9a..d11a677e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -56,7 +56,7 @@ use Traversable; */ class Client implements ClientInterface, IteratorAggregate { - public const VERSION = '3.4.3-dev'; + public const VERSION = '3.5.0'; /** @var OptionsInterface */ private $options; diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index d3eada45..0425ef2d 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -77,6 +77,7 @@ use Predis\Command\Redis\VADD; * @method $this sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false) * @method $this ttl($key) * @method $this type($key) + * @method $this unlink(string ...$keys) * @method $this append($key, $value) * @method $this arcount(string $key) * @method $this ardel(string $key, int ...$index) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index ceadec02..1a3fd8e1 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -87,6 +87,7 @@ use Predis\Response\Status; * @method array sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false) * @method int ttl(string $key) * @method mixed type(string $key) + * @method int unlink(string ...$keys) * @method int append(string $key, $value) * @method int arcount(string $key) * @method int ardel(string $key, int ...$index) diff --git a/src/Cluster/ClusterStrategy.php b/src/Cluster/ClusterStrategy.php index 6ed3a8d8..16064b06 100644 --- a/src/Cluster/ClusterStrategy.php +++ b/src/Cluster/ClusterStrategy.php @@ -42,6 +42,7 @@ abstract class ClusterStrategy implements StrategyInterface /* commands operating on the key space */ 'EXISTS' => $getKeyFromAllArguments, 'DEL' => $getKeyFromAllArguments, + 'UNLINK' => $getKeyFromAllArguments, 'TYPE' => $getKeyFromFirstArgument, 'EXPIRE' => $getKeyFromFirstArgument, 'EXPIREAT' => $getKeyFromFirstArgument, diff --git a/src/Command/Redis/UNLINK.php b/src/Command/Redis/UNLINK.php new file mode 100644 index 00000000..c9c0fd07 --- /dev/null +++ b/src/Command/Redis/UNLINK.php @@ -0,0 +1,34 @@ +applyPrefixForAllArguments($prefix); + } +} diff --git a/tests/Predis/Cluster/PredisStrategyTest.php b/tests/Predis/Cluster/PredisStrategyTest.php index 3be751e5..1679f210 100644 --- a/tests/Predis/Cluster/PredisStrategyTest.php +++ b/tests/Predis/Cluster/PredisStrategyTest.php @@ -369,6 +369,7 @@ class PredisStrategyTest extends PredisTestCase /* commands operating on the key space */ 'EXISTS' => 'keys-all', 'DEL' => 'keys-all', + 'UNLINK' => 'keys-all', 'TYPE' => 'keys-first', 'EXPIRE' => 'keys-first', 'EXPIREAT' => 'keys-first', diff --git a/tests/Predis/Cluster/RedisStrategyTest.php b/tests/Predis/Cluster/RedisStrategyTest.php index 9e4a9595..d23b53c3 100644 --- a/tests/Predis/Cluster/RedisStrategyTest.php +++ b/tests/Predis/Cluster/RedisStrategyTest.php @@ -392,6 +392,7 @@ class RedisStrategyTest extends PredisTestCase /* commands operating on the key space */ 'EXISTS' => 'keys-all', 'DEL' => 'keys-all', + 'UNLINK' => 'keys-all', 'TYPE' => 'keys-first', 'EXPIRE' => 'keys-first', 'EXPIREAT' => 'keys-first', diff --git a/tests/Predis/Command/Redis/UNLINK_Test.php b/tests/Predis/Command/Redis/UNLINK_Test.php new file mode 100644 index 00000000..6122ff27 --- /dev/null +++ b/tests/Predis/Command/Redis/UNLINK_Test.php @@ -0,0 +1,120 @@ +getCommand(); + $command->setArguments($arguments); + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $command = $this->getCommand(); + + $this->assertSame(10, $command->parseResponse(10)); + } + + /** + * @group disconnected + */ + public function testPrefixKeys(): void + { + /** @var PrefixableCommand $command */ + $command = $this->getCommand(); + $actualArguments = ['arg1', 'arg2', 'arg3', 'arg4']; + $prefix = 'prefix:'; + $expectedArguments = ['prefix:arg1', 'prefix:arg2', 'prefix:arg3', 'prefix:arg4']; + + $command->setArguments($actualArguments); + $command->prefixKeys($prefix); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @group connected + * @requiresRedisVersion >= 4.0.0 + */ + public function testReturnsNumberOfUnlinkedKeys(): void + { + $redis = $this->getClient(); + + $this->assertSame(0, $redis->unlink('foo')); + + $redis->set('foo', 'bar'); + $this->assertSame(1, $redis->unlink('foo')); + + $redis->set('foo', 'bar'); + $this->assertSame(1, $redis->unlink('foo', 'hoge')); + + $redis->set('foo', 'bar'); + $redis->set('hoge', 'piyo'); + $this->assertSame(2, $redis->unlink('foo', 'hoge')); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.0.0 + */ + public function testReturnsNumberOfUnlinkedKeysResp3(): void + { + $redis = $this->getResp3Client(); + + $this->assertSame(0, $redis->unlink('foo')); + + $redis->set('foo', 'bar'); + $this->assertSame(1, $redis->unlink('foo')); + + $redis->set('foo', 'bar'); + $this->assertSame(1, $redis->unlink('foo', 'hoge')); + + $redis->set('foo', 'bar'); + $redis->set('hoge', 'piyo'); + $this->assertSame(2, $redis->unlink('foo', 'hoge')); + } +}