Skip to content

Commit 17bd33c

Browse files
Merge pull request #92 from WendellAdriel/feature/lazy-validation
Add lazy validation feature
2 parents 19034b3 + 90911ea commit 17bd33c

File tree

4 files changed

+145
-28
lines changed

4 files changed

+145
-28
lines changed

src/SimpleDTO.php

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,36 @@ abstract class SimpleDTO implements BaseDTO, CastsAttributes
3333
{
3434
use DataResolver, DataTransformer;
3535

36+
public bool $lazyValidation = false;
37+
3638
/** @internal */
3739
protected array $dtoData = [];
3840

41+
/** @internal */
3942
protected array $validatedData = [];
4043

44+
/** @internal */
4145
protected bool $requireCasting = false;
4246

47+
/** @internal */
4348
protected \Illuminate\Contracts\Validation\Validator|\Illuminate\Validation\Validator $validator;
4449

50+
/** @internal */
4551
protected array $dtoRules = [];
4652

53+
/** @internal */
4754
protected array $dtoMessages = [];
4855

56+
/** @internal */
4957
protected array $dtoDefaults = [];
5058

59+
/** @internal */
5160
protected array $dtoCasts = [];
5261

62+
/** @internal */
5363
protected array $dtoMapData = [];
5464

65+
/** @internal */
5566
protected array $dtoMapTransform = [];
5667

5768
/**
@@ -154,9 +165,9 @@ protected function mapToTransform(): array
154165
/**
155166
* @throws MissingCastTypeException|CastTargetException
156167
*/
157-
protected function passedValidation(): void
168+
protected function passedValidation(bool $forceCast = false): void
158169
{
159-
$this->validatedData = $this->validatedData();
170+
$this->validatedData = $this->validatedData($forceCast);
160171
/** @var array<Castable> $casts */
161172
$casts = $this->buildCasts();
162173

@@ -187,7 +198,7 @@ protected function passedValidation(): void
187198

188199
$formatted = $this->shouldReturnNull($key, $value)
189200
? null
190-
: $this->castValue($casts[$key], $key, $value);
201+
: $this->castValue($casts[$key], $key, $value, $forceCast);
191202

192203
$this->{$key} = $formatted;
193204
$this->validatedData[$key] = $formatted;
@@ -212,7 +223,7 @@ protected function isValidData(): bool
212223
*
213224
* @throws MissingCastTypeException|CastTargetException
214225
*/
215-
protected function validatedData(): array
226+
protected function validatedData(bool $forceCast = false): array
216227
{
217228
$acceptedKeys = $this->getAcceptedProperties();
218229
$result = [];
@@ -233,7 +244,7 @@ protected function validatedData(): array
233244

234245
$result[$key] = $this->shouldReturnNull($key, $value)
235246
? null
236-
: $this->castValue($casts[$key], $key, $value);
247+
: $this->castValue($casts[$key], $key, $value, $forceCast);
237248
}
238249
}
239250

@@ -249,8 +260,12 @@ protected function validatedData(): array
249260
/**
250261
* @throws CastTargetException
251262
*/
252-
protected function castValue(mixed $cast, string $key, mixed $value): mixed
263+
protected function castValue(mixed $cast, string $key, mixed $value, bool $forceCast = false): mixed
253264
{
265+
if ($this->lazyValidation && ! $forceCast) {
266+
return $value;
267+
}
268+
254269
if ($cast instanceof Castable) {
255270
return $cast->cast($key, $value);
256271
}
@@ -300,6 +315,16 @@ protected function buildDataForExport(): array
300315
return $this->mapDTOData($mapping, $this->validatedData);
301316
}
302317

318+
protected function buildDataForValidation(array $data): array
319+
{
320+
$mapping = [
321+
...$this->mapData(),
322+
...$this->dtoMapData,
323+
];
324+
325+
return $this->mapDTOData($mapping, $data);
326+
}
327+
303328
private function buildAttributesData(): void
304329
{
305330
$publicProperties = $this->getPublicProperties();
@@ -374,16 +399,6 @@ private function getPropertiesForAttribute(array $properties, string $attribute)
374399
return $result;
375400
}
376401

377-
private function buildDataForValidation(array $data): array
378-
{
379-
$mapping = [
380-
...$this->mapData(),
381-
...$this->dtoMapData,
382-
];
383-
384-
return $this->mapDTOData($mapping, $data);
385-
}
386-
387402
private function mapDTOData(array $mapping, array $data): array
388403
{
389404
$mappedData = [];
@@ -525,6 +540,7 @@ private function isforbiddenProperty(string $property): bool
525540
'dtoCasts',
526541
'dtoMapData',
527542
'dtoMapTransform',
543+
'lazyValidation',
528544
]);
529545
}
530546
}

src/ValidatedDTO.php

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ public function attributes(): array
3333
return [];
3434
}
3535

36+
/**
37+
* @throws ValidationException|MissingCastTypeException|CastTargetException
38+
*/
39+
public function validate(): void
40+
{
41+
$this->dtoData = $this->buildDataForValidation($this->toArray());
42+
43+
$this->validationPasses()
44+
? $this->passedValidation(true)
45+
: $this->failedValidation();
46+
}
47+
3648
protected function after(\Illuminate\Validation\Validator $validator): void
3749
{
3850
// Do nothing
@@ -43,7 +55,7 @@ protected function after(\Illuminate\Validation\Validator $validator): void
4355
*
4456
* @throws MissingCastTypeException|CastTargetException
4557
*/
46-
protected function validatedData(): array
58+
protected function validatedData(bool $forceCast = false): array
4759
{
4860
$acceptedKeys = array_keys($this->rulesList());
4961
$result = [];
@@ -64,7 +76,7 @@ protected function validatedData(): array
6476

6577
$result[$key] = $this->shouldReturnNull($key, $value)
6678
? null
67-
: $this->castValue($casts[$key], $key, $value);
79+
: $this->castValue($casts[$key], $key, $value, $forceCast);
6880
}
6981
}
7082

@@ -82,16 +94,7 @@ protected function validatedData(): array
8294

8395
protected function isValidData(): bool
8496
{
85-
$this->validator = Validator::make(
86-
$this->dtoData,
87-
$this->rulesList(),
88-
$this->messagesList(),
89-
$this->attributes()
90-
);
91-
92-
$this->validator->after(fn (\Illuminate\Validation\Validator $validator) => $this->after($validator));
93-
94-
return $this->validator->passes();
97+
return $this->lazyValidation || $this->validationPasses();
9598
}
9699

97100
/**
@@ -107,6 +110,20 @@ protected function shouldReturnNull(string $key, mixed $value): bool
107110
return is_null($value) && $this->isOptionalProperty($key);
108111
}
109112

113+
private function validationPasses(): bool
114+
{
115+
$this->validator = Validator::make(
116+
$this->dtoData,
117+
$this->rulesList(),
118+
$this->messagesList(),
119+
$this->attributes()
120+
);
121+
122+
$this->validator->after(fn (\Illuminate\Validation\Validator $validator) => $this->after($validator));
123+
124+
return $this->validator->passes();
125+
}
126+
110127
private function isOptionalProperty(string $property): bool
111128
{
112129
$rules = $this->rulesList();

tests/Datasets/LazyDTO.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WendellAdriel\ValidatedDTO\Tests\Datasets;
6+
7+
use WendellAdriel\ValidatedDTO\Casting\IntegerCast;
8+
use WendellAdriel\ValidatedDTO\Casting\StringCast;
9+
use WendellAdriel\ValidatedDTO\ValidatedDTO;
10+
11+
class LazyDTO extends ValidatedDTO
12+
{
13+
public bool $lazyValidation = true;
14+
15+
public ?string $name;
16+
17+
public ?int $age = null;
18+
19+
protected function rules(): array
20+
{
21+
return [
22+
'name' => 'required',
23+
'age' => 'numeric',
24+
];
25+
}
26+
27+
protected function defaults(): array
28+
{
29+
return [];
30+
}
31+
32+
protected function casts(): array
33+
{
34+
return [
35+
'name' => new StringCast(),
36+
'age' => new IntegerCast(),
37+
];
38+
}
39+
}

tests/Unit/LazyValidationTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Validation\ValidationException;
6+
use WendellAdriel\ValidatedDTO\Tests\Datasets\LazyDTO;
7+
use WendellAdriel\ValidatedDTO\ValidatedDTO;
8+
9+
beforeEach(function () {
10+
$this->subject_name = fake()->name;
11+
});
12+
13+
it('instantiates a ValidatedDTO marked as lazy without validating its data', function () {
14+
$validatedDTO = new LazyDTO(['name' => $this->subject_name]);
15+
16+
expect($validatedDTO)->toBeInstanceOf(ValidatedDTO::class)
17+
->and($validatedDTO->validatedData)
18+
->toBe(['name' => $this->subject_name])
19+
->and($validatedDTO->lazyValidation)
20+
->toBeTrue();
21+
});
22+
23+
it('does not fails a lazy validation with valid data', function () {
24+
$validatedDTO = new LazyDTO(['name' => $this->subject_name]);
25+
26+
expect($validatedDTO)->toBeInstanceOf(ValidatedDTO::class)
27+
->and($validatedDTO->validatedData)
28+
->toBe(['name' => $this->subject_name])
29+
->and($validatedDTO->lazyValidation)
30+
->toBeTrue();
31+
32+
$validatedDTO->validate();
33+
});
34+
35+
it('fails a lazy validation with invalid data', function () {
36+
$validatedDTO = new LazyDTO(['name' => null]);
37+
38+
expect($validatedDTO)->toBeInstanceOf(ValidatedDTO::class)
39+
->and($validatedDTO->validatedData)
40+
->toBe(['name' => null])
41+
->and($validatedDTO->lazyValidation)
42+
->toBeTrue();
43+
44+
$validatedDTO->validate();
45+
})->throws(ValidationException::class);

0 commit comments

Comments
 (0)