Skip to content

Commit

Permalink
Add new Cast attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
WendellAdriel committed Oct 10, 2023
1 parent 3cfbda2 commit f235f72
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 4 deletions.
23 changes: 23 additions & 0 deletions src/Attributes/Cast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\ValidatedDTO\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class Cast
{
public function __construct(
/**
* @var class-string
*/
public string $type,
/**
* @var class-string
*/
public ?string $param = null,
) {
}
}
31 changes: 28 additions & 3 deletions src/SimpleDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Illuminate\Validation\ValidationException;
use ReflectionClass;
use ReflectionProperty;
use WendellAdriel\ValidatedDTO\Attributes\Cast;
use WendellAdriel\ValidatedDTO\Attributes\DefaultValue;
use WendellAdriel\ValidatedDTO\Attributes\Map;
use WendellAdriel\ValidatedDTO\Attributes\Rules;
Expand Down Expand Up @@ -39,6 +40,8 @@ abstract class SimpleDTO implements BaseDTO, CastsAttributes

protected array $dtoDefaults = [];

protected array $dtoCasts = [];

protected array $dtoMapData = [];

protected array $dtoMapTransform = [];
Expand Down Expand Up @@ -147,7 +150,7 @@ protected function passedValidation(): void
{
$this->validatedData = $this->validatedData();
/** @var array<Castable> $casts */
$casts = $this->casts();
$casts = $this->buildCasts();

foreach ($this->validatedData as $key => $value) {
$this->{$key} = $value;
Expand All @@ -161,7 +164,7 @@ protected function passedValidation(): void
foreach ($defaults as $key => $value) {
if (
! property_exists($this, $key) ||
empty($this->{$key})
! isset($this->{$key})
) {
if (! array_key_exists($key, $casts)) {
if ($this->requireCasting) {
Expand Down Expand Up @@ -205,7 +208,7 @@ protected function validatedData(): array
$result = [];

/** @var array<Castable> $casts */
$casts = $this->casts();
$casts = $this->buildCasts();

foreach ($this->data as $key => $value) {
if (in_array($key, $acceptedKeys)) {
Expand Down Expand Up @@ -254,6 +257,21 @@ protected function shouldReturnNull(string $key, mixed $value): bool
return is_null($value);
}

protected function buildCasts(): array
{
$casts = [];
foreach ($this->dtoCasts as $property => $cast) {
$casts[$property] = is_null($cast->param)
? new $cast->type()
: new $cast->type(new $cast->param());
}

return [
...$this->casts(),
...$casts,
];
}

private function buildAttributesData(): void
{
$publicProperties = $this->getPublicProperties();
Expand All @@ -276,6 +294,12 @@ private function buildAttributesData(): void
$this->dtoDefaults[$property] = $attributeInstance->value;
}

$castProperties = $this->getPropertiesForAttribute($publicProperties, Cast::class);
foreach ($castProperties as $property => $attribute) {
$attributeInstance = $attribute->newInstance();
$this->dtoCasts[$property] = $attributeInstance;
}

$mapDataProperties = $this->getPropertiesForAttribute($publicProperties, Map::class);
foreach ($mapDataProperties as $property => $attribute) {
$attributeInstance = $attribute->newInstance();
Expand Down Expand Up @@ -471,6 +495,7 @@ private function isforbiddenProperty(string $property): bool
'dtoRules',
'dtoMessages',
'dtoDefaults',
'dtoCasts',
'dtoMapData',
'dtoMapTransform',
]);
Expand Down
2 changes: 1 addition & 1 deletion src/ValidatedDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected function validatedData(): array
$result = [];

/** @var array<Castable> $casts */
$casts = $this->casts();
$casts = $this->buildCasts();

foreach ($this->data as $key => $value) {
if (in_array($key, $acceptedKeys)) {
Expand Down
14 changes: 14 additions & 0 deletions tests/Datasets/UserAttributesDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@

namespace WendellAdriel\ValidatedDTO\Tests\Datasets;

use WendellAdriel\ValidatedDTO\Attributes\Cast;
use WendellAdriel\ValidatedDTO\Attributes\DefaultValue;
use WendellAdriel\ValidatedDTO\Attributes\Map;
use WendellAdriel\ValidatedDTO\Attributes\Rules;
use WendellAdriel\ValidatedDTO\Casting\ArrayCast;
use WendellAdriel\ValidatedDTO\Casting\BooleanCast;
use WendellAdriel\ValidatedDTO\Casting\FloatCast;
use WendellAdriel\ValidatedDTO\Casting\IntegerCast;
use WendellAdriel\ValidatedDTO\Concerns\EmptyCasts;
use WendellAdriel\ValidatedDTO\Concerns\EmptyDefaults;
use WendellAdriel\ValidatedDTO\Concerns\EmptyRules;
Expand All @@ -25,5 +30,14 @@ class UserAttributesDTO extends ValidatedDTO

#[Rules(['sometimes', 'boolean'])]
#[DefaultValue(true)]
#[Cast(BooleanCast::class)]
public bool $active;

#[Rules(['sometimes', 'integer'])]
#[Cast(IntegerCast::class)]
public ?int $age;

#[Rules(['sometimes', 'array'])]
#[Cast(type: ArrayCast::class, param: FloatCast::class)]
public ?array $grades;
}
30 changes: 30 additions & 0 deletions tests/Unit/AttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,33 @@
'active' => true,
]);
});

it('casts the DTO data using the Cast attribute', function () {
$userDTO = new UserAttributesDTO([
'name' => $this->subject_name,
'email' => $this->subject_email,
'active' => '0',
'age' => '25',
'grades' => ['10', '9.5', '8.5'],
]);

expect($userDTO)->toBeInstanceOf(UserAttributesDTO::class)
->and($userDTO->validatedData)
->toBe([
'name' => $this->subject_name,
'email' => $this->subject_email,
'active' => false,
'age' => 25,
'grades' => [10.0, 9.5, 8.5],
])
->and($userDTO->validator->passes())
->toBeTrue()
->and($userDTO->toArray())
->toBe([
'full_name' => $this->subject_name,
'email' => $this->subject_email,
'active' => false,
'age' => 25,
'grades' => [10.0, 9.5, 8.5],
]);
});

0 comments on commit f235f72

Please sign in to comment.