From 7de89b586bdf51e17ec2b00e3a4557343b986927 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Mon, 27 Jan 2025 18:49:14 -0500 Subject: [PATCH 1/8] Added Arr and Collection classes from closed PR --- .gitignore | 1 + clover.xml | 1664 ---------------------------------------- docs/DataValidation.md | 26 + 3 files changed, 27 insertions(+), 1664 deletions(-) delete mode 100644 clover.xml create mode 100644 docs/DataValidation.md diff --git a/.gitignore b/.gitignore index 783d717..847bd3e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ vendor/ .idea/ coverage.xml package.xml +clover.xml coverage-html/ .phpunit/ /.php-cs-fixer.cache diff --git a/clover.xml b/clover.xml deleted file mode 100644 index 26f192e..0000000 --- a/clover.xml +++ /dev/null @@ -1,1664 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/DataValidation.md b/docs/DataValidation.md new file mode 100644 index 0000000..a392ef6 --- /dev/null +++ b/docs/DataValidation.md @@ -0,0 +1,26 @@ +Data Validation += + +Sometimes it's necessary to run some validations before creating your `Data` object. +The library provides a way to do this by using a `RuleReferer`. for example: + +```php +use Nuxtifyts\PhpDto\Data; + +final readonly class CoordinatesData extends Data +{ + public function __construct( + private float $latitude, + private float $longitude + ) { + } +} +``` + +This class will automatically add some validation for the `latitude` and `longitude` properties: + +```php +[ + 'latitude' => 'float' +] +``` From 5c57ef4bbb935a4da561896819867aa99f633165 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Mon, 27 Jan 2025 18:49:23 -0500 Subject: [PATCH 2/8] Added Arr and Collection classes from closed PR --- composer.json | 3 +- src/Concerns/BaseData.php | 1 - src/Support/Arr.php | 165 ++++++++++- src/Support/Collection.php | 117 ++++++++ tests/Unit/Support/ArrTest.php | 407 +++++++++++++++++++++++++- tests/Unit/Support/CollectionTest.php | 238 +++++++++++++++ 6 files changed, 924 insertions(+), 7 deletions(-) create mode 100644 src/Support/Collection.php create mode 100644 tests/Unit/Support/CollectionTest.php diff --git a/composer.json b/composer.json index a09f12f..0a5b607 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ } }, "scripts": { - "ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=ci --configuration phpunit.xml", + "ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --testsuite=ci --configuration phpunit.xml", + "unit-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=unit --configuration phpunit.xml", "phpstan": "vendor/bin/phpstan analyse --configuration phpstan.neon --memory-limit=256M" } } diff --git a/src/Concerns/BaseData.php b/src/Concerns/BaseData.php index 2e9b0d1..7d42eeb 100644 --- a/src/Concerns/BaseData.php +++ b/src/Concerns/BaseData.php @@ -9,7 +9,6 @@ use Nuxtifyts\PhpDto\Normalizers\Concerns\HasNormalizers; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipeline; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipelinePassable; -use ReflectionClass; use Throwable; trait BaseData diff --git a/src/Support/Arr.php b/src/Support/Arr.php index 0a83032..9bada21 100644 --- a/src/Support/Arr.php +++ b/src/Support/Arr.php @@ -2,11 +2,13 @@ namespace Nuxtifyts\PhpDto\Support; +use BackedEnum; +use InvalidArgumentException; + final readonly class Arr { /** * @param array $array - * @param string $key * @param array $default * * @return array @@ -30,4 +32,165 @@ public static function isArrayOfClassStrings(array $array, string $classString): && is_subclass_of($value, $classString) ); } + + /** + * @param array $array + */ + public static function getStringOrNull(array $array, string $key): ?string + { + $value = $array[$key] ?? null; + + return is_string($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getString(array $array, string $key, string $default = ''): string + { + return self::getStringOrNull($array, $key) ?? $default; + } + + /** + * @param array $array + */ + public static function getIntegerOrNull(array $array, string $key): ?int + { + $value = $array[$key] ?? null; + + return is_int($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getInteger(array $array, string $key, int $default = 0): int + { + return self::getIntegerOrNull($array, $key) ?? $default; + } + + /** + * @param array $array + */ + public static function getFloatOrNull(array $array, string $key): ?float + { + $value = $array[$key] ?? null; + + return is_float($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getFloat(array $array, string $key, float $default = 0.0): float + { + return self::getFloatOrNull($array, $key) ?? $default; + } + + /** + * @param array $array + */ + public static function getBooleanOrNull(array $array, string $key): ?bool + { + $value = $array[$key] ?? null; + + return is_bool($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getBoolean(array $array, string $key, bool $default = false): bool + { + return self::getBooleanOrNull($array, $key) ?? $default; + } + + /** + * @template T of BackedEnum + * + * @param array $array + * @param class-string $enumClass + * @param ?T $default + * + * @return ?T + */ + public static function getBackedEnumOrNull( + array $array, + string $key, + string $enumClass, + ?BackedEnum $default = null + ): ?BackedEnum { + $value = $array[$key] ?? null; + + if ($value instanceof $enumClass) { + return $value; + } else if ( + (is_string($value) || is_integer($value)) + && $resolvedValue = $enumClass::tryFrom($value) + ) { + return $resolvedValue; + } + + return is_null($default) + ? null + : ($default instanceof $enumClass + ? $default + : throw new InvalidArgumentException('Default value must be an instance of ' . $enumClass) + ); + } + + /** + * @template T of BackedEnum + * + * @param array $array + * @param class-string $enumClass + * @param T $default + * + * @return T + */ + public static function getBackedEnum( + array $array, + string $key, + string $enumClass, + BackedEnum $default + ): BackedEnum { + return self::getBackedEnumOrNull($array, $key, $enumClass, $default) ?? $default; + } + + + /** + * @param array $array + * + * @return ($preserveKeys is true ? array : list) + */ + public static function flatten(array $array, float $depth = INF, bool $preserveKeys = true): array + { + $result = []; + + foreach ($array as $key => $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (! is_array($item)) { + if ($preserveKeys) { + $result[$key] = $item; + } else { + $result[] = $item; + } + } else { + $values = $depth === 1.0 + ? $item + : self::flatten($item, $depth - 1, $preserveKeys); + + foreach ($values as $subKey => $value) { + if ($preserveKeys) { + $result[$subKey] = $value; + } else { + $result[] = $value; + } + } + } + } + + return $result; + } } diff --git a/src/Support/Collection.php b/src/Support/Collection.php new file mode 100644 index 0000000..47369c1 --- /dev/null +++ b/src/Support/Collection.php @@ -0,0 +1,117 @@ + */ + protected array $items = []; + + /** + * @param array $items + */ + public function __construct(array $items = []) + { + $this->items = $items; + } + + /** + * @param TValue $item + * + * @return self + */ + public function push(mixed $item): self + { + $this->items[] = $item; + return $this; + } + + /** + * @param TKey $key + * @param TValue $value + * + * @return self + */ + public function put(mixed $key, mixed $value): self + { + $this->items[$key] = $value; + return $this; + } + + /** + * @param ?callable(TValue $item): bool $callable + * + * @return ?TValue + */ + public function first(?callable $callable = null): mixed + { + return is_null($callable) + ? reset($this->items) ?: null + : array_find($this->items, $callable); + } + + /** + * @template TNewValue of mixed + * @param callable(TValue $item): TNewValue $callable + * + * @return self + */ + public function map(callable $callable): self + { + return new self(array_map($callable, $this->items)); + } + + /** + * @return ($preserveKeys is true ? Collection : Collection) + */ + public function collapse(bool $preserveKeys = false): self + { + return $this->flatten(1, $preserveKeys); + } + + /** + * @return ($preserveKeys is true ? Collection : Collection) + */ + public function flatten(float $depth = INF, bool $preserveKeys = true): self + { + return new self(Arr::flatten($this->items, $depth, $preserveKeys)); + } + + public function isNotEmpty(): bool + { + return !empty($this->items); + } + + public function isEmpty(): bool + { + return !$this->isNotEmpty(); + } + + /** + * @param callable(TValue $item): bool $callable + */ + public function every(callable $callable): bool + { + return array_all($this->items, $callable); + } + + /** + * @param callable(TValue $item): bool $callable + */ + public function some(callable $callable): bool + { + return array_any($this->items, $callable); + } + + /** + * @return array + */ + public function all(): array + { + return $this->items; + } +} diff --git a/tests/Unit/Support/ArrTest.php b/tests/Unit/Support/ArrTest.php index 633b923..59f77d6 100644 --- a/tests/Unit/Support/ArrTest.php +++ b/tests/Unit/Support/ArrTest.php @@ -2,16 +2,22 @@ namespace Nuxtifyts\PhpDto\Tests\Unit\Support; -use Nuxtifyts\PhpDto\Serializers\BackedEnumSerializer; -use Nuxtifyts\PhpDto\Serializers\ScalarTypeSerializer; -use Nuxtifyts\PhpDto\Serializers\Serializer; +use InvalidArgumentException; use Nuxtifyts\PhpDto\Support\Arr; +use PHPUnit\Framework\Attributes\Test; use Nuxtifyts\PhpDto\Tests\Unit\UnitCase; +use PHPUnit\Framework\Attributes\UsesClass; +use Nuxtifyts\PhpDto\Serializers\Serializer; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Test; +use Nuxtifyts\PhpDto\Serializers\BackedEnumSerializer; +use Nuxtifyts\PhpDto\Serializers\ScalarTypeSerializer; +use Nuxtifyts\PhpDto\Tests\Dummies\Enums\YesNoBackedEnum; +use Nuxtifyts\PhpDto\Tests\Dummies\Enums\ColorsBackedEnum; #[CoversClass(Arr::class)] +#[UsesClass(YesNoBackedEnum::class)] +#[UsesClass(ColorsBackedEnum::class)] final class ArrTest extends UnitCase { /** @@ -20,6 +26,7 @@ final class ArrTest extends UnitCase #[Test] #[DataProvider('get_arr_provider')] #[DataProvider('is_array_of_class_strings_provider')] + #[DataProvider('flatten_provider')] public function arr_helper_functions( string $functionName, array $parameters, @@ -62,9 +69,281 @@ public static function get_arr_provider(): array ], [], ], + 'get string existing key, invalid value' => [ + 'getString', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + '', + ], + 'get string existing key, valid value' => [ + 'getString', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 'value', + ], + 'get string non-existing key' => [ + 'getString', + [ + 'array' => ['key' => 'value'], + 'key' => 'nonExistingKey', + ], + '', + ], + 'get string or null existing key, invalid value' => [ + 'getStringOrNull', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + null, + ], + 'get string or null existing key, valid value' => [ + 'getStringOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 'value', + ], + 'get string or null non-existing key' => [ + 'getStringOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get integer existing key, invalid value' => [ + 'getInteger', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 0, + ], + 'get integer existing key, valid value' => [ + 'getInteger', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + 1, + ], + 'get integer non-existing key' => [ + 'getInteger', + [ + 'array' => ['key' => 1], + 'key' => 'nonExistingKey', + ], + 0, + ], + 'get integer or null existing key, invalid value' => [ + 'getIntegerOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + null, + ], + 'get integer or null existing key, valid value' => [ + 'getIntegerOrNull', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + 1, + ], + 'get integer or null non-existing key' => [ + 'getIntegerOrNull', + [ + 'array' => ['key' => 1], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get float existing key, invalid value' => [ + 'getFloat', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 0.0, + ], + 'get float existing key, valid value' => [ + 'getFloat', + [ + 'array' => ['key' => 1.1], + 'key' => 'key', + ], + 1.1, + ], + 'get float non-existing key' => [ + 'getFloat', + [ + 'array' => ['key' => 1.1], + 'key' => 'nonExistingKey', + ], + 0.0, + ], + 'get float or null existing key, invalid value' => [ + 'getFloatOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + null, + ], + 'get float or null existing key, valid value' => [ + 'getFloatOrNull', + [ + 'array' => ['key' => 1.1], + 'key' => 'key', + ], + 1.1, + ], + 'get float or null non-existing key' => [ + 'getFloatOrNull', + [ + 'array' => ['key' => 1.1], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get boolean existing key, invalid value' => [ + 'getBoolean', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + false, + ], + 'get boolean existing key, valid value' => [ + 'getBoolean', + [ + 'array' => ['key' => true], + 'key' => 'key', + ], + true, + ], + 'get boolean non-existing key' => [ + 'getBoolean', + [ + 'array' => ['key' => true], + 'key' => 'nonExistingKey', + ], + false, + ], + 'get boolean or null existing key, invalid value' => [ + 'getBooleanOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + null, + ], + 'get boolean or null existing key, valid value' => [ + 'getBooleanOrNull', + [ + 'array' => ['key' => true], + 'key' => 'key', + ], + true, + ], + 'get boolean or null non-existing key' => [ + 'getBooleanOrNull', + [ + 'array' => ['key' => true], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get backed enum or null, invalid value' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => 'invalid'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + ], + null + ], + 'get backed enum or null, invalid value default provided' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => 'invalid'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::NO + ], + 'get backed enum or null, valid backed enum value' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => YesNoBackedEnum::YES], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + ], + YesNoBackedEnum::YES + ], + 'get backed enum or null, valid string value' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => 'yes'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + ], + YesNoBackedEnum::YES + ], + 'get backed enum, invalid value' => [ + 'getBackedEnum', + [ + 'array' => ['key' => 'invalid'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::NO + ], + 'get backed enum, valid backed enum value' => [ + 'getBackedEnum', + [ + 'array' => ['key' => YesNoBackedEnum::YES], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::YES + ], + 'get backed enum, valid string value' => [ + 'getBackedEnum', + [ + 'array' => ['key' => 'yes'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::YES + ], ]; } + #[Test] + public function get_backed_enum_or_null_will_throw_an_exception_if_default_value_is_invalid(): void + { + self::expectException(InvalidArgumentException::class); + + Arr::getBackedEnumOrNull( + ['key' => 'invalid'], + 'key', + YesNoBackedEnum::class, + ColorsBackedEnum::RED + ); + } + /** * @return array */ @@ -96,4 +375,124 @@ public static function is_array_of_class_strings_provider(): array ], ]; } + + /** + * @return array + */ + public static function flatten_provider(): array + { + return [ + 'flatten, empty array' => [ + 'flatten', + [ + 'array' => [], + ], + [] + ], + 'flatten array, one depth' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => 1.1, + 'a2' => 1.2 + ], + 'b' => 2, + ], + ], + [ + 'a1' => 1.1, + 'a2' => 1.2, + 'b' => 2 + ] + ], + 'flatten array, multiple depths' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 'a2' => 2 + ], + 'b' => 3, + ], + ], + [ + 'a1.1' => 1.1, + 'a1.2' => 1.2, + 'a2' => 2, + 'b' => 3 + ] + ], + 'flatten array, and resets array keys' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => 1.1, + 'a2' => 1.2 + ], + 'b' => 2, + ], + 'depth' => 1.0, + 'preserveKeys' => false, + ], + [ + 1.1, + 1.2, + 2 + ] + ], + 'flatten array, and resets array keys, multiple depths' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 'a2' => 2 + ], + 'b' => 3, + ], + 'preserveKeys' => false, + ], + [ + 1.1, + 1.2, + 2, + 3 + ] + ], + 'flatten array, one depth, not enough' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 'a2' => 2 + ], + 'b' => 3, + ], + 'depth' => 1, + 'preserveKeys' => false, + ], + [ + [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 2, + 3 + ] + ] + ]; + } } diff --git a/tests/Unit/Support/CollectionTest.php b/tests/Unit/Support/CollectionTest.php new file mode 100644 index 0000000..9039364 --- /dev/null +++ b/tests/Unit/Support/CollectionTest.php @@ -0,0 +1,238 @@ + $collection + * @param array $functionParams + */ + #[Test] + #[DataProvider('push_function_provider')] + #[DataProvider('put_function_provider')] + #[DataProvider('first_function_provider')] + #[DataProvider('map_function_provider')] + #[DataProvider('collapse_function_provider')] + #[DataProvider('flatten_function_provider')] + #[DataProvider('all_function_provider')] + #[DataProvider('validation_functions_provider')] + public function will_be_able_to_perform_functions( + Collection $collection, + string $functionName, + array $functionParams, + mixed $expected + ): void { + $result = $collection->{$functionName}(...$functionParams); + + if ($expected instanceof Collection) { + self::assertInstanceOf(Collection::class, $result); + self::assertCollection($result, $expected); + } else { + self::assertEquals($expected, $result); + } + } + + /** + * @return array + */ + public static function push_function_provider(): array + { + return [ + 'push' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'push', + 'functionParams' => [ 'item' => 4 ], + 'expected' => new Collection([1, 2, 3, 4]) + ] + ]; + } + + /** + * @return array + */ + public static function put_function_provider(): array + { + return [ + 'put in new key' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'put', + 'functionParams' => [ 'key' => 3, 'value' => 4 ], + 'expected' => new Collection([1, 2, 3, 4]) + ], + 'put in existing key will override value' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'put', + 'functionParams' => [ 'key' => 0, 'value' => 4 ], + 'expected' => new Collection([4, 2, 3]) + ] + ]; + } + + /** + * @return array + */ + public static function first_function_provider(): array + { + return [ + 'first without callable and non empty collection' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'first', + 'functionParams' => [], + 'expected' => 1 + ], + 'first without callable and empty collection' => [ + 'collection' => new Collection([]), + 'functionName' => 'first', + 'functionParams' => [], + 'expected' => null + ], + 'first with callable and existing item that will meet requirements' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'first', + 'functionParams' => [ 'callable' => static fn (int $item) => $item === 2 ], + 'expected' => 2 + ], + 'first with callable and no item that will meet requirements' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'first', + 'functionParams' => [ 'callable' => static fn (int $item) => $item === 4 ], + 'expected' => null + ] + ]; + } + + /** + * @return array + */ + public static function map_function_provider(): array + { + return [ + 'map' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'map', + 'functionParams' => [ 'callable' => static fn (int $item) => $item * 2 ], + 'expected' => new Collection([2, 4, 6]) + ] + ]; + } + + /** + * @return array + */ + public static function collapse_function_provider(): array + { + return [ + 'collapse' => [ + 'collection' => new Collection([ + new Collection([ 'a' => 1, 2, 3]), + new Collection([4, 5, 6]), + new Collection([7, 8, 9]) + ]), + 'functionName' => 'collapse', + 'functionParams' => [], + 'expected' => new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9]) + ], + + ]; + } + + /** + * @return array + */ + public static function flatten_function_provider(): array + { + return [ + 'flatten' => [ + 'collection' => new Collection([ + 'a1' => new Collection([ + 'a1.1' => 1.1, + 'a1.2' => new Collection([ + 'a1.2.1' => 1.21, + 'a1.2.2' => 1.22, + 'a1.2.3' => new Collection([ + 'a1.2.3.1' => 1.231, + 'a1.2.3.2' => 1.232, + 'a1.2.3.3' => 1.233 + ]) + ]) + ]) + ]), + 'functionName' => 'flatten', + 'functionParams' => [], + 'expected' => new Collection([ + 'a1.1' => 1.1, + 'a1.2.1' => 1.21, + 'a1.2.2' => 1.22, + 'a1.2.3.1' => 1.231, + 'a1.2.3.2' => 1.232, + 'a1.2.3.3' => 1.233 + ]) + ] + ]; + } + + /** + * @return array + */ + public static function all_function_provider(): array + { + return [ + 'all' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'all', + 'functionParams' => [], + 'expected' => [1, 2, 3] + ] + ]; + } + + /** + * @return array + */ + public static function validation_functions_provider(): array + { + return [ + 'isEmpty' => [ + 'collection' => new Collection([]), + 'functionName' => 'isEmpty', + 'functionParams' => [], + 'expected' => true + ], + 'isNotEmpty' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'isNotEmpty', + 'functionParams' => [], + 'expected' => true + ], + 'every' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'every', + 'functionParams' => [ 'callable' => static fn (int $item) => $item > 0 ], + 'expected' => true + ], + 'some' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'some', + 'functionParams' => [ 'callable' => static fn (int $item) => $item === 2 ], + 'expected' => true + ], + ]; + } + + /** + * @param Collection $collection + * @param Collection $expected + */ + private static function assertCollection(Collection $collection, Collection $expected): void + { + self::assertEquals($expected->all(), $collection->all()); + } +} From 70516d9d7a35f8528310ddb7a331977031b3c965 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Mon, 27 Jan 2025 19:25:10 -0500 Subject: [PATCH 3/8] Added DataConfiguration for Validation --- docs/DataConfiguration.md | 7 +- src/Configuration/DataConfiguration.php | 5 ++ src/Configuration/ValidationConfiguration.php | 64 +++++++++++++++++++ src/Contracts/ValidateableData.php | 34 ++++++++++ src/Exceptions/DataConfigurationException.php | 6 ++ src/Exceptions/DataValidationException.php | 9 +++ .../Validation/Contracts/DataValidator.php | 17 +++++ .../Validation/Contracts/RuleReferer.php | 18 ++++++ .../Validation/ValidationRulesReferer.php | 22 +++++++ src/Support/Validation/Validator.php | 19 ++++++ tests/Unit/DataConfigurationTest.php | 48 ++++++++++++++ 11 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 src/Configuration/ValidationConfiguration.php create mode 100644 src/Contracts/ValidateableData.php create mode 100644 src/Exceptions/DataValidationException.php create mode 100644 src/Support/Validation/Contracts/DataValidator.php create mode 100644 src/Support/Validation/Contracts/RuleReferer.php create mode 100644 src/Support/Validation/ValidationRulesReferer.php create mode 100644 src/Support/Validation/Validator.php diff --git a/docs/DataConfiguration.md b/docs/DataConfiguration.md index 2e73108..aa63b34 100644 --- a/docs/DataConfiguration.md +++ b/docs/DataConfiguration.md @@ -38,6 +38,11 @@ DataConfiguration::getInstance([ BackedEnumSerializer::class, ScalarTypeSerializer::class, ] - ] + ], + + 'validation' => [ + 'ruleReferer' => ValidationRulesReferer::class, + 'validator' => Validator::class, + ] ]) ``` diff --git a/src/Configuration/DataConfiguration.php b/src/Configuration/DataConfiguration.php index cfc2046..62cf51c 100644 --- a/src/Configuration/DataConfiguration.php +++ b/src/Configuration/DataConfiguration.php @@ -12,6 +12,7 @@ class DataConfiguration implements Configuration protected function __construct( protected(set) NormalizersConfiguration $normalizers, protected(set) SerializersConfiguration $serializers, + protected(set) ValidationConfiguration $validation, ) { } @@ -36,6 +37,10 @@ public static function getInstance( serializers: SerializersConfiguration::getInstance( Arr::getArray($config ?? [], 'serializers'), $forceCreate + ), + validation: ValidationConfiguration::getInstance( + Arr::getArray($config ?? [], 'validation'), + $forceCreate ) ); } diff --git a/src/Configuration/ValidationConfiguration.php b/src/Configuration/ValidationConfiguration.php new file mode 100644 index 0000000..0cd49c4 --- /dev/null +++ b/src/Configuration/ValidationConfiguration.php @@ -0,0 +1,64 @@ + $validatorClass + * @param class-string $validationRulesRefererClass + */ + protected function __construct( + protected(set) string $validatorClass, + protected(set) string $validationRulesRefererClass + ) { + } + + /** + * @param ?array $config + * + * @throws DataConfigurationException + */ + public static function getInstance( + ?array $config = null, + bool $forceCreate = false + ): self { + if (self::$instance && !$forceCreate) { + return self::$instance; + } + + $validatorClass = Arr::getString($config ?? [], 'validator', Validator::class); + + $validationRulesRefererClass = Arr::getString( + $config ?? [], + 'ruleReferer', + ValidationRulesReferer::class + ); + + if ( + !$validatorClass + || !class_exists($validatorClass) + || !is_subclass_of($validatorClass, DataValidator::class) + + || !$validationRulesRefererClass + || !class_exists($validationRulesRefererClass) + || !is_subclass_of($validationRulesRefererClass, RuleReferer::class) + ) { + throw DataConfigurationException::invalidValidationClasses(); + } + + return self::$instance = new self( + validatorClass: $validatorClass, + validationRulesRefererClass: $validationRulesRefererClass + ); + } +} diff --git a/src/Contracts/ValidateableData.php b/src/Contracts/ValidateableData.php new file mode 100644 index 0000000..a465039 --- /dev/null +++ b/src/Contracts/ValidateableData.php @@ -0,0 +1,34 @@ + $data + * + * @throws DataValidationException + */ + public static function validate(array $data): void; + + /** + * @param array $data + * + * @throws DataValidationException + */ + public static function validateAndCreate(array $data): static; + + /** + * @return true|array> + */ + public function isValid(): true|array; + + /** + * @param ?array $data + * + * @return array + */ + public static function validationRules(?array $data = null): array; +} diff --git a/src/Exceptions/DataConfigurationException.php b/src/Exceptions/DataConfigurationException.php index e29841e..16b6c6c 100644 --- a/src/Exceptions/DataConfigurationException.php +++ b/src/Exceptions/DataConfigurationException.php @@ -9,6 +9,7 @@ class DataConfigurationException extends Exception protected const int INVALID_BASE_SERIALIZERS = 10000; protected const int INVALID_BASE_NORMALIZERS = 20000; + protected const int INVALID_VALIDATION_CLASSES = 30000; public static function invalidBaseSerializers(): self { @@ -19,4 +20,9 @@ public static function invalidBaseNormalizers(): self { return new self('Invalid base normalizers', self::INVALID_BASE_NORMALIZERS); } + + public static function invalidValidationClasses(): self + { + return new self('Invalid validation classes', self::INVALID_VALIDATION_CLASSES); + } } diff --git a/src/Exceptions/DataValidationException.php b/src/Exceptions/DataValidationException.php new file mode 100644 index 0000000..269a395 --- /dev/null +++ b/src/Exceptions/DataValidationException.php @@ -0,0 +1,9 @@ + $value + * + * @return array + * + * @throws Throwable + */ + public static function validate(array $value): array; +} diff --git a/src/Support/Validation/Contracts/RuleReferer.php b/src/Support/Validation/Contracts/RuleReferer.php new file mode 100644 index 0000000..0e512bb --- /dev/null +++ b/src/Support/Validation/Contracts/RuleReferer.php @@ -0,0 +1,18 @@ + $classContext + * + * @return list|array + */ + public static function getRulesFromClassContext(ClassContext $classContext): array; +} diff --git a/src/Support/Validation/ValidationRulesReferer.php b/src/Support/Validation/ValidationRulesReferer.php new file mode 100644 index 0000000..94f26f7 --- /dev/null +++ b/src/Support/Validation/ValidationRulesReferer.php @@ -0,0 +1,22 @@ + $classContext + * + * @return list|array + */ + public static function getRulesFromClassContext(ClassContext $classContext): array + { + return []; // Rule referer should be replaced from config. + } +} diff --git a/src/Support/Validation/Validator.php b/src/Support/Validation/Validator.php new file mode 100644 index 0000000..d033a41 --- /dev/null +++ b/src/Support/Validation/Validator.php @@ -0,0 +1,19 @@ + $value + * + * @return array + * + */ + public static function validate(array $value): array + { + return $value; // Validator should be replaced from config. + } +} diff --git a/tests/Unit/DataConfigurationTest.php b/tests/Unit/DataConfigurationTest.php index 9be82eb..61a00ad 100644 --- a/tests/Unit/DataConfigurationTest.php +++ b/tests/Unit/DataConfigurationTest.php @@ -5,16 +5,23 @@ use Nuxtifyts\PhpDto\Configuration\DataConfiguration; use Nuxtifyts\PhpDto\Configuration\NormalizersConfiguration; use Nuxtifyts\PhpDto\Configuration\SerializersConfiguration; +use Nuxtifyts\PhpDto\Configuration\ValidationConfiguration; use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; use Nuxtifyts\PhpDto\Normalizers\ArrayNormalizer; +use Nuxtifyts\PhpDto\Support\Validation\ValidationRulesReferer; +use Nuxtifyts\PhpDto\Support\Validation\Validator; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\UsesClass; use Throwable; #[CoversClass(NormalizersConfiguration::class)] #[CoversClass(SerializersConfiguration::class)] +#[CoversClass(ValidationConfiguration::class)] #[CoversClass(DataConfiguration::class)] #[CoversClass(DataConfigurationException::class)] +#[UsesClass(Validator::class)] +#[UsesClass(ValidationRulesReferer::class)] final class DataConfigurationTest extends UnitCase { /** @@ -98,4 +105,45 @@ public function will_throw_an_exception_if_invalid_serializers_are_provided(): v ], ], forceCreate: true); } + + /** + * @throws Throwable + */ + #[Test] + public function will_throw_an_exception_if_invalid_validator_is_provided(): void + { + self::expectException(DataConfigurationException::class); + + DataConfiguration::getInstance([ + 'validation' => [ + 'validator' => 'invalidValidator', + ] + ], forceCreate: true); + } + + /** + * @throws Throwable + */ + #[Test] + public function will_throw_an_exception_if_invalid_rule_referer_is_provided(): void + { + self::expectException(DataConfigurationException::class); + + DataConfiguration::getInstance([ + 'validation' => [ + 'ruleReferer' => 'invalidRuleReferer' + ] + ], forceCreate: true); + } + + /** + * @throws Throwable + */ + public function will_provide_validation_configuration(): void + { + $config = DataConfiguration::getInstance(forceCreate: true); + + self::assertTrue($config->validation->validatorClass === Validator::class); + self::assertTrue($config->validation->validationRulesRefererClass === ValidationRulesReferer::class); + } } From 8fcea397097cd03e25a881a7853edbc3981da548 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Sat, 1 Feb 2025 10:54:23 -0500 Subject: [PATCH 4/8] Added non finished approach to validation (will be abandoned) --- docs/DataValidation.md | 26 ------- src/Concerns/ValidateableData.php | 78 +++++++++++++++++++ src/Contracts/ValidateableData.php | 11 +-- src/Data.php | 6 +- src/Exceptions/DataValidationException.php | 7 ++ .../Validation/Contracts/DataValidator.php | 5 ++ .../Validation/Contracts/RuleReferer.php | 2 + .../Validation/ValidationRulesReferer.php | 5 ++ src/Support/Validation/Validator.php | 8 ++ 9 files changed, 113 insertions(+), 35 deletions(-) delete mode 100644 docs/DataValidation.md create mode 100644 src/Concerns/ValidateableData.php diff --git a/docs/DataValidation.md b/docs/DataValidation.md deleted file mode 100644 index a392ef6..0000000 --- a/docs/DataValidation.md +++ /dev/null @@ -1,26 +0,0 @@ -Data Validation -= - -Sometimes it's necessary to run some validations before creating your `Data` object. -The library provides a way to do this by using a `RuleReferer`. for example: - -```php -use Nuxtifyts\PhpDto\Data; - -final readonly class CoordinatesData extends Data -{ - public function __construct( - private float $latitude, - private float $longitude - ) { - } -} -``` - -This class will automatically add some validation for the `latitude` and `longitude` properties: - -```php -[ - 'latitude' => 'float' -] -``` diff --git a/src/Concerns/ValidateableData.php b/src/Concerns/ValidateableData.php new file mode 100644 index 0000000..bbf7785 --- /dev/null +++ b/src/Concerns/ValidateableData.php @@ -0,0 +1,78 @@ +validation; + + $validationRules = $validationConfig->validationRulesRefererClass::createInstance() + ->getRulesFromClassContext(ClassContext::getInstance(static::class)); + + $validationConfig + ->validatorClass::createInstance($validationRules) + ->validate($data); + } catch (Throwable $t) { + throw DataValidationException::invalidData($t->getMessage(), previous: $t); + } + } + + /** + * @param array $data + * + * @throws DataValidationException + */ + public static function validateAndCreate(array $data): static + { + try { + /** @var ClassContext $context */ + $context = ClassContext::getInstance(static::class); + + $value = static::normalizeValue($args, static::class, $context->normalizers) + ?: static::normalizeValue($args[0] ?? [], static::class, $context->normalizers); + + if ($value === false) { + throw DataCreationException::invalidParamsPassed(static::class); + } + + // Add Validate Data pipe + } catch (Throwable $t) { + throw DataValidationException::invalidData($t->getMessage(), previous: $t); + } + } + + public function isValid(): bool + { + try { + $this::validate($this->toArray()); + + return true; + } catch (Exception) { + return false; + } + } + + /** + * @param ?array $data + * + * @return array + */ + public static function validationRules(?array $data = null): array + { + return []; + } +} diff --git a/src/Contracts/ValidateableData.php b/src/Contracts/ValidateableData.php index a465039..76e83c6 100644 --- a/src/Contracts/ValidateableData.php +++ b/src/Contracts/ValidateableData.php @@ -11,19 +11,14 @@ interface ValidateableData * * @throws DataValidationException */ - public static function validate(array $data): void; + public static function validate(mixed $data): void; /** - * @param array $data - * * @throws DataValidationException */ - public static function validateAndCreate(array $data): static; + public static function validateAndCreate(mixed $data): static; - /** - * @return true|array> - */ - public function isValid(): true|array; + public function isValid(): bool; /** * @param ?array $data diff --git a/src/Data.php b/src/Data.php index 0b234b9..92fe925 100644 --- a/src/Data.php +++ b/src/Data.php @@ -5,16 +5,20 @@ use Nuxtifyts\PhpDto\Contracts\BaseData as BaseDataContract; use Nuxtifyts\PhpDto\Contracts\EmptyData as EmptyDataContract; use Nuxtifyts\PhpDto\Contracts\CloneableData as CloneableDataContract; +use Nuxtifyts\PhpDto\Contracts\ValidateableData as ValidateableDataContract; use Nuxtifyts\PhpDto\Concerns\BaseData; use Nuxtifyts\PhpDto\Concerns\EmptyData; use Nuxtifyts\PhpDto\Concerns\CloneableData; +use Nuxtifyts\PhpDto\Concerns\ValidateableData; abstract readonly class Data implements BaseDataContract, EmptyDataContract, - CloneableDataContract + CloneableDataContract, + ValidateableDataContract { use BaseData; use EmptyData; use CloneableData; + use ValidateableData; } diff --git a/src/Exceptions/DataValidationException.php b/src/Exceptions/DataValidationException.php index 269a395..fbbaf37 100644 --- a/src/Exceptions/DataValidationException.php +++ b/src/Exceptions/DataValidationException.php @@ -3,7 +3,14 @@ namespace Nuxtifyts\PhpDto\Exceptions; use Exception; +use Throwable; class DataValidationException extends Exception { + protected const int INVALID_DATA_CODE = 0; + + public static function invalidData(string $message, int $code = self::INVALID_DATA_CODE, ?Throwable $previous = null): self + { + return new self($message, $code, $previous); + } } diff --git a/src/Support/Validation/Contracts/DataValidator.php b/src/Support/Validation/Contracts/DataValidator.php index 217e11c..37a390f 100644 --- a/src/Support/Validation/Contracts/DataValidator.php +++ b/src/Support/Validation/Contracts/DataValidator.php @@ -6,6 +6,11 @@ interface DataValidator { + /** + * @param array $rules + */ + public static function createInstance(array $rules): self; + /** * @param array $value * diff --git a/src/Support/Validation/Contracts/RuleReferer.php b/src/Support/Validation/Contracts/RuleReferer.php index 0e512bb..5ec2683 100644 --- a/src/Support/Validation/Contracts/RuleReferer.php +++ b/src/Support/Validation/Contracts/RuleReferer.php @@ -7,6 +7,8 @@ interface RuleReferer { + public static function createInstance(): self; + /** * @template T of Data * diff --git a/src/Support/Validation/ValidationRulesReferer.php b/src/Support/Validation/ValidationRulesReferer.php index 94f26f7..4852978 100644 --- a/src/Support/Validation/ValidationRulesReferer.php +++ b/src/Support/Validation/ValidationRulesReferer.php @@ -8,6 +8,11 @@ final readonly class ValidationRulesReferer implements RuleReferer { + public static function createInstance(mixed ...$args): self + { + return new self(); + } + /** * @template T of Data * diff --git a/src/Support/Validation/Validator.php b/src/Support/Validation/Validator.php index d033a41..0307527 100644 --- a/src/Support/Validation/Validator.php +++ b/src/Support/Validation/Validator.php @@ -6,6 +6,14 @@ final readonly class Validator implements DataValidator { + /** + * @param array $rules + */ + public static function createInstance(array $rules): self + { + return new self(); + } + /** * @param array $value * From 98d01edd6f3d4d5a6cc73d35886a0a9bf3a95c89 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Sat, 1 Feb 2025 10:56:32 -0500 Subject: [PATCH 5/8] Validation cleanup --- docs/DataConfiguration.md | 5 -- src/Concerns/ValidateableData.php | 78 ------------------- src/Configuration/DataConfiguration.php | 5 -- src/Configuration/ValidationConfiguration.php | 64 --------------- src/Contracts/ValidateableData.php | 29 ------- src/Data.php | 6 +- .../Validation/Contracts/DataValidator.php | 22 ------ .../Validation/Contracts/RuleReferer.php | 20 ----- .../Validation/ValidationRulesReferer.php | 27 ------- src/Support/Validation/Validator.php | 27 ------- 10 files changed, 1 insertion(+), 282 deletions(-) delete mode 100644 src/Concerns/ValidateableData.php delete mode 100644 src/Configuration/ValidationConfiguration.php delete mode 100644 src/Contracts/ValidateableData.php delete mode 100644 src/Support/Validation/Contracts/DataValidator.php delete mode 100644 src/Support/Validation/Contracts/RuleReferer.php delete mode 100644 src/Support/Validation/ValidationRulesReferer.php delete mode 100644 src/Support/Validation/Validator.php diff --git a/docs/DataConfiguration.md b/docs/DataConfiguration.md index aa63b34..498756d 100644 --- a/docs/DataConfiguration.md +++ b/docs/DataConfiguration.md @@ -39,10 +39,5 @@ DataConfiguration::getInstance([ ScalarTypeSerializer::class, ] ], - - 'validation' => [ - 'ruleReferer' => ValidationRulesReferer::class, - 'validator' => Validator::class, - ] ]) ``` diff --git a/src/Concerns/ValidateableData.php b/src/Concerns/ValidateableData.php deleted file mode 100644 index bbf7785..0000000 --- a/src/Concerns/ValidateableData.php +++ /dev/null @@ -1,78 +0,0 @@ -validation; - - $validationRules = $validationConfig->validationRulesRefererClass::createInstance() - ->getRulesFromClassContext(ClassContext::getInstance(static::class)); - - $validationConfig - ->validatorClass::createInstance($validationRules) - ->validate($data); - } catch (Throwable $t) { - throw DataValidationException::invalidData($t->getMessage(), previous: $t); - } - } - - /** - * @param array $data - * - * @throws DataValidationException - */ - public static function validateAndCreate(array $data): static - { - try { - /** @var ClassContext $context */ - $context = ClassContext::getInstance(static::class); - - $value = static::normalizeValue($args, static::class, $context->normalizers) - ?: static::normalizeValue($args[0] ?? [], static::class, $context->normalizers); - - if ($value === false) { - throw DataCreationException::invalidParamsPassed(static::class); - } - - // Add Validate Data pipe - } catch (Throwable $t) { - throw DataValidationException::invalidData($t->getMessage(), previous: $t); - } - } - - public function isValid(): bool - { - try { - $this::validate($this->toArray()); - - return true; - } catch (Exception) { - return false; - } - } - - /** - * @param ?array $data - * - * @return array - */ - public static function validationRules(?array $data = null): array - { - return []; - } -} diff --git a/src/Configuration/DataConfiguration.php b/src/Configuration/DataConfiguration.php index 62cf51c..3fd2221 100644 --- a/src/Configuration/DataConfiguration.php +++ b/src/Configuration/DataConfiguration.php @@ -12,7 +12,6 @@ class DataConfiguration implements Configuration protected function __construct( protected(set) NormalizersConfiguration $normalizers, protected(set) SerializersConfiguration $serializers, - protected(set) ValidationConfiguration $validation, ) { } @@ -38,10 +37,6 @@ public static function getInstance( Arr::getArray($config ?? [], 'serializers'), $forceCreate ), - validation: ValidationConfiguration::getInstance( - Arr::getArray($config ?? [], 'validation'), - $forceCreate - ) ); } } diff --git a/src/Configuration/ValidationConfiguration.php b/src/Configuration/ValidationConfiguration.php deleted file mode 100644 index 0cd49c4..0000000 --- a/src/Configuration/ValidationConfiguration.php +++ /dev/null @@ -1,64 +0,0 @@ - $validatorClass - * @param class-string $validationRulesRefererClass - */ - protected function __construct( - protected(set) string $validatorClass, - protected(set) string $validationRulesRefererClass - ) { - } - - /** - * @param ?array $config - * - * @throws DataConfigurationException - */ - public static function getInstance( - ?array $config = null, - bool $forceCreate = false - ): self { - if (self::$instance && !$forceCreate) { - return self::$instance; - } - - $validatorClass = Arr::getString($config ?? [], 'validator', Validator::class); - - $validationRulesRefererClass = Arr::getString( - $config ?? [], - 'ruleReferer', - ValidationRulesReferer::class - ); - - if ( - !$validatorClass - || !class_exists($validatorClass) - || !is_subclass_of($validatorClass, DataValidator::class) - - || !$validationRulesRefererClass - || !class_exists($validationRulesRefererClass) - || !is_subclass_of($validationRulesRefererClass, RuleReferer::class) - ) { - throw DataConfigurationException::invalidValidationClasses(); - } - - return self::$instance = new self( - validatorClass: $validatorClass, - validationRulesRefererClass: $validationRulesRefererClass - ); - } -} diff --git a/src/Contracts/ValidateableData.php b/src/Contracts/ValidateableData.php deleted file mode 100644 index 76e83c6..0000000 --- a/src/Contracts/ValidateableData.php +++ /dev/null @@ -1,29 +0,0 @@ - $data - * - * @throws DataValidationException - */ - public static function validate(mixed $data): void; - - /** - * @throws DataValidationException - */ - public static function validateAndCreate(mixed $data): static; - - public function isValid(): bool; - - /** - * @param ?array $data - * - * @return array - */ - public static function validationRules(?array $data = null): array; -} diff --git a/src/Data.php b/src/Data.php index 92fe925..0b234b9 100644 --- a/src/Data.php +++ b/src/Data.php @@ -5,20 +5,16 @@ use Nuxtifyts\PhpDto\Contracts\BaseData as BaseDataContract; use Nuxtifyts\PhpDto\Contracts\EmptyData as EmptyDataContract; use Nuxtifyts\PhpDto\Contracts\CloneableData as CloneableDataContract; -use Nuxtifyts\PhpDto\Contracts\ValidateableData as ValidateableDataContract; use Nuxtifyts\PhpDto\Concerns\BaseData; use Nuxtifyts\PhpDto\Concerns\EmptyData; use Nuxtifyts\PhpDto\Concerns\CloneableData; -use Nuxtifyts\PhpDto\Concerns\ValidateableData; abstract readonly class Data implements BaseDataContract, EmptyDataContract, - CloneableDataContract, - ValidateableDataContract + CloneableDataContract { use BaseData; use EmptyData; use CloneableData; - use ValidateableData; } diff --git a/src/Support/Validation/Contracts/DataValidator.php b/src/Support/Validation/Contracts/DataValidator.php deleted file mode 100644 index 37a390f..0000000 --- a/src/Support/Validation/Contracts/DataValidator.php +++ /dev/null @@ -1,22 +0,0 @@ - $rules - */ - public static function createInstance(array $rules): self; - - /** - * @param array $value - * - * @return array - * - * @throws Throwable - */ - public static function validate(array $value): array; -} diff --git a/src/Support/Validation/Contracts/RuleReferer.php b/src/Support/Validation/Contracts/RuleReferer.php deleted file mode 100644 index 5ec2683..0000000 --- a/src/Support/Validation/Contracts/RuleReferer.php +++ /dev/null @@ -1,20 +0,0 @@ - $classContext - * - * @return list|array - */ - public static function getRulesFromClassContext(ClassContext $classContext): array; -} diff --git a/src/Support/Validation/ValidationRulesReferer.php b/src/Support/Validation/ValidationRulesReferer.php deleted file mode 100644 index 4852978..0000000 --- a/src/Support/Validation/ValidationRulesReferer.php +++ /dev/null @@ -1,27 +0,0 @@ - $classContext - * - * @return list|array - */ - public static function getRulesFromClassContext(ClassContext $classContext): array - { - return []; // Rule referer should be replaced from config. - } -} diff --git a/src/Support/Validation/Validator.php b/src/Support/Validation/Validator.php deleted file mode 100644 index 0307527..0000000 --- a/src/Support/Validation/Validator.php +++ /dev/null @@ -1,27 +0,0 @@ - $rules - */ - public static function createInstance(array $rules): self - { - return new self(); - } - - /** - * @param array $value - * - * @return array - * - */ - public static function validate(array $value): array - { - return $value; // Validator should be replaced from config. - } -} From b5e5bfa7e1fb1a36d2ea66e6497c015e037839a1 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Sat, 1 Feb 2025 10:57:36 -0500 Subject: [PATCH 6/8] Updated README.md --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 917badc..8bc6dbe 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ final readonly class UserData extends Data public function __construct( public string $firstName, #[Aliases('familyName')] - public stirng $lastName + public string $lastName ) { $this->fullName = "$this->firstName $this->lastName"; } @@ -67,10 +67,6 @@ This package was inspired from the [spatie/data-transfer-object](https://github. The main thing that I tried to focus on when creating this package is to make it outside of Laravel ecosystem, meaning: no dependency on [illuminate/support](https://github.com/illuminate/support). -**In no way** I am trying to compare this package with the original one, -Clearly, the original package is more advanced and has more features than this one, -and if you are using Laravel, I highly recommend using the original package instead of this one. - ### Requirements - PHP 8.4 or higher From 5db817cc7aefbef82f57694d0e33ff9d6e2b9006 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Sat, 1 Feb 2025 11:00:38 -0500 Subject: [PATCH 7/8] Reverted validation changes take 2 --- src/Exceptions/DataConfigurationException.php | 6 --- src/Exceptions/DataValidationException.php | 16 ------- tests/Unit/DataConfigurationTest.php | 48 ------------------- 3 files changed, 70 deletions(-) delete mode 100644 src/Exceptions/DataValidationException.php diff --git a/src/Exceptions/DataConfigurationException.php b/src/Exceptions/DataConfigurationException.php index 16b6c6c..e29841e 100644 --- a/src/Exceptions/DataConfigurationException.php +++ b/src/Exceptions/DataConfigurationException.php @@ -9,7 +9,6 @@ class DataConfigurationException extends Exception protected const int INVALID_BASE_SERIALIZERS = 10000; protected const int INVALID_BASE_NORMALIZERS = 20000; - protected const int INVALID_VALIDATION_CLASSES = 30000; public static function invalidBaseSerializers(): self { @@ -20,9 +19,4 @@ public static function invalidBaseNormalizers(): self { return new self('Invalid base normalizers', self::INVALID_BASE_NORMALIZERS); } - - public static function invalidValidationClasses(): self - { - return new self('Invalid validation classes', self::INVALID_VALIDATION_CLASSES); - } } diff --git a/src/Exceptions/DataValidationException.php b/src/Exceptions/DataValidationException.php deleted file mode 100644 index fbbaf37..0000000 --- a/src/Exceptions/DataValidationException.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'validator' => 'invalidValidator', - ] - ], forceCreate: true); - } - - /** - * @throws Throwable - */ - #[Test] - public function will_throw_an_exception_if_invalid_rule_referer_is_provided(): void - { - self::expectException(DataConfigurationException::class); - - DataConfiguration::getInstance([ - 'validation' => [ - 'ruleReferer' => 'invalidRuleReferer' - ] - ], forceCreate: true); - } - - /** - * @throws Throwable - */ - public function will_provide_validation_configuration(): void - { - $config = DataConfiguration::getInstance(forceCreate: true); - - self::assertTrue($config->validation->validatorClass === Validator::class); - self::assertTrue($config->validation->validationRulesRefererClass === ValidationRulesReferer::class); - } } From a0c8d081afb3744417a3345aa6d9f084b19dea96 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Sat, 1 Feb 2025 11:05:12 -0500 Subject: [PATCH 8/8] Updating github actions to v4 --- .github/workflows/php-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index f1abe3c..1b888e8 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -43,7 +43,7 @@ jobs: run: composer run-script ci-test - name: Upload coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-report path: coverage.xml