diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index 23fc0d3f..e6a6064b 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -18,6 +18,7 @@ use Predis\Command\CommandInterface; /** * Interface defining a client-side context such as a pipeline or transaction. * + * @method $this copy(string $source, string $destination, int $db = -1, bool $replace = false) * @method $this del(array|string $keys) * @method $this dump($key) * @method $this exists($key) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 76baf94b..3acd5fa5 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -27,6 +27,7 @@ use Predis\Response\Status; * and more friendly interface to ease programming which is described in the * following list of methods: * + * @method int copy(string $source, string $destination, int $db = -1, bool $replace = false) * @method int del(string[]|string $keyOrKeys, string ...$keys = null) * @method string|null dump(string $key) * @method int exists(string $key) diff --git a/src/Command/Redis/COPY.php b/src/Command/Redis/COPY.php new file mode 100644 index 00000000..7b3bafd3 --- /dev/null +++ b/src/Command/Redis/COPY.php @@ -0,0 +1,36 @@ +setDB($arguments); + $arguments = $this->getArguments(); + + $this->setReplace($arguments); + } +} diff --git a/src/Command/Traits/DB.php b/src/Command/Traits/DB.php new file mode 100644 index 00000000..38a85e3f --- /dev/null +++ b/src/Command/Traits/DB.php @@ -0,0 +1,41 @@ += $argumentsLength) { + parent::setArguments($arguments); + return; + } + + if (!is_numeric($arguments[static::$dbArgumentPositionOffset])) { + throw new UnexpectedValueException('DB argument should be a valid numeric value'); + } + + if ($arguments[static::$dbArgumentPositionOffset] < 0) { + array_splice($arguments, static::$dbArgumentPositionOffset, 1); + parent::setArguments($arguments); + return; + } + + $argument = $arguments[static::$dbArgumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, static::$dbArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$dbArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [$this->dbModifier], + [$argument], + $argumentsAfter + )); + } +} diff --git a/src/Command/Traits/Replace.php b/src/Command/Traits/Replace.php new file mode 100644 index 00000000..e0e89da5 --- /dev/null +++ b/src/Command/Traits/Replace.php @@ -0,0 +1,24 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testSuccessfullyCopyValueOnNonExistingDestinationKey(): void + { + $redis = $this->getClient(); + $redis->set('key', 'value'); + + $actualResponse = $redis->copy('key', 'destination'); + + $this->assertSame(1, $actualResponse); + $this->assertSame($redis->get('key'), $redis->get('destination')); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testSuccessfullyCopyValueFromSourceToAnotherDb(): void + { + $defaultDatabaseIndexClient = $this->getClient(); + $defaultDatabaseIndexClient->set('key', 'value'); + + $copyResponse = $defaultDatabaseIndexClient->copy('key', 'new_key', 14); + + $anotherDatabaseIndexClient = $this->createClient(['database' => 14], null, false); + $actualValue = $anotherDatabaseIndexClient->get('new_key'); + $anotherDatabaseIndexClient->flushdb(); + + $this->assertNull($anotherDatabaseIndexClient->get('new_key')); + $this->assertSame(1, $copyResponse); + $this->assertSame('value', $actualValue); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testDoNotCopyValueOnAlreadyExistingDestinationKey(): void + { + $redis = $this->getClient(); + $redis->set('key', 'value'); + $redis->set('destination', 'destination_value'); + + $actualResponse = $redis->copy('key', 'destination'); + + $this->assertSame(0, $actualResponse); + $this->assertSame('destination_value', $redis->get('destination')); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 6.2.0 + */ + public function testSuccessfullyCopyValueWithReplaceArgumentOnAlreadyExistingDestinationKey(): void + { + $redis = $this->getClient(); + $redis->set('key', 'value'); + $redis->set('destination', 'destination_value'); + + $actualResponse = $redis->copy('key', 'destination', -1, true); + + $this->assertSame(1, $actualResponse); + $this->assertSame('value', $redis->get('destination')); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['source', 'destination'], + ['source', 'destination'] + ], + 'with DB argument' => [ + ['source', 'destination', 1], + ['source', 'destination', 'DB', 1] + ], + 'with replace argument' => [ + ['source', 'destination', -1, true], + ['source', 'destination', 'REPLACE'] + ], + 'with all arguments' => [ + ['source', 'destination', 1, true], + ['source', 'destination', 'DB', 1, 'REPLACE'] + ] + ]; + } +} diff --git a/tests/Predis/Command/Traits/DbTest.php b/tests/Predis/Command/Traits/DbTest.php new file mode 100644 index 00000000..ae224636 --- /dev/null +++ b/tests/Predis/Command/Traits/DbTest.php @@ -0,0 +1,78 @@ +testClass = new class extends RedisCommand { + use DB; + + public static $dbArgumentPositionOffset = 0; + + public function getId() + { + return 'test'; + } + }; + } + + /** + * @dataProvider argumentsProvider + * @param int $offset + * @param array $arguments + * @param array $expectedResponse + * @return void + */ + public function testReturnsCorrectArguments(int $offset, array $arguments, array $expectedResponse): void + { + $this->testClass::$dbArgumentPositionOffset = $offset; + + $this->testClass->setArguments($arguments); + + $this->assertSame($expectedResponse, $this->testClass->getArguments()); + } + + /** + * @return void + */ + public function testThrowsErrorOnUnexpectedValueGiven(): void + { + $this->testClass::$dbArgumentPositionOffset = 0; + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('DB argument should be a valid numeric value'); + + $this->testClass->setArguments(['wrong']); + } + + public function argumentsProvider(): array + { + return [ + 'with positive integer db argument' => [ + 0, + [1], + ['DB', 1] + ], + 'with wrong offset' => [ + 1, + [1], + [1] + ], + 'with negative integer db argument' => [ + 1, + ['argument1', -1], + ['argument1'] + ] + ]; + } +} diff --git a/tests/Predis/Command/Traits/ReplaceTest.php b/tests/Predis/Command/Traits/ReplaceTest.php new file mode 100644 index 00000000..27e0ff2d --- /dev/null +++ b/tests/Predis/Command/Traits/ReplaceTest.php @@ -0,0 +1,47 @@ +testClass = new class extends RedisCommand { + use Replace; + + public function getId() + { + return 'test'; + } + }; + } + + /** + * @dataProvider argumentsProvider + * @param array $arguments + * @param array $expectedResponse + * @return void + */ + public function testReturnsCorrectArguments(array $arguments, array $expectedResponse): void + { + $this->testClass->setArguments($arguments); + + $this->assertSame($expectedResponse, $this->testClass->getArguments()); + } + + public function argumentsProvider(): array + { + return [ + 'with boolean - true' => [[true],['REPLACE']], + 'with boolean - false' => [[false], []], + 'with non boolean' => [['string'], ['string']], + ]; + } +}