From 9265c02bfafe6a83770d99c7f16faf14fefd8509 Mon Sep 17 00:00:00 2001 From: "Lucas S. Vieira" Date: Sun, 5 Oct 2025 03:46:49 +0100 Subject: [PATCH 1/4] improve Collection 'keyOptions' attribute validation --- src/Collection/Collection.php | 7 ++- src/Collection/Key.php | 17 ++++++ src/Cursor/CollectionCursor.php | 2 +- src/Validation/Admin/Task/TaskValidator.php | 2 +- .../Collection/CollectionValidator.php | 31 ++++++++++- .../Exceptions/InvalidKeyOptionException.php | 44 +++++++++++++++ .../Exceptions/InvalidParameterException.php | 4 +- src/Validation/Graph/GraphValidator.php | 2 +- src/Validation/Rules/Rules.php | 20 +++---- .../TransactionOptionsValidator.php | 4 +- tests/Collection/CollectionTest.php | 21 +++++++- .../Collection/CollectionValidatorTest.php | 53 ++++++++++++++++++- 12 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 src/Collection/Key.php create mode 100644 src/Validation/Exceptions/InvalidKeyOptionException.php diff --git a/src/Collection/Collection.php b/src/Collection/Collection.php index 87b933c..0ce1086 100644 --- a/src/Collection/Collection.php +++ b/src/Collection/Collection.php @@ -136,8 +136,7 @@ class Collection implements JsonSerializable 'type' => 2, 'keyOptions' => [ 'allowUserKeys' => true, - 'type' => 'traditional', - 'lastValue' => 0 + 'type' => Key::TRADITIONAL, ], ]; @@ -214,7 +213,7 @@ public function __set(string $name, $value) * @return CursorInterface|bool Cursor if collection exists on database. False otherwise. * @throws GuzzleException|InvalidParameterException|CursorException */ - public function all() + public function all(): bool|CollectionCursor { if (!$this->isNew()) { return new CollectionCursor($this); @@ -272,7 +271,7 @@ public function getName(): string * * @return string|null String if collection exists on database. Null if not. */ - public function getId() + public function getId(): ?string { return ($this->attributes['objectId'] === null) ? $this->attributes['id'] : $this->attributes['objectId']; } diff --git a/src/Collection/Key.php b/src/Collection/Key.php new file mode 100644 index 0000000..1144865 --- /dev/null +++ b/src/Collection/Key.php @@ -0,0 +1,17 @@ + @@ -48,7 +51,33 @@ public function rules(): array 'numberOfShards' => Rules::equalsOrGreaterThan(1), 'isSystem' => Rules::boolean(), 'type' => Rules::in([2, 3]), - 'keyOptions' => Rules::arr(), + 'keyOptions' => Rules::callbackValidation(self::validateKeyOptions()), ]; } + + /** + * Validate key options for collection creation + * + * @return \Closure + */ + private static function validateKeyOptions(): \Closure + { + /** + * 'offset' and 'increment' options are only allowed when used with type 'autoincrement' + * + * @return bool + * @throws InvalidKeyOptionException + */ + return function (array $keyOptions) { + if (array_key_exists('offset', $keyOptions) && $keyOptions['type'] != Key::AUTOINCREMENT) { + throw new InvalidKeyOptionException("offset", $keyOptions['type']); + } + + if (array_key_exists('increment', $keyOptions) && $keyOptions['type'] != Key::AUTOINCREMENT) { + throw new InvalidKeyOptionException("increment", $keyOptions['type']); + } + + return true; + }; + } } diff --git a/src/Validation/Exceptions/InvalidKeyOptionException.php b/src/Validation/Exceptions/InvalidKeyOptionException.php new file mode 100644 index 0000000..f5d94a2 --- /dev/null +++ b/src/Validation/Exceptions/InvalidKeyOptionException.php @@ -0,0 +1,44 @@ +getIndexes(); - // Try to drop an non-existent index + // Try to drop a non-existent index $this->expectException(DatabaseException::class); $collection->dropIndex($list->last()); } @@ -520,6 +521,24 @@ public function testSave() $this->assertTrue($collection->drop()); } + public function testSaveWithNonDefaultOptions() + { + $keyOptions = [ + 'allowUserKeys' => false, + 'type' => Key::UUID, + ]; + + $db = new Database($this->getConnectionObject()); + $collection = new Collection('test_save_coll', $db, ['keyOptions' => $keyOptions]); + + // Check if collection is created. + $this->assertNull($collection->getId()); + + $this->assertTrue($collection->save()); + $this->assertIsString($collection->getId()); + $this->assertTrue($collection->drop()); + } + public function testSaveThrowDatabaseException() { // Mock error diff --git a/tests/Validation/Collection/CollectionValidatorTest.php b/tests/Validation/Collection/CollectionValidatorTest.php index 34086a1..2a8ae8a 100644 --- a/tests/Validation/Collection/CollectionValidatorTest.php +++ b/tests/Validation/Collection/CollectionValidatorTest.php @@ -3,6 +3,8 @@ namespace Unit\Validation\Collection; use Unit\TestCase; +use ArangoDB\Collection\Key; +use ArangoDB\Validation\Exceptions\InvalidKeyOptionException; use ArangoDB\Validation\Collection\CollectionValidator; use ArangoDB\Validation\Exceptions\MissingParameterException; use ArangoDB\Validation\Exceptions\InvalidParameterException; @@ -23,7 +25,7 @@ protected function mockCollectionArray(): array 'type' => rand(2, 3), 'keyOptions' => [ 'allowUserKeys' => (bool)rand(0, 1), - 'type' => 'traditional', + 'type' => Key::AUTOINCREMENT, 'lastValue' => 0 ], ]; @@ -80,4 +82,53 @@ public function testThrowInvalidParameterException() $this->expectException(InvalidParameterException::class); $this->assertTrue($collectionValidator->validate()); } + + public function testThrowInvalidKeyOptionException() + { + $mock = $this->mockCollectionArray(); + $mock['keyOptions'] = [ + 'allowUserKeys' => (bool)rand(0, 1), + 'type' => Key::UUID, + 'offset' => 1, + 'lastValue' => 0 + ]; + $collectionValidator = new CollectionValidator($mock); + $this->expectException(InvalidKeyOptionException::class); + $this->assertTrue($collectionValidator->validate()); + + $mock = $this->mockCollectionArray(); + $mock['keyOptions'] = [ + 'allowUserKeys' => (bool)rand(0, 1), + 'type' => Key::TRADITIONAL, + 'offset' => 1, + 'lastValue' => 0 + ]; + $collectionValidator = new CollectionValidator($mock); + $this->expectException(InvalidKeyOptionException::class); + $this->assertTrue($collectionValidator->validate()); + + $mock = $this->mockCollectionArray(); + $mock['keyOptions'] = [ + 'allowUserKeys' => (bool)rand(0, 1), + 'type' => Key::PADDED, + 'offset' => 1, + 'lastValue' => 0 + ]; + $collectionValidator = new CollectionValidator($mock); + $this->expectException(InvalidKeyOptionException::class); + $this->assertTrue($collectionValidator->validate()); + } + + public function testAllowExtraOptionsForAutoIncrementKeyType() + { + $mock = $this->mockCollectionArray(); + $mock['keyOptions'] = [ + 'allowUserKeys' => (bool)rand(0, 1), + 'type' => Key::AUTOINCREMENT, + 'offset' => 1, + 'lastValue' => 0 + ]; + $collectionValidator = new CollectionValidator($mock); + $this->assertTrue($collectionValidator->validate()); + } } From 9c322a16415c772cb99b7ee1a3d578a7cffd502b Mon Sep 17 00:00:00 2001 From: "Lucas S. Vieira" Date: Sun, 5 Oct 2025 03:52:33 +0100 Subject: [PATCH 2/4] refactor 'KeyType' class --- src/Collection/Collection.php | 2 +- src/Collection/{Key.php => KeyType.php} | 2 +- src/DataStructures/ArrayList.php | 4 ++-- src/DataStructures/Contracts/ListInterface.php | 2 +- src/Validation/Collection/CollectionValidator.php | 9 ++++----- tests/Collection/CollectionTest.php | 4 ++-- .../Collection/CollectionValidatorTest.php | 12 ++++++------ 7 files changed, 17 insertions(+), 18 deletions(-) rename src/Collection/{Key.php => KeyType.php} (96%) diff --git a/src/Collection/Collection.php b/src/Collection/Collection.php index 0ce1086..a5c1dfc 100644 --- a/src/Collection/Collection.php +++ b/src/Collection/Collection.php @@ -136,7 +136,7 @@ class Collection implements JsonSerializable 'type' => 2, 'keyOptions' => [ 'allowUserKeys' => true, - 'type' => Key::TRADITIONAL, + 'type' => KeyType::TRADITIONAL, ], ]; diff --git a/src/Collection/Key.php b/src/Collection/KeyType.php similarity index 96% rename from src/Collection/Key.php rename to src/Collection/KeyType.php index 1144865..7cd7bdb 100644 --- a/src/Collection/Key.php +++ b/src/Collection/KeyType.php @@ -8,7 +8,7 @@ * @package ArangoDB\Collection * @author Lucas S. Vieira */ -class Key +class KeyType { public const string TRADITIONAL = 'traditional'; public const string AUTOINCREMENT = 'autoincrement'; diff --git a/src/DataStructures/ArrayList.php b/src/DataStructures/ArrayList.php index 99c6969..14125aa 100644 --- a/src/DataStructures/ArrayList.php +++ b/src/DataStructures/ArrayList.php @@ -71,7 +71,7 @@ public function last() /** * Get a value by its key * - * @param int|string $key Key to verify on list. + * @param int|string $key KeyType to verify on list. * @return mixed */ public function get($key) @@ -96,7 +96,7 @@ public function push($value): void /** * Put a object into list on given key * - * @param string|integer $key Key for manage the value. + * @param string|integer $key KeyType for manage the value. * @param mixed $value Value to add. */ public function put($key, $value): void diff --git a/src/DataStructures/Contracts/ListInterface.php b/src/DataStructures/Contracts/ListInterface.php index 52d87b1..cb0d1d8 100644 --- a/src/DataStructures/Contracts/ListInterface.php +++ b/src/DataStructures/Contracts/ListInterface.php @@ -44,7 +44,7 @@ public function push($value): void; /** * Put a object into list on given key * - * @param string|integer $key Key for manage the value. + * @param string|integer $key KeyType for manage the value. * @param mixed $value Value to add. */ public function put($key, $value): void; diff --git a/src/Validation/Collection/CollectionValidator.php b/src/Validation/Collection/CollectionValidator.php index 2714e11..b0e89b5 100644 --- a/src/Validation/Collection/CollectionValidator.php +++ b/src/Validation/Collection/CollectionValidator.php @@ -4,11 +4,10 @@ namespace ArangoDB\Validation\Collection; -use ArangoDB\Collection\Key; -use ArangoDB\Validation\Exceptions\InvalidKeyOptionException; +use ArangoDB\Collection\KeyType; use ArangoDB\Validation\Validator; use ArangoDB\Validation\Rules\Rules; -use ArangoDB\Validation\Exceptions\InvalidParameterException; +use ArangoDB\Validation\Exceptions\InvalidKeyOptionException; /** * Validate the collection options values.
@@ -69,11 +68,11 @@ private static function validateKeyOptions(): \Closure * @throws InvalidKeyOptionException */ return function (array $keyOptions) { - if (array_key_exists('offset', $keyOptions) && $keyOptions['type'] != Key::AUTOINCREMENT) { + if (array_key_exists('offset', $keyOptions) && $keyOptions['type'] != KeyType::AUTOINCREMENT) { throw new InvalidKeyOptionException("offset", $keyOptions['type']); } - if (array_key_exists('increment', $keyOptions) && $keyOptions['type'] != Key::AUTOINCREMENT) { + if (array_key_exists('increment', $keyOptions) && $keyOptions['type'] != KeyType::AUTOINCREMENT) { throw new InvalidKeyOptionException("increment", $keyOptions['type']); } diff --git a/tests/Collection/CollectionTest.php b/tests/Collection/CollectionTest.php index fb63f3f..c5e2b86 100644 --- a/tests/Collection/CollectionTest.php +++ b/tests/Collection/CollectionTest.php @@ -3,7 +3,7 @@ namespace Unit\Collection; use ArangoDB\Admin\Server; -use ArangoDB\Collection\Key; +use ArangoDB\Collection\KeyType; use Unit\TestCase; use GuzzleHttp\Psr7\Response; use ArangoDB\Document\Vertex; @@ -525,7 +525,7 @@ public function testSaveWithNonDefaultOptions() { $keyOptions = [ 'allowUserKeys' => false, - 'type' => Key::UUID, + 'type' => KeyType::UUID, ]; $db = new Database($this->getConnectionObject()); diff --git a/tests/Validation/Collection/CollectionValidatorTest.php b/tests/Validation/Collection/CollectionValidatorTest.php index 2a8ae8a..26e5929 100644 --- a/tests/Validation/Collection/CollectionValidatorTest.php +++ b/tests/Validation/Collection/CollectionValidatorTest.php @@ -3,7 +3,7 @@ namespace Unit\Validation\Collection; use Unit\TestCase; -use ArangoDB\Collection\Key; +use ArangoDB\Collection\KeyType; use ArangoDB\Validation\Exceptions\InvalidKeyOptionException; use ArangoDB\Validation\Collection\CollectionValidator; use ArangoDB\Validation\Exceptions\MissingParameterException; @@ -25,7 +25,7 @@ protected function mockCollectionArray(): array 'type' => rand(2, 3), 'keyOptions' => [ 'allowUserKeys' => (bool)rand(0, 1), - 'type' => Key::AUTOINCREMENT, + 'type' => KeyType::AUTOINCREMENT, 'lastValue' => 0 ], ]; @@ -88,7 +88,7 @@ public function testThrowInvalidKeyOptionException() $mock = $this->mockCollectionArray(); $mock['keyOptions'] = [ 'allowUserKeys' => (bool)rand(0, 1), - 'type' => Key::UUID, + 'type' => KeyType::UUID, 'offset' => 1, 'lastValue' => 0 ]; @@ -99,7 +99,7 @@ public function testThrowInvalidKeyOptionException() $mock = $this->mockCollectionArray(); $mock['keyOptions'] = [ 'allowUserKeys' => (bool)rand(0, 1), - 'type' => Key::TRADITIONAL, + 'type' => KeyType::TRADITIONAL, 'offset' => 1, 'lastValue' => 0 ]; @@ -110,7 +110,7 @@ public function testThrowInvalidKeyOptionException() $mock = $this->mockCollectionArray(); $mock['keyOptions'] = [ 'allowUserKeys' => (bool)rand(0, 1), - 'type' => Key::PADDED, + 'type' => KeyType::PADDED, 'offset' => 1, 'lastValue' => 0 ]; @@ -124,7 +124,7 @@ public function testAllowExtraOptionsForAutoIncrementKeyType() $mock = $this->mockCollectionArray(); $mock['keyOptions'] = [ 'allowUserKeys' => (bool)rand(0, 1), - 'type' => Key::AUTOINCREMENT, + 'type' => KeyType::AUTOINCREMENT, 'offset' => 1, 'lastValue' => 0 ]; From d1809beddc3c959685877ea261e9afeda518182d Mon Sep 17 00:00:00 2001 From: "Lucas S. Vieira" Date: Sun, 5 Oct 2025 04:15:51 +0100 Subject: [PATCH 3/4] fix exception attribute type --- src/Validation/Exceptions/InvalidParameterException.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Validation/Exceptions/InvalidParameterException.php b/src/Validation/Exceptions/InvalidParameterException.php index 140ee98..eba0b36 100644 --- a/src/Validation/Exceptions/InvalidParameterException.php +++ b/src/Validation/Exceptions/InvalidParameterException.php @@ -18,9 +18,9 @@ class InvalidParameterException extends BaseException /** * Parameter name. * - * @var string + * @var string|int */ - protected string $parameter; + protected string|int $parameter; /** * Parameter value. From 53d59fbd95394d4af8e5fb96a3e0205d06467f7f Mon Sep 17 00:00:00 2001 From: "Lucas S. Vieira" Date: Sun, 5 Oct 2025 04:21:45 +0100 Subject: [PATCH 4/4] drop support for PHP 8.2 --- .github/workflows/php.yml | 2 +- composer.json | 2 +- composer.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index e355aa7..e9f1f8d 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ["8.2", "8.3", "8.4"] + php-versions: ["8.3", "8.4"] steps: - name: Setup PHP diff --git a/composer.json b/composer.json index 6d26c09..deaf4f8 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ } ], "require": { - "php": ">=8.2", + "php": ">=8.3", "ext-json": "*", "guzzlehttp/guzzle": "^7.10", "symfony/service-contracts": "^3.6", diff --git a/composer.lock b/composer.lock index f4bf221..7e6795b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ff1a2e2e927c70f8b7fcc5ead9598a5e", + "content-hash": "5ebd295a107c4a23f59620b48c7c75ba", "packages": [ { "name": "guzzlehttp/guzzle", @@ -5289,7 +5289,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.2", + "php": ">=8.3", "ext-json": "*" }, "platform-dev": {},