-
-
Notifications
You must be signed in to change notification settings - Fork 40
/
Copy pathValidatedDTO.php
152 lines (126 loc) · 3.9 KB
/
ValidatedDTO.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<?php
declare(strict_types=1);
namespace WendellAdriel\ValidatedDTO;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use WendellAdriel\ValidatedDTO\Casting\Castable;
use WendellAdriel\ValidatedDTO\Exceptions\CastTargetException;
use WendellAdriel\ValidatedDTO\Exceptions\MissingCastTypeException;
abstract class ValidatedDTO extends SimpleDTO
{
/**
* Defines the validation rules for the DTO.
*/
abstract protected function rules(): array;
/**
* Defines the custom messages for validator errors.
*/
public function messages(): array
{
return [];
}
/**
* Defines the custom attributes for validator errors.
*/
public function attributes(): array
{
return [];
}
/**
* @throws ValidationException|MissingCastTypeException|CastTargetException
*/
public function validate(): void
{
$this->dtoData = $this->buildDataForValidation($this->toArray());
$this->validationPasses()
? $this->passedValidation(true)
: $this->failedValidation();
}
protected function after(\Illuminate\Validation\Validator $validator): void
{
// Do nothing
}
/**
* Builds the validated data from the given data and the rules.
*
* @throws MissingCastTypeException|CastTargetException
*/
protected function validatedData(bool $forceCast = false): array
{
$acceptedKeys = array_keys($this->rulesList());
$result = [];
/** @var array<Castable> $casts */
$casts = $this->buildCasts();
foreach ($this->dtoData as $key => $value) {
if (in_array($key, $acceptedKeys)) {
if (! array_key_exists($key, $casts)) {
if ($this->requireCasting) {
throw new MissingCastTypeException($key);
}
$result[$key] = $value;
continue;
}
$result[$key] = $this->shouldReturnNull($key, $value)
? null
: $this->castValue($casts[$key], $key, $value, $forceCast);
}
}
foreach ($acceptedKeys as $property) {
if (
! array_key_exists($property, $result) &&
$this->isOptionalProperty($property)
) {
$result[$property] = null;
}
}
return $result;
}
protected function isValidData(): bool
{
return $this->lazyValidation || $this->validationPasses();
}
/**
* @throws ValidationException
*/
protected function failedValidation(): void
{
throw new ValidationException($this->validator);
}
protected function shouldReturnNull(string $key, mixed $value): bool
{
return is_null($value) && $this->isOptionalProperty($key);
}
private function validationPasses(): bool
{
$this->validator = Validator::make(
$this->dtoData,
$this->rulesList(),
$this->messagesList(),
$this->attributes()
);
$this->validator->after(fn (\Illuminate\Validation\Validator $validator) => $this->after($validator));
return $this->validator->passes();
}
private function isOptionalProperty(string $property): bool
{
$rules = $this->rulesList();
$propertyRules = is_array($rules[$property])
? $rules[$property]
: explode('|', $rules[$property]);
return in_array('optional', $propertyRules) || in_array('nullable', $propertyRules);
}
private function rulesList(): array
{
return [
...$this->rules(),
...$this->dtoRules,
];
}
private function messagesList(): array
{
return [
...$this->messages(),
...$this->dtoMessages,
];
}
}