Skip to content

Feature / data validation #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
35d0084
Implement validation framework with Validator, validation rules, and …
Fa-BRAIK Jan 8, 2025
f841242
Refactor validation rule tests: rename RequireRuleTest to RequiredRul…
Fa-BRAIK Jan 9, 2025
e08f32f
Add DateRule validation and corresponding tests; refactor validation …
Fa-BRAIK Jan 9, 2025
b4242e2
Enhance DateRule to support custom date formats and improve validatio…
Fa-BRAIK Jan 10, 2025
7da3aef
Remove ValidateableData trait as it is no longer needed in the valida…
Fa-BRAIK Jan 10, 2025
66c2544
Add RegexRule validation with evaluation logic and corresponding tests
Fa-BRAIK Jan 10, 2025
44fbdb6
Update .gitignore to include clover.xml and tests/.DS_Store
Fa-BRAIK Jan 10, 2025
5c8e7b5
Add utility methods to retrieve values from arrays with type checks a…
Fa-BRAIK Jan 10, 2025
331b3a1
Refactor RegexRule properties to protected visibility and introduce S…
Fa-BRAIK Jan 10, 2025
638f3fc
Introduce MinMaxValues trait for centralized min/max validation and i…
Fa-BRAIK Jan 11, 2025
33a251d
Add getBackedEnumOrNull method to Arr class for enum retrieval with v…
Fa-BRAIK Jan 11, 2025
4e7d637
Add getBackedEnum method to Arr class for enum retrieval with default…
Fa-BRAIK Jan 11, 2025
8d33d3b
Add NumericRule class for numeric validation with min/max constraints…
Fa-BRAIK Jan 11, 2025
00ae95b
Enhance StringRule to support alpha numeric validation and improve Re…
Fa-BRAIK Jan 11, 2025
8da677c
Use `string` type for class constants
Fa-BRAIK Jan 11, 2025
6a2d0ac
Refactor test structure by organizing Validation rules
Fa-BRAIK Jan 11, 2025
d58acf1
Add EmailRule with validation and tests, update DateRule
Fa-BRAIK Jan 11, 2025
350f7b7
Added Logical rules
Fa-BRAIK Jan 19, 2025
ccd1006
Adjusting LogicalRule to create a new collection when constructed
Fa-BRAIK Jan 19, 2025
b00916d
Adjusted logical rules
Fa-BRAIK Jan 19, 2025
eb94ced
Refactor on validation messages name
Fa-BRAIK Jan 19, 2025
aacb5a0
Adjusted NullableRule
Fa-BRAIK Jan 19, 2025
38be46c
Specifying type of const in StringRule
Fa-BRAIK Jan 19, 2025
22de1a0
Added tests for SingularRule
Fa-BRAIK Jan 19, 2025
ef482c9
Fixing PHPStan
Fa-BRAIK Jan 19, 2025
942ddc9
Added tests for AndRule and OrRule
Fa-BRAIK Jan 19, 2025
a3c610e
Added Array test for flatten function
Fa-BRAIK Jan 19, 2025
13df455
Added Collection test
Fa-BRAIK Jan 19, 2025
604f2a0
PHPStan fixes
Fa-BRAIK Jan 19, 2025
c88db7f
Added unit test and ci test separately
Fa-BRAIK Jan 20, 2025
e0bfd77
Added truthy rule
Fa-BRAIK Jan 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
vendor/
.idea/
coverage.xml
clover.xml
package.xml
coverage-html/
.phpunit/
/.php-cs-fixer.cache
tests/.DS_Store
1,664 changes: 0 additions & 1,664 deletions clover.xml

This file was deleted.

3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
1 change: 0 additions & 1 deletion src/Concerns/BaseData.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/Contexts/ClassContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ private function syncClassAttributes(): void
/**
* @throws ReflectionException
*
* @return T
* @return Data
* @phpstan-return T
*/
public function newInstanceWithoutConstructor(): mixed
{
Expand All @@ -149,7 +150,8 @@ public function newInstanceWithoutConstructor(): mixed
/**
* @throws ReflectionException
*
* @return T
* @return Data
* @phpstan-return T
*/
public function newInstanceWithConstructorCall(mixed ...$args): mixed
{
Expand Down
32 changes: 32 additions & 0 deletions src/Contracts/ValidateableData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Nuxtifyts\PhpDto\Contracts;

use Nuxtifyts\PhpDto\Exceptions\DataValidationException;

interface ValidateableData
{
/**
* @param array<string, mixed> $data
*
* @throws DataValidationException
*/
public static function validate(array $data): void;

/**
* @param array<string, mixed> $data
*
* @throws DataValidationException
*/
public static function validateAndCreate(array $data): static;

/**
* @return true|array<string, array<string>>
*/
public function isValid(): true|array;

/**
* @return array<string, mixed>
*/
public static function validationRules(): array;
}
6 changes: 6 additions & 0 deletions src/Enums/Property/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ enum Type: string
self::BOOLEAN,
self::STRING,
];

/** @var list<Type> */
public const array NUMERIC_TYPES = [
self::FLOAT,
self::INT,
];
}
9 changes: 9 additions & 0 deletions src/Exceptions/DataValidationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Nuxtifyts\PhpDto\Exceptions;

use Exception;

class DataValidationException extends Exception
{
}
15 changes: 15 additions & 0 deletions src/Exceptions/LogicalRuleException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Nuxtifyts\PhpDto\Exceptions;

use Exception;

class LogicalRuleException extends Exception
{
protected const int UNABLE_TO_CREATE_RULE_CODE = 1;

public static function unableToCreateRule(string $message = 'Unable to create rule'): self
{
return new self($message, self::UNABLE_TO_CREATE_RULE_CODE);
}
}
15 changes: 15 additions & 0 deletions src/Exceptions/ValidationRuleException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Nuxtifyts\PhpDto\Exceptions;

use Exception;

class ValidationRuleException extends Exception
{
protected const int INVALID_PARAMETERS = 1;

public static function invalidParameters(): self
{
return new self('Invalid parameters', self::INVALID_PARAMETERS);
}
}
164 changes: 163 additions & 1 deletion src/Support/Arr.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

namespace Nuxtifyts\PhpDto\Support;

use BackedEnum;
use InvalidArgumentException;

final readonly class Arr
{
/**
* @param array<array-key, mixed> $array
* @param string $key
* @param array<array-key, mixed> $default
*
* @return array<array-key, mixed>
Expand All @@ -18,6 +20,130 @@ public static function getArray(array $array, string $key, array $default = []):
return is_array($value) ? $value : $default;
}

/**
* @param array<array-key, mixed> $array
*/
public static function getStringOrNull(array $array, string $key): ?string
{
$value = $array[$key] ?? null;

return is_string($value) ? $value : null;
}

/**
* @param array<array-key, mixed> $array
*/
public static function getString(array $array, string $key, string $default = ''): string
{
return self::getStringOrNull($array, $key) ?? $default;
}

/**
* @param array<array-key, mixed> $array
*/
public static function getIntegerOrNull(array $array, string $key): ?int
{
$value = $array[$key] ?? null;

return is_int($value) ? $value : null;
}

/**
* @param array<array-key, mixed> $array
*/
public static function getInteger(array $array, string $key, int $default = 0): int
{
return self::getIntegerOrNull($array, $key) ?? $default;
}

/**
* @param array<array-key, mixed> $array
*/
public static function getFloatOrNull(array $array, string $key): ?float
{
$value = $array[$key] ?? null;

return is_float($value) ? $value : null;
}

/**
* @param array<array-key, mixed> $array
*/
public static function getFloat(array $array, string $key, float $default = 0.0): float
{
return self::getFloatOrNull($array, $key) ?? $default;
}

/**
* @param array<array-key, mixed> $array
*/
public static function getBooleanOrNull(array $array, string $key): ?bool
{
$value = $array[$key] ?? null;

return is_bool($value) ? $value : null;
}

/**
* @param array<array-key, mixed> $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-key, mixed> $array
* @param class-string<T> $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-key, mixed> $array
* @param class-string<T> $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-key, mixed> $array
* @param class-string<object> $classString
Expand All @@ -30,4 +156,40 @@ public static function isArrayOfClassStrings(array $array, string $classString):
&& is_subclass_of($value, $classString)
);
}

/**
* @param array<array-key, mixed> $array
*
* @return ($preserveKeys is true ? array<array-key, mixed> : list<mixed>)
*/
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;
}
}
Loading
Loading