Skip to content

Commit 35d0084

Browse files
committed
Implement validation framework with Validator, validation rules, and exceptions
1 parent 4dff40b commit 35d0084

16 files changed

+904
-235
lines changed

clover.xml

Lines changed: 336 additions & 232 deletions
Large diffs are not rendered by default.

src/Concerns/BaseData.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use Nuxtifyts\PhpDto\Normalizers\Concerns\HasNormalizers;
1010
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipeline;
1111
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipelinePassable;
12-
use ReflectionClass;
1312
use Throwable;
1413

1514
trait BaseData

src/Concerns/ValidateableData.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Concerns;
4+
5+
use Nuxtifyts\PhpDto\Exceptions\DataValidationException;
6+
7+
trait ValidateableData
8+
{
9+
/**
10+
* @return array<string, mixed>
11+
*/
12+
public static function validationRules(): array
13+
{
14+
return [];
15+
}
16+
}

src/Contexts/ClassContext.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ private function syncClassAttributes(): void
139139
/**
140140
* @throws ReflectionException
141141
*
142-
* @return T
142+
* @return Data
143+
* @phpstan-return T
143144
*/
144145
public function newInstanceWithoutConstructor(): mixed
145146
{
@@ -149,7 +150,8 @@ public function newInstanceWithoutConstructor(): mixed
149150
/**
150151
* @throws ReflectionException
151152
*
152-
* @return T
153+
* @return Data
154+
* @phpstan-return T
153155
*/
154156
public function newInstanceWithConstructorCall(mixed ...$args): mixed
155157
{

src/Contracts/ValidateableData.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Contracts;
4+
5+
use Nuxtifyts\PhpDto\Exceptions\DataValidationException;
6+
7+
interface ValidateableData
8+
{
9+
/**
10+
* @param array<string, mixed> $data
11+
*
12+
* @throws DataValidationException
13+
*/
14+
public static function validate(array $data): void;
15+
16+
/**
17+
* @param array<string, mixed> $data
18+
*
19+
* @throws DataValidationException
20+
*/
21+
public static function validateAndCreate(array $data): static;
22+
23+
/**
24+
* @return true|array<string, array<string>>
25+
*/
26+
public function isValid(): true|array;
27+
28+
/**
29+
* @return array<string, mixed>
30+
*/
31+
public static function validationRules(): array;
32+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Exceptions;
4+
5+
use Exception;
6+
7+
class DataValidationException extends Exception
8+
{
9+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Exceptions;
4+
5+
use Exception;
6+
7+
class ValidationRuleException extends Exception
8+
{
9+
protected const int INVALID_PARAMETERS = 1;
10+
11+
public static function invalidParameters(): self
12+
{
13+
return new self('Invalid parameters', self::INVALID_PARAMETERS);
14+
}
15+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Validation\Rules;
4+
5+
use BackedEnum;
6+
use Nuxtifyts\PhpDto\Exceptions\ValidationRuleException;
7+
use Nuxtifyts\PhpDto\Support\Arr;
8+
9+
class BackedEnumRule implements ValidationRule
10+
{
11+
/** @var class-string<BackedEnum> */
12+
public protected(set) string $backedEnumClass;
13+
14+
/**
15+
* @var ?array<array-key, BackedEnum>
16+
*/
17+
public protected(set) ?array $allowedValues = null;
18+
19+
public string $name {
20+
get {
21+
return 'backed_enum';
22+
}
23+
}
24+
25+
public function evaluate(mixed $value): bool
26+
{
27+
if ($value instanceof $this->backedEnumClass) {
28+
/** @var BackedEnum $value */
29+
$resolvedValue = $value;
30+
} else if (is_string($value) || is_integer($value)) {
31+
$resolvedValue = $this->backedEnumClass::tryFrom($value);
32+
} else {
33+
return false;
34+
}
35+
36+
return !!$resolvedValue
37+
&& (
38+
is_null($this->allowedValues)
39+
|| in_array($resolvedValue->value, array_column($this->allowedValues, 'value'))
40+
);
41+
}
42+
43+
/**
44+
* @param ?array<string, mixed> $parameters
45+
*
46+
* @throws ValidationRuleException
47+
*/
48+
public static function make(?array $parameters = null): self
49+
{
50+
$instance = new self();
51+
52+
$backedEnumClass = $parameters['backedEnumClass'] ?? null;
53+
54+
if (
55+
!is_string($backedEnumClass)
56+
|| !enum_exists($backedEnumClass)
57+
|| !is_subclass_of($backedEnumClass, BackedEnum::class)
58+
) {
59+
throw ValidationRuleException::invalidParameters();
60+
}
61+
62+
$instance->backedEnumClass = $backedEnumClass;
63+
$instance->allowedValues = array_filter(
64+
array_map(static fn (mixed $value) =>
65+
($value instanceof $instance->backedEnumClass)
66+
? $value
67+
: null,
68+
Arr::getArray($parameters ?? [], 'allowedValues')
69+
)
70+
) ?: null;
71+
72+
return $instance;
73+
}
74+
75+
public function validationMessage(): string
76+
{
77+
if ($this->allowedValues) {
78+
$allowedValues = implode(
79+
', ',
80+
array_map(static fn (BackedEnum $value) => $value->value, $this->allowedValues)
81+
);
82+
83+
return "The :attribute field must be one of the following values: $allowedValues.";
84+
} else {
85+
return 'The :attribute field is invalid.';
86+
}
87+
}
88+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Validation\Rules;
4+
5+
class NullableRule implements ValidationRule
6+
{
7+
public string $name {
8+
get {
9+
return 'nullable';
10+
}
11+
}
12+
13+
public function evaluate(mixed $value): bool
14+
{
15+
return true;
16+
}
17+
18+
/**
19+
* @param ?array<string, mixed> $parameters
20+
*/
21+
public static function make(?array $parameters = null): self
22+
{
23+
return new self();
24+
}
25+
26+
public function validationMessage(): string
27+
{
28+
return '';
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Validation\Rules;
4+
5+
class RequiredRule implements ValidationRule
6+
{
7+
public string $name {
8+
get {
9+
return 'required';
10+
}
11+
}
12+
13+
public function evaluate(mixed $value): bool
14+
{
15+
return !empty($value);
16+
}
17+
18+
/**
19+
* @param ?array<string, mixed> $parameters
20+
*/
21+
public static function make(?array $parameters = null): self
22+
{
23+
return new self();
24+
}
25+
26+
public function validationMessage(): string
27+
{
28+
return 'The :attribute field is required.';
29+
}
30+
}

0 commit comments

Comments
 (0)