From 639be150cd3a7af7af5599d921137dd106b77b7f Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Mon, 26 Jul 2021 13:43:49 +0200 Subject: [PATCH] wip --- .../ResolveDataObjectFromArrayAction.php | 13 +++----- src/Attributes/WithCast.php | 5 ++++ src/Attributes/WithTransformer.php | 30 +++++++++++++++++++ src/Casts/DateTimeInterfaceCast.php | 8 +++-- src/LaravelDataServiceProvider.php | 20 ------------- src/Support/DataConfig.php | 9 +----- src/Support/DataProperty.php | 22 ++++++++++++++ src/Transformers/DataTransformer.php | 23 +++++++------- src/Transformers/DateTransformer.php | 6 ++-- tests/DataTest.php | 19 ++++++++++++ tests/Support/DataPropertyTest.php | 24 +++++++++++++++ 11 files changed, 127 insertions(+), 52 deletions(-) create mode 100644 src/Attributes/WithTransformer.php diff --git a/src/Actions/ResolveDataObjectFromArrayAction.php b/src/Actions/ResolveDataObjectFromArrayAction.php index e36b1f61..2c476b4f 100644 --- a/src/Actions/ResolveDataObjectFromArrayAction.php +++ b/src/Actions/ResolveDataObjectFromArrayAction.php @@ -19,10 +19,10 @@ public function execute(string $class, array $values): Data { /** @var \Spatie\LaravelData\Data $data */ $data = collect($this->dataConfig->getDataProperties($class)) - ->mapWithKeys(fn (DataProperty $property) => [ + ->mapWithKeys(fn(DataProperty $property) => [ $property->name() => $this->resolveValue($property, $values[$property->name()] ?? null), ]) - ->pipe(fn (Collection $properties) => new $class(...$properties)); + ->pipe(fn(Collection $properties) => new $class(...$properties)); return $data; } @@ -51,7 +51,7 @@ private function resolveValue(DataProperty $property, mixed $value): mixed if ($property->isDataCollection()) { $items = array_map( - fn (array $item) => $this->execute($property->getDataClass(), $item), + fn(array $item) => $this->execute($property->getDataClass(), $item), $value ); @@ -72,12 +72,7 @@ private function resolveValueByAttributeCast( DataProperty $property, mixed $value ): mixed { - $attribute = $property->castAttribute(); - - /** @psalm-suppress all */ - $cast = new ($attribute->castClass)(...$attribute->arguments); - - return $cast->cast($property, $value); + return $property->castAttribute()->get()->cast($property, $value); } private function resolveGlobalCast(DataProperty $property): ?Cast diff --git a/src/Attributes/WithCast.php b/src/Attributes/WithCast.php index 7433479f..481548c8 100644 --- a/src/Attributes/WithCast.php +++ b/src/Attributes/WithCast.php @@ -22,4 +22,9 @@ public function __construct( $this->arguments = $arguments; } + + public function get(): Cast + { + return new ($this->castClass)(...$this->arguments); + } } diff --git a/src/Attributes/WithTransformer.php b/src/Attributes/WithTransformer.php new file mode 100644 index 00000000..e8e33a1e --- /dev/null +++ b/src/Attributes/WithTransformer.php @@ -0,0 +1,30 @@ + */ + public string $transformerClass, + mixed ...$arguments + ) { + if (! is_a($this->transformerClass, Transformer::class, true)) { + throw new Exception("Transformer given is not a transformer"); + } + + $this->arguments = $arguments; + } + + public function get(): Transformer + { + return new ($this->transformerClass)(...$this->arguments); + } +} diff --git a/src/Casts/DateTimeInterfaceCast.php b/src/Casts/DateTimeInterfaceCast.php index ca150ac6..93237a80 100644 --- a/src/Casts/DateTimeInterfaceCast.php +++ b/src/Casts/DateTimeInterfaceCast.php @@ -9,12 +9,14 @@ class DateTimeInterfaceCast implements Cast { public function __construct( - protected string $format + protected ?string $format = null ) { } public function cast(DataProperty $property, mixed $value): DateTimeInterface | Uncastable { + $format = $this->format ?? config('data.date_format'); + $type = $this->findType($property); if ($type instanceof Uncastable) { @@ -22,10 +24,10 @@ public function cast(DataProperty $property, mixed $value): DateTimeInterface | } /** @var \DateTime|\DateTimeImmutable $name */ - $datetime = $type::createFromFormat($this->format, $value); + $datetime = $type::createFromFormat($format, $value); if ($datetime === false) { - throw new Exception("Could not cast date: `{$value}` using format {$this->format}"); + throw new Exception("Could not cast date: `{$value}` using format {$format}"); } return $datetime; diff --git a/src/LaravelDataServiceProvider.php b/src/LaravelDataServiceProvider.php index 42ffa26c..b421cbfa 100644 --- a/src/LaravelDataServiceProvider.php +++ b/src/LaravelDataServiceProvider.php @@ -23,26 +23,6 @@ public function configurePackage(Package $package): void public function packageRegistered() { - $this->app->when(DateTimeCast::class)->needs('$format')->give( - config('data.date_format') - ); - - $this->app->when(DateTimeImmutableCast::class)->needs('$format')->give( - config('data.date_format') - ); - - $this->app->when(CarbonCast::class)->needs('$format')->give( - config('data.date_format') - ); - - $this->app->when(CarbonImmutableCast::class)->needs('$format')->give( - config('data.date_format') - ); - - $this->app->when(DateTransformer::class)->needs('$format')->give( - config('data.date_format') - ); - $this->app->singleton( DataConfig::class, fn () => new DataConfig(config('data')) diff --git a/src/Support/DataConfig.php b/src/Support/DataConfig.php index 97c4bd42..5d1245a3 100644 --- a/src/Support/DataConfig.php +++ b/src/Support/DataConfig.php @@ -64,14 +64,7 @@ public function getAutoRules(): array return $this->autoRules; } - public function transform(mixed $value): mixed - { - $transformer = $this->findTransformerForValue($value); - - return $transformer?->transform($value) ?? $value; - } - - protected function findTransformerForValue(mixed $value): ?Transformer + public function findTransformerForValue(mixed $value): ?Transformer { foreach ($this->transformers as $transformer) { if ($transformer->canTransform($value)) { diff --git a/src/Support/DataProperty.php b/src/Support/DataProperty.php index 777c1918..6910a1a9 100644 --- a/src/Support/DataProperty.php +++ b/src/Support/DataProperty.php @@ -9,6 +9,7 @@ use ReflectionUnionType; use Spatie\LaravelData\Attributes\DataValidationAttribute; use Spatie\LaravelData\Attributes\WithCast; +use Spatie\LaravelData\Attributes\WithTransformer; use Spatie\LaravelData\Data; use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\Exceptions\InvalidDataPropertyType; @@ -35,6 +36,8 @@ class DataProperty private ?WithCast $castAttribute; + private ?WithTransformer $transformerAttribute; + public static function create(ReflectionProperty $property): static { return new self($property); @@ -113,6 +116,15 @@ public function castAttribute(): ?WithCast return $this->castAttribute; } + public function transformerAttribute(): ?WithTransformer + { + if (! isset($this->transformerAttribute)) { + $this->loadAttributes(); + } + + return $this->transformerAttribute; + } + /** * @return class-string<\Spatie\LaravelData\Data> */ @@ -252,6 +264,12 @@ private function loadAttributes(): void continue; } + + if ($initiatedAttribute instanceof WithTransformer) { + $this->transformerAttribute = $initiatedAttribute; + + continue; + } } $this->validationAttributes = $validationAttributes; @@ -259,5 +277,9 @@ private function loadAttributes(): void if (! isset($this->castAttribute)) { $this->castAttribute = null; } + + if (! isset($this->transformerAttribute)) { + $this->transformerAttribute = null; + } } } diff --git a/src/Transformers/DataTransformer.php b/src/Transformers/DataTransformer.php index 07d503e8..66bf7cc1 100644 --- a/src/Transformers/DataTransformer.php +++ b/src/Transformers/DataTransformer.php @@ -2,12 +2,11 @@ namespace Spatie\LaravelData\Transformers; -use ReflectionClass; -use ReflectionProperty; use Spatie\LaravelData\Data; use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\Lazy; use Spatie\LaravelData\Support\DataConfig; +use Spatie\LaravelData\Support\DataProperty; class DataTransformer { @@ -42,18 +41,17 @@ public function transform(Data $data): array protected function resolvePayload(Data $data): array { - $reflection = new ReflectionClass($data); - $inclusionTree = $data->getInclusionTree(); $exclusionTree = $data->getExclusionTree(); return array_reduce( - $reflection->getProperties(ReflectionProperty::IS_PUBLIC), - function (array $payload, ReflectionProperty $property) use ($data, $exclusionTree, $inclusionTree) { - $name = $property->getName(); + $this->config->getDataProperties($data::class), + function (array $payload, DataProperty $property) use ($data, $exclusionTree, $inclusionTree) { + $name = $property->name(); if ($this->shouldIncludeProperty($name, $data->{$name}, $inclusionTree, $exclusionTree)) { $payload[$name] = $this->resolvePropertyValue( + $property, $data->{$name}, $inclusionTree[$name] ?? [], $exclusionTree[$name] ?? [] @@ -96,6 +94,7 @@ protected function shouldIncludeProperty( } protected function resolvePropertyValue( + DataProperty $property, mixed $value, array $nestedInclusionTree, array $nestedExclusionTree, @@ -112,8 +111,12 @@ protected function resolvePropertyValue( : $value; } - return $this->withValueTransforming - ? $this->config->transform($value) - : $value; + if (! $this->withValueTransforming) { + return $value; + } + + $transformer = $property->transformerAttribute()?->get() ?? $this->config->findTransformerForValue($value); + + return $transformer?->transform($value) ?? $value; } } diff --git a/src/Transformers/DateTransformer.php b/src/Transformers/DateTransformer.php index 96a5b105..a1166a2b 100644 --- a/src/Transformers/DateTransformer.php +++ b/src/Transformers/DateTransformer.php @@ -6,7 +6,7 @@ class DateTransformer implements Transformer { - public function __construct(protected string $format) + public function __construct(protected ?string $format = null) { } @@ -17,7 +17,9 @@ public function canTransform(mixed $value): bool public function transform(mixed $value): string { + $format = $this->format ?? config('data.date_format'); + /** @var \DateTimeInterface $value */ - return $value->format($this->format); + return $value->format($format); } } diff --git a/tests/DataTest.php b/tests/DataTest.php index 34f70d33..075d2d3e 100644 --- a/tests/DataTest.php +++ b/tests/DataTest.php @@ -3,6 +3,7 @@ namespace Spatie\LaravelData\Tests; use DateTime; +use Spatie\LaravelData\Attributes\WithTransformer; use Spatie\LaravelData\Data; use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\Lazy; @@ -13,6 +14,7 @@ use Spatie\LaravelData\Tests\Fakes\LazyData; use Spatie\LaravelData\Tests\Fakes\MultiLazyData; use Spatie\LaravelData\Tests\Fakes\SimpleData; +use Spatie\LaravelData\Transformers\DateTransformer; class DataTest extends TestCase { @@ -313,6 +315,23 @@ public function __construct(public DateTime $date) $this->assertEquals(['date' => '1994-05-16T00:00:00+00:00'], $data->toArray()); } + /** @test */ + public function it_can_manually_specify_a_transformer() + { + $date = new DateTime('16 may 1994'); + + $data = new class($date) extends Data { + public function __construct( + #[WithTransformer(DateTransformer::class, 'd-m-Y')] + public $date + ) + { + } + }; + + $this->assertEquals(['date' => '16-05-1994'], $data->toArray()); + } + /** @test */ public function it_can_dynamically_include_data_based_upon_the_request() { diff --git a/tests/Support/DataPropertyTest.php b/tests/Support/DataPropertyTest.php index 9f61694a..3c30e45c 100644 --- a/tests/Support/DataPropertyTest.php +++ b/tests/Support/DataPropertyTest.php @@ -5,6 +5,7 @@ use ReflectionProperty; use Spatie\LaravelData\Attributes\Max; use Spatie\LaravelData\Attributes\WithCast; +use Spatie\LaravelData\Attributes\WithTransformer; use Spatie\LaravelData\Casts\DateTimeCast; use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\Exceptions\InvalidDataPropertyType; @@ -12,6 +13,7 @@ use Spatie\LaravelData\Support\DataProperty; use Spatie\LaravelData\Tests\Fakes\SimpleData; use Spatie\LaravelData\Tests\TestCase; +use Spatie\LaravelData\Transformers\DateTransformer; class DataPropertyTest extends TestCase { @@ -281,6 +283,28 @@ public function it_can_get_the_cast_attribute_with_arguments() $this->assertEquals(new WithCast(DateTimeCast::class, 'd-m-y'), $helper->castAttribute()); } + /** @test */ + public function it_can_get_the_transformer_attribute() + { + $helper = $this->resolveHelper(new class { + #[WithTransformer(DateTransformer::class)] + public SimpleData $property; + }); + + $this->assertEquals(new WithTransformer(DateTransformer::class), $helper->transformerAttribute()); + } + + /** @test */ + public function it_can_get_the_transformer_attribute_with_arguments() + { + $helper = $this->resolveHelper(new class { + #[WithTransformer(DateTransformer::class, 'd-m-y')] + public SimpleData $property; + }); + + $this->assertEquals(new WithTransformer(DateTransformer::class, 'd-m-y'), $helper->transformerAttribute()); + } + /** @test */ public function it_can_get_the_data_class_for_a_data_object() {