Skip to content

Commit

Permalink
Merge pull request #92 from WendellAdriel/feature/lazy-validation
Browse files Browse the repository at this point in the history
Add lazy validation feature
  • Loading branch information
WendellAdriel authored Nov 6, 2024
2 parents 19034b3 + 90911ea commit 17bd33c
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 28 deletions.
48 changes: 32 additions & 16 deletions src/SimpleDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,36 @@ abstract class SimpleDTO implements BaseDTO, CastsAttributes
{
use DataResolver, DataTransformer;

public bool $lazyValidation = false;

/** @internal */
protected array $dtoData = [];

/** @internal */
protected array $validatedData = [];

/** @internal */
protected bool $requireCasting = false;

/** @internal */
protected \Illuminate\Contracts\Validation\Validator|\Illuminate\Validation\Validator $validator;

/** @internal */
protected array $dtoRules = [];

/** @internal */
protected array $dtoMessages = [];

/** @internal */
protected array $dtoDefaults = [];

/** @internal */
protected array $dtoCasts = [];

/** @internal */
protected array $dtoMapData = [];

/** @internal */
protected array $dtoMapTransform = [];

/**
Expand Down Expand Up @@ -154,9 +165,9 @@ protected function mapToTransform(): array
/**
* @throws MissingCastTypeException|CastTargetException
*/
protected function passedValidation(): void
protected function passedValidation(bool $forceCast = false): void
{
$this->validatedData = $this->validatedData();
$this->validatedData = $this->validatedData($forceCast);
/** @var array<Castable> $casts */
$casts = $this->buildCasts();

Expand Down Expand Up @@ -187,7 +198,7 @@ protected function passedValidation(): void

$formatted = $this->shouldReturnNull($key, $value)
? null
: $this->castValue($casts[$key], $key, $value);
: $this->castValue($casts[$key], $key, $value, $forceCast);

$this->{$key} = $formatted;
$this->validatedData[$key] = $formatted;
Expand All @@ -212,7 +223,7 @@ protected function isValidData(): bool
*
* @throws MissingCastTypeException|CastTargetException
*/
protected function validatedData(): array
protected function validatedData(bool $forceCast = false): array
{
$acceptedKeys = $this->getAcceptedProperties();
$result = [];
Expand All @@ -233,7 +244,7 @@ protected function validatedData(): array

$result[$key] = $this->shouldReturnNull($key, $value)
? null
: $this->castValue($casts[$key], $key, $value);
: $this->castValue($casts[$key], $key, $value, $forceCast);
}
}

Expand All @@ -249,8 +260,12 @@ protected function validatedData(): array
/**
* @throws CastTargetException
*/
protected function castValue(mixed $cast, string $key, mixed $value): mixed
protected function castValue(mixed $cast, string $key, mixed $value, bool $forceCast = false): mixed
{
if ($this->lazyValidation && ! $forceCast) {
return $value;
}

if ($cast instanceof Castable) {
return $cast->cast($key, $value);
}
Expand Down Expand Up @@ -300,6 +315,16 @@ protected function buildDataForExport(): array
return $this->mapDTOData($mapping, $this->validatedData);
}

protected function buildDataForValidation(array $data): array
{
$mapping = [
...$this->mapData(),
...$this->dtoMapData,
];

return $this->mapDTOData($mapping, $data);
}

private function buildAttributesData(): void
{
$publicProperties = $this->getPublicProperties();
Expand Down Expand Up @@ -374,16 +399,6 @@ private function getPropertiesForAttribute(array $properties, string $attribute)
return $result;
}

private function buildDataForValidation(array $data): array
{
$mapping = [
...$this->mapData(),
...$this->dtoMapData,
];

return $this->mapDTOData($mapping, $data);
}

private function mapDTOData(array $mapping, array $data): array
{
$mappedData = [];
Expand Down Expand Up @@ -525,6 +540,7 @@ private function isforbiddenProperty(string $property): bool
'dtoCasts',
'dtoMapData',
'dtoMapTransform',
'lazyValidation',
]);
}
}
41 changes: 29 additions & 12 deletions src/ValidatedDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ 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
Expand All @@ -43,7 +55,7 @@ protected function after(\Illuminate\Validation\Validator $validator): void
*
* @throws MissingCastTypeException|CastTargetException
*/
protected function validatedData(): array
protected function validatedData(bool $forceCast = false): array
{
$acceptedKeys = array_keys($this->rulesList());
$result = [];
Expand All @@ -64,7 +76,7 @@ protected function validatedData(): array

$result[$key] = $this->shouldReturnNull($key, $value)
? null
: $this->castValue($casts[$key], $key, $value);
: $this->castValue($casts[$key], $key, $value, $forceCast);
}
}

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

protected function isValidData(): 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();
return $this->lazyValidation || $this->validationPasses();
}

/**
Expand All @@ -107,6 +110,20 @@ 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();
Expand Down
39 changes: 39 additions & 0 deletions tests/Datasets/LazyDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\ValidatedDTO\Tests\Datasets;

use WendellAdriel\ValidatedDTO\Casting\IntegerCast;
use WendellAdriel\ValidatedDTO\Casting\StringCast;
use WendellAdriel\ValidatedDTO\ValidatedDTO;

class LazyDTO extends ValidatedDTO
{
public bool $lazyValidation = true;

public ?string $name;

public ?int $age = null;

protected function rules(): array
{
return [
'name' => 'required',
'age' => 'numeric',
];
}

protected function defaults(): array
{
return [];
}

protected function casts(): array
{
return [
'name' => new StringCast(),
'age' => new IntegerCast(),
];
}
}
45 changes: 45 additions & 0 deletions tests/Unit/LazyValidationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

use Illuminate\Validation\ValidationException;
use WendellAdriel\ValidatedDTO\Tests\Datasets\LazyDTO;
use WendellAdriel\ValidatedDTO\ValidatedDTO;

beforeEach(function () {
$this->subject_name = fake()->name;
});

it('instantiates a ValidatedDTO marked as lazy without validating its data', function () {
$validatedDTO = new LazyDTO(['name' => $this->subject_name]);

expect($validatedDTO)->toBeInstanceOf(ValidatedDTO::class)
->and($validatedDTO->validatedData)
->toBe(['name' => $this->subject_name])
->and($validatedDTO->lazyValidation)
->toBeTrue();
});

it('does not fails a lazy validation with valid data', function () {
$validatedDTO = new LazyDTO(['name' => $this->subject_name]);

expect($validatedDTO)->toBeInstanceOf(ValidatedDTO::class)
->and($validatedDTO->validatedData)
->toBe(['name' => $this->subject_name])
->and($validatedDTO->lazyValidation)
->toBeTrue();

$validatedDTO->validate();
});

it('fails a lazy validation with invalid data', function () {
$validatedDTO = new LazyDTO(['name' => null]);

expect($validatedDTO)->toBeInstanceOf(ValidatedDTO::class)
->and($validatedDTO->validatedData)
->toBe(['name' => null])
->and($validatedDTO->lazyValidation)
->toBeTrue();

$validatedDTO->validate();
})->throws(ValidationException::class);

0 comments on commit 17bd33c

Please sign in to comment.