From 03ec7cfc9be3ebf67e977bcbfe698cc190f70529 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Fri, 19 Jan 2024 15:48:24 +0100 Subject: [PATCH] Speed improvements, upgrade guide, changelog --- CHANGELOG.md | 4 +- UPGRADING.md | 233 +++++++++++++++--- src/Casts/Cast.php | 2 +- src/Casts/DateTimeInterfaceCast.php | 3 +- src/Casts/EnumCast.php | 2 +- src/Concerns/BaseData.php | 2 +- src/Concerns/TransformableData.php | 2 +- src/Contracts/BaseData.php | 2 +- src/DataPipes/AuthorizedDataPipe.php | 4 +- src/DataPipes/CastPropertiesDataPipe.php | 6 +- src/DataPipes/DataPipe.php | 8 +- src/DataPipes/DefaultValuesDataPipe.php | 6 +- .../FillRouteParameterPropertiesDataPipe.php | 12 +- src/DataPipes/MapPropertiesDataPipe.php | 7 +- src/DataPipes/ValidatePropertiesDataPipe.php | 4 +- src/Resolvers/DataFromArrayResolver.php | 65 ++--- src/Resolvers/DataFromSomethingResolver.php | 2 +- ...=> TransformedDataCollectableResolver.php} | 2 +- src/Resolvers/TransformedDataResolver.php | 2 +- src/Support/DataContainer.php | 10 +- src/Support/ResolvedDataPipeline.php | 4 +- tests/Casts/DateTimeInterfaceCastTest.php | 28 +-- tests/Casts/EnumCastTest.php | 8 +- .../CastTransformers/FakeCastTransformer.php | 2 +- tests/Fakes/Castables/SimpleCastable.php | 2 +- tests/Fakes/Casts/ConfidentialDataCast.php | 2 +- .../Casts/ConfidentialDataCollectionCast.php | 2 +- tests/Fakes/Casts/ContextAwareCast.php | 4 +- tests/Fakes/Casts/MeaningOfLifeCast.php | 2 +- tests/Fakes/Casts/StringToUpperCast.php | 2 +- tests/PipelineTest.php | 5 +- tests/TestSupport/DataValidationAsserter.php | 2 +- 32 files changed, 294 insertions(+), 147 deletions(-) rename src/Resolvers/{TransformedDataCollectionResolver.php => TransformedDataCollectableResolver.php} (98%) diff --git a/CHANGELOG.md b/CHANGELOG.md index c51edd634..f4a132ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ All notable changes to `laravel-data` will be documented in this file. - Addition of collect method - Removal of collection method - Add support for using Laravel Model attributes as data properties -- Add support for class defined defaults - Allow creating data objects using `from` without parameters - Add support for a Dto and Resource object +- Added contexts to the creation and transformation process +- Allow creating a data object or collection using a factory +- Speed up the process of creating and transforming data objects ## 3.11.0 - 2023-12-21 diff --git a/UPGRADING.md b/UPGRADING.md index 8460d00fc..f941c40dc 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,48 +1,189 @@ # Upgrading -Because there are many breaking changes an upgrade is not that easy. There are many edge cases this guide does not cover. We accept PRs to improve this guide. +Because there are many breaking changes an upgrade is not that easy. There are many edge cases this guide does not +cover. We accept PRs to improve this guide. ## From v3 to v4 The following things are required when upgrading: -- Laravel 10 is now required -- Start by going through your code and replace all static `SomeData::collection($items)` method calls with `SomeData::collect($items, DataCollection::class)` - - Use `DataPaginatedCollection::class` when you're expecting a paginated collection - - Use `DataCursorPaginatedCollection::class` when you're expecting a cursor paginated collection - - For a more gentle upgrade you can also use the `WithDeprecatedCollectionMethod` trait which adds the collection method again, but this trait will be removed in v5 - - If you were using `$_collectionClass`, `$_paginatedCollectionClass` or `$_cursorPaginatedCollectionClass` then take a look at the magic collect functionality on information about how to replace these -- If you were manually working with `$_includes`, ` $_excludes`, `$_only`, `$_except` or `$_wrap` these can now be found within the `$_dataContext` -- We split up some traits and interfaces, if you're manually using these on you own data implementation then take a look what has changed - - DataTrait (T) and PrepareableData (T/I) were removed - - EmptyData (T/I) and ContextableData (T/I) was added -- If you were calling the transform method on a data object, a `TransformationContextFactory` or `TransformationContext` is now the only parameter you can pass - - Take a look within the docs what has changed -- If you have implemented a custom `Transformer`, update the `transform` method signature with the new `TransformationContext` parameter -- If you have implemented a custom `Cast` - - The `$castContext` parameter is renamed to `$properties` and changed it type from `array` to `collection` - - A new `$context` parameter is added of type `CreationContext` -- If you have implemented a custom DataPipe, update the `handle` method signature with the new `TransformationContext` parameter -- If you manually created `ValidatePropertiesDataPipe` using the `allTypes` parameter, please now use the creation context for this -- The `withoutMagicalCreationFrom` method was removed from data in favour for creation by factory -- If you were using internal data structures like `DataClass` and `DataProperty` then take a look at what has been changed -- The `DataCollectableTransformer` and `DataTransformer` were replaced with their appropriate resolvers -- If you've cached the data structures, be sure to clear the cache -- In previous versions, when trying to include, exclude, only or except certain data properties that did not exist not exception was thrown. This is now the case, these exceptions can be silenced by setting `ignore_invalid_partials` to true within the config file +**Dependency changes (Likelihood Of Impact: High)** + +The package now requires Laravel 10 as a minimum. + +**Data::collection removal (Likelihood Of Impact: High)** + +We've removed the Data::collection method in favour of the collect method. The collect method will return as type what +it gets as a type, so passing in an array will return an array of data objects. With the second parameter the output +type can be overwritten: + +```php +// v3 +SomeData::collection($items); // DataCollection + +// v4 +SomeData::collect($items); // array of SomeData + +SomeData::collect($items, DataCollection::class); // DataCollection +``` + +If you were using the `$_collectionClass`, `$_paginatedCollectionClass` or `$_cursorPaginatedCollectionClass` properties +then take a look at the magic collect functionality on information about how to replace these. + +If you want to keep the old behaviour, you can use the `WithDeprecatedCollectionMethod` trait and `DeprecatedData` +interface on your data objects which adds the collection method again, this trait will probably be removed in v5. + +**Transformers (Likelihood Of Impact: High)** + +If you've implemented a custom transformer, then add the new `TransformationContext` parameter to the `transform` method +signature. + +```php +// v3 +public function transform(DataProperty $property, mixed $value): mixed +{ + // ... +} + +// v4 +public function transform(DataProperty $property,mixed $value,TransformationContext $context): mixed +{ + // ... +} +``` + +**Casts (Likelihood Of Impact: High)** + +If you've implemented a custom cast, then add the new $context `CreationContext` parameter to the `cast` method +signature and rename the old $context parameter to $properties. + +```php +// v3 +public function cast(DataProperty $property, mixed $value, array $context): mixed; +{ + // ... +} + +// v4 +public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): mixed +{ + // ... +} +``` + +**The transform method (Likelihood Of Impact: Medium)** + +The transform method singature was changed to use a factory pattern instead of paramaters: + +```php +// v3 +$data->transform( + transformValues:true, + wrapExecutionType:WrapExecutionType::Disabled, + mapPropertyNames:true, +); + +// v4 +$data->transform( + TransformationContextFactory::create() + ->transformValues() + ->wrapExecutionType(WrapExecutionType::Disabled) + ->mapPropertyNames() +); +``` + +**Data Pipes (Likelihood Of Impact: Medium)** + +If you've implemented a custom data pipe, then add the new `CreationContext` parameter to the `handle` method +signature and change the type of the $properties parameter from `Collection` to `array`. Lastly, return an `array` of +properties instead of a `Collection`. + +```php +// v3 +public function handle(mixed $payload, DataClass $class, Collection $properties): Collection +{ + // ... +} + +// v4 +public function handle(mixed $payload, DataClass $class, array $properties, CreationContext $creationContext): array +{ + // ... +} + +``` + +**Invalid partials (Likelihood Of Impact: Medium)** + +Previously when trying to include, exclude, only or except certain data properties that did not exist the package +continued working. From now on an exception will be thrown, these exceptions can be silenced by +setting `ignore_invalid_partials` to `true` within the config file + +**Internal data structure changes (Likelihood Of Impact: Low)** + +If you use internal data structures like `DataClass` and `DataProperty` then take a look at these classes, a lot as +changed and the creation process now uses factories instead of static constructors. + +**Data interfaces and traits (Likelihood Of Impact: Low)** + +We've split up some interfaces and traits, if you were manually using these on your own data implementation then take a +look what has changed: + +- DataTrait was removed, you can easily build it yourself if required +- PrepareableData (interface and trait) were merged with BaseData +- An EmptyData (interface and trait) was added +- An ContextableData (interface and trait) was added + +**ValidatePropertiesDataPipe (Likelihood Of Impact: Low)** + +If you've used the `ValidatePropertiesDataPipe::allTypes` parameter to validate all types, then please use the new +context when creating a data object to enable this or update your `data.php` config file with the new default. + +**Removal of `withoutMagicalCreationFrom` (Likelihood Of Impact: Low)** + +If you used the `withoutMagicalCreationFrom` method on a data object, the now use the context to disable magical data +object creation: + +```php +// v3 +SomeData::withoutMagicalCreationFrom($payload); + +// v4 +SomeData::factory()->withoutMagicalCreation()->from($payload); +``` + +**Partials (Likelihood Of Impact: Low)** + +If you we're manually setting the `$_includes`, ` $_excludes`, `$_only`, `$_except` or `$_wrap` properties on a data +object, these have now been removed. Instead, you should use the new DataContext and add the partial. + +**Removal of `DataCollectableTransformer` and `DataTransformer` (Likelihood Of Impact: Low)** + +If you were using the `DataCollectableTransformer` or `DataTransformer` then please use the `TransformedDataCollectableResolver` and `TransformedDataResolver` instead. + +**Some advice with this new version of laravel-data** We advise you to take a look at the following things: -- Take a look within your data objects if `DataCollection`'s, `DataPaginatedCollection`'s and `DataCursorPaginatedCollection`'s can be replaced with regular arrays, Laravel Collections and Paginator + +- If you've cached data structures, be sure to clear the cache +- Take a look within your data objects if `DataCollection`'s, `DataPaginatedCollection`'s + and `DataCursorPaginatedCollection`'s can be replaced with regular arrays, Laravel Collections and Paginators making code a lot more readable - Replace `DataCollectionOf` attributes with annotations, providing IDE completion and more info for static analyzers - Replace some `extends Data` definitions with `extends Resource` or `extends Dto` for more minimal data objects -- When using `only` and `except` at the same time on a data object/collection, previously only the except would be executed. From now on, we first execute the except and then the only. +- When using `only` and `except` at the same time on a data object/collection, previously only the except would be + executed. From now on, we first execute the except and then the only. + ## From v2 to v3 -Upgrading to laravel data shouldn't take long, we've documented all possible changes just to provide the whole context. You probably won't have to do anything: +Upgrading to laravel data shouldn't take long, we've documented all possible changes just to provide the whole context. +You probably won't have to do anything: - Laravel 9 is now required -- validation is completely rewritten - - rules are now generated based upon the payload provided, not what a payload possibly could be. This means rules can change depending on the provided payload. - - When you're injecting a `$payload`, `$relativePayload` or `$path` parameter in a custom rules method in your data object, then remove this and use the new `ValidationContext`: +- validation is completely rewritten + - rules are now generated based upon the payload provided, not what a payload possibly could be. This means rules + can change depending on the provided payload. + - When you're injecting a `$payload`, `$relativePayload` or `$path` parameter in a custom rules method in your data + object, then remove this and use the new `ValidationContext`: ```php class SomeData extends Data { @@ -62,14 +203,19 @@ class SomeData extends Data { } } ``` - - The type of `$rules` in the RuleInferrer handle method changed from `RulesCollection` to `PropertyRules` - - RuleInferrers now take a $context parameter which is a `ValidationContext` in their handle method - - Validation attributes now keep track where they are being evaluated when you have nested data objects. Now field references are relative to the object and not to the root validated object - - Some resolvers are removed like: `DataClassValidationRulesResolver`, `DataPropertyValidationRulesResolver` - - The default order of rule inferrers has been changed - - The $payload parameter in the `getValidationRules` method is now required - - The $fields parameter was removed from the `getValidationRules` method, this now should be done outside of the package -- all data specific properties are now prefixed with _, to avoid conflicts with properties with your own defined properties. This is especially important when overwriting `$collectionClass`, `$paginatedCollectionClass`, `$cursorPaginatedCollectionClass`, be sure to add the extra _ within your data classes. + +- The type of `$rules` in the RuleInferrer handle method changed from `RulesCollection` to `PropertyRules` +- RuleInferrers now take a $context parameter which is a `ValidationContext` in their handle method +- Validation attributes now keep track where they are being evaluated when you have nested data objects. Now field + references are relative to the object and not to the root validated object +- Some resolvers are removed like: `DataClassValidationRulesResolver`, `DataPropertyValidationRulesResolver` +- The default order of rule inferrers has been changed +- The $payload parameter in the `getValidationRules` method is now required +- The $fields parameter was removed from the `getValidationRules` method, this now should be done outside of the package +- all data specific properties are now prefixed with _, to avoid conflicts with properties with your own defined + properties. This is especially important when + overwriting `$collectionClass`, `$paginatedCollectionClass`, `$cursorPaginatedCollectionClass`, be sure to add the + extra _ within your data classes. - Serialization logic is now updated and will only include data specific properties ## From v1 to v2 @@ -79,7 +225,9 @@ High impact changes - Please check the most recent `data.php` config file and change yours accordingly - The `Cast` interface now has a `$context` argument in the `cast` method - The `RuleInferrer` interface now has a `RulesCollection` argument instead of an `array` -- By default, it is now impossible to include/exclude properties using a request parameter until manually specified. This behaviour can be overwritten by adding these methods to your data object: +- By default, it is now impossible to include/exclude properties using a request parameter until manually specified. + This behaviour can be overwritten by adding these methods to your data object: + ```php public static function allowedRequestIncludes(): ?array { @@ -91,8 +239,10 @@ High impact changes return null; } ``` + - The `validate` method on a data object will not create a data object after validation, use `validateAndCreate` instead -- `DataCollection` is now being split into a `DataCollection`, `PaginatedDataCollection` and `CursorPaginatedDataCollection` +- `DataCollection` is now being split into a `DataCollection`, `PaginatedDataCollection` + and `CursorPaginatedDataCollection` Low impact changes @@ -105,4 +255,5 @@ Low impact changes - The `DataTypeScriptTransformer` is updated for this new version, if you extend this then please take a look - The `DataTransformer` and `DataCollectionTransformer` now use a `WrapExecutionType` - The `filter` method was removed from paginated data collections -- The `through` and `filter` operations on a `DataCollection` will now happen instant instead of waiting for the transforming process +- The `through` and `filter` operations on a `DataCollection` will now happen instant instead of waiting for the + transforming process diff --git a/src/Casts/Cast.php b/src/Casts/Cast.php index fa87837dc..fc2dbf59a 100644 --- a/src/Casts/Cast.php +++ b/src/Casts/Cast.php @@ -8,5 +8,5 @@ interface Cast { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): mixed; + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): mixed; } diff --git a/src/Casts/DateTimeInterfaceCast.php b/src/Casts/DateTimeInterfaceCast.php index 6a4bdf491..0dd44dac4 100644 --- a/src/Casts/DateTimeInterfaceCast.php +++ b/src/Casts/DateTimeInterfaceCast.php @@ -4,7 +4,6 @@ use DateTimeInterface; use DateTimeZone; -use Illuminate\Support\Collection; use Spatie\LaravelData\Exceptions\CannotCastDate; use Spatie\LaravelData\Support\Creation\CreationContext; use Spatie\LaravelData\Support\DataProperty; @@ -19,7 +18,7 @@ public function __construct( ) { } - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): DateTimeInterface|Uncastable + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): DateTimeInterface|Uncastable { $formats = collect($this->format ?? config('data.date_format')); diff --git a/src/Casts/EnumCast.php b/src/Casts/EnumCast.php index 996fc804b..f305d979b 100644 --- a/src/Casts/EnumCast.php +++ b/src/Casts/EnumCast.php @@ -16,7 +16,7 @@ public function __construct( ) { } - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): BackedEnum | Uncastable + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): BackedEnum | Uncastable { $type = $this->type ?? $property->type->type->findAcceptedTypeForBaseType(BackedEnum::class); diff --git a/src/Concerns/BaseData.php b/src/Concerns/BaseData.php index 3c7b95fcd..53b4d6185 100644 --- a/src/Concerns/BaseData.php +++ b/src/Concerns/BaseData.php @@ -80,7 +80,7 @@ public static function pipeline(): DataPipeline ->through(CastPropertiesDataPipe::class); } - public static function prepareForPipeline(Collection $properties): Collection + public static function prepareForPipeline(array $properties): array { return $properties; } diff --git a/src/Concerns/TransformableData.php b/src/Concerns/TransformableData.php index c9dfdb769..09ffe7c63 100644 --- a/src/Concerns/TransformableData.php +++ b/src/Concerns/TransformableData.php @@ -24,7 +24,7 @@ public function transform( $resolver = match (true) { $this instanceof BaseDataContract => DataContainer::get()->transformedDataResolver(), - $this instanceof BaseDataCollectableContract => DataContainer::get()->transformedDataCollectionResolver(), + $this instanceof BaseDataCollectableContract => DataContainer::get()->transformedDataCollectableResolver(), default => throw new Exception('Cannot transform data object') }; diff --git a/src/Contracts/BaseData.php b/src/Contracts/BaseData.php index bd316446d..3675e5503 100644 --- a/src/Contracts/BaseData.php +++ b/src/Contracts/BaseData.php @@ -48,7 +48,7 @@ public static function factory(?CreationContext $creationContext = null): Creati public static function normalizers(): array; - public static function prepareForPipeline(Collection $properties): Collection; + public static function prepareForPipeline(array $properties): array; public static function pipeline(): DataPipeline; diff --git a/src/DataPipes/AuthorizedDataPipe.php b/src/DataPipes/AuthorizedDataPipe.php index c044a992e..843775493 100644 --- a/src/DataPipes/AuthorizedDataPipe.php +++ b/src/DataPipes/AuthorizedDataPipe.php @@ -13,9 +13,9 @@ class AuthorizedDataPipe implements DataPipe public function handle( mixed $payload, DataClass $class, - Collection $properties, + array $properties, CreationContext $creationContext - ): Collection { + ): array { if (! $payload instanceof Request) { return $properties; } diff --git a/src/DataPipes/CastPropertiesDataPipe.php b/src/DataPipes/CastPropertiesDataPipe.php index 36edd69a9..459c62965 100644 --- a/src/DataPipes/CastPropertiesDataPipe.php +++ b/src/DataPipes/CastPropertiesDataPipe.php @@ -20,9 +20,9 @@ public function __construct( public function handle( mixed $payload, DataClass $class, - Collection $properties, + array $properties, CreationContext $creationContext - ): Collection { + ): array { foreach ($properties as $name => $value) { $dataProperty = $class->properties->first(fn (DataProperty $dataProperty) => $dataProperty->name === $name); @@ -43,7 +43,7 @@ public function handle( protected function cast( DataProperty $property, mixed $value, - Collection $properties, + array $properties, CreationContext $creationContext ): mixed { $shouldCast = $this->shouldBeCasted($property, $value); diff --git a/src/DataPipes/DataPipe.php b/src/DataPipes/DataPipe.php index 59bae51b8..69f249826 100644 --- a/src/DataPipes/DataPipe.php +++ b/src/DataPipes/DataPipe.php @@ -11,15 +11,15 @@ interface DataPipe /** * @param mixed $payload * @param DataClass $class - * @param Collection $properties + * @param array $properties * @param CreationContext $creationContext * - * @return Collection + * @return array */ public function handle( mixed $payload, DataClass $class, - Collection $properties, + array $properties, CreationContext $creationContext - ): Collection; + ): array; } diff --git a/src/DataPipes/DefaultValuesDataPipe.php b/src/DataPipes/DefaultValuesDataPipe.php index 56ff601c0..8d37acbb7 100644 --- a/src/DataPipes/DefaultValuesDataPipe.php +++ b/src/DataPipes/DefaultValuesDataPipe.php @@ -12,11 +12,11 @@ class DefaultValuesDataPipe implements DataPipe public function handle( mixed $payload, DataClass $class, - Collection $properties, + array $properties, CreationContext $creationContext - ): Collection { + ): array { foreach ($class->properties as $name => $property) { - if($properties->has($name)) { + if(array_key_exists($name, $properties)) { continue; } diff --git a/src/DataPipes/FillRouteParameterPropertiesDataPipe.php b/src/DataPipes/FillRouteParameterPropertiesDataPipe.php index ca667e8f5..52270ed50 100644 --- a/src/DataPipes/FillRouteParameterPropertiesDataPipe.php +++ b/src/DataPipes/FillRouteParameterPropertiesDataPipe.php @@ -3,7 +3,6 @@ namespace Spatie\LaravelData\DataPipes; use Illuminate\Http\Request; -use Illuminate\Support\Collection; use Spatie\LaravelData\Attributes\FromRouteParameter; use Spatie\LaravelData\Attributes\FromRouteParameterProperty; use Spatie\LaravelData\Exceptions\CannotFillFromRouteParameterPropertyUsingScalarValue; @@ -16,9 +15,9 @@ class FillRouteParameterPropertiesDataPipe implements DataPipe public function handle( mixed $payload, DataClass $class, - Collection $properties, + array $properties, CreationContext $creationContext - ): Collection { + ): array { if (! $payload instanceof Request) { return $properties; } @@ -32,7 +31,7 @@ public function handle( continue; } - if (! $attribute->replaceWhenPresentInBody && $properties->has($dataProperty->name)) { + if (! $attribute->replaceWhenPresentInBody && array_key_exists($dataProperty->name, $properties)) { continue; } @@ -42,10 +41,7 @@ public function handle( continue; } - $properties->put( - $dataProperty->name, - $this->resolveValue($dataProperty, $attribute, $parameter) - ); + $properties[$dataProperty->name] = $this->resolveValue($dataProperty, $attribute, $parameter); } return $properties; diff --git a/src/DataPipes/MapPropertiesDataPipe.php b/src/DataPipes/MapPropertiesDataPipe.php index 90a5a120c..b8681df38 100644 --- a/src/DataPipes/MapPropertiesDataPipe.php +++ b/src/DataPipes/MapPropertiesDataPipe.php @@ -3,7 +3,6 @@ namespace Spatie\LaravelData\DataPipes; use Illuminate\Support\Arr; -use Illuminate\Support\Collection; use Spatie\LaravelData\Support\Creation\CreationContext; use Spatie\LaravelData\Support\DataClass; @@ -12,9 +11,9 @@ class MapPropertiesDataPipe implements DataPipe public function handle( mixed $payload, DataClass $class, - Collection $properties, + array $properties, CreationContext $creationContext - ): Collection { + ): array { if ($creationContext->mapPropertyNames === false) { return $properties; } @@ -25,7 +24,7 @@ public function handle( } if (Arr::has($properties, $dataProperty->inputMappedName)) { - $properties->put($dataProperty->name, Arr::get($properties, $dataProperty->inputMappedName)); + $properties[$dataProperty->name] = Arr::get($properties, $dataProperty->inputMappedName); } } diff --git a/src/DataPipes/ValidatePropertiesDataPipe.php b/src/DataPipes/ValidatePropertiesDataPipe.php index cbbc25bd9..34b6884ea 100644 --- a/src/DataPipes/ValidatePropertiesDataPipe.php +++ b/src/DataPipes/ValidatePropertiesDataPipe.php @@ -13,9 +13,9 @@ class ValidatePropertiesDataPipe implements DataPipe public function handle( mixed $payload, DataClass $class, - Collection $properties, + array $properties, CreationContext $creationContext - ): Collection { + ): array { if ($creationContext->validationType === ValidationType::Disabled) { return $properties; } diff --git a/src/Resolvers/DataFromArrayResolver.php b/src/Resolvers/DataFromArrayResolver.php index 45ae6207c..27341cef8 100644 --- a/src/Resolvers/DataFromArrayResolver.php +++ b/src/Resolvers/DataFromArrayResolver.php @@ -26,47 +26,48 @@ public function __construct(protected DataConfig $dataConfig) * * @return TData */ - public function execute(string $class, Collection $properties): BaseData + public function execute(string $class, array $properties): BaseData { $dataClass = $this->dataConfig->getDataClass($class); $data = $this->createData($dataClass, $properties); - $dataClass - ->properties - ->reject( - fn (DataProperty $property) => $property->isPromoted - || $property->isReadonly - || ! $properties->has($property->name) - ) - ->each(function (DataProperty $property) use ($properties, $data) { - if ($property->type->isOptional - && isset($data->{$property->name}) - && $properties->get($property->name) instanceof Optional - ) { - return; - } - - if ($property->computed - && $property->type->isNullable - && $properties->get($property->name) === null - ) { - return; // Nullable properties get assigned null by default - } - - if ($property->computed) { - throw CannotSetComputedValue::create($property); - } - - $data->{$property->name} = $properties->get($property->name); - }); + foreach ($dataClass->properties as $property) { + if( + $property->isPromoted + || $property->isReadonly + || ! array_key_exists($property->name, $properties) + ){ + continue; + } + + if ($property->type->isOptional + && isset($data->{$property->name}) + && $properties[$property->name] instanceof Optional + ) { + continue; + } + + if ($property->computed + && $property->type->isNullable + && $properties[$property->name] === null + ) { + continue; // Nullable properties get assigned null by default + } + + if ($property->computed) { + throw CannotSetComputedValue::create($property); + } + + $data->{$property->name} = $properties[$property->name]; + } return $data; } protected function createData( DataClass $dataClass, - Collection $properties, + array $properties, ) { $constructorParameters = $dataClass->constructorMethod?->parameters; @@ -77,8 +78,8 @@ protected function createData( $parameters = []; foreach ($constructorParameters as $parameter) { - if ($properties->has($parameter->name)) { - $parameters[$parameter->name] = $properties->get($parameter->name); + if (array_key_exists($parameter->name, $properties)) { + $parameters[$parameter->name] = $properties[$parameter->name]; continue; } diff --git a/src/Resolvers/DataFromSomethingResolver.php b/src/Resolvers/DataFromSomethingResolver.php index e13eeb29a..b7c23a76e 100644 --- a/src/Resolvers/DataFromSomethingResolver.php +++ b/src/Resolvers/DataFromSomethingResolver.php @@ -34,7 +34,7 @@ public function execute( return $data; } - $properties = new Collection(); + $properties = []; $pipeline = $this->dataConfig->getResolvedDataPipeline($class); diff --git a/src/Resolvers/TransformedDataCollectionResolver.php b/src/Resolvers/TransformedDataCollectableResolver.php similarity index 98% rename from src/Resolvers/TransformedDataCollectionResolver.php rename to src/Resolvers/TransformedDataCollectableResolver.php index 5a62bae0e..bb25233f9 100644 --- a/src/Resolvers/TransformedDataCollectionResolver.php +++ b/src/Resolvers/TransformedDataCollectableResolver.php @@ -20,7 +20,7 @@ use Spatie\LaravelData\Support\Wrapping\WrapExecutionType; use Spatie\LaravelData\Support\Wrapping\WrapType; -class TransformedDataCollectionResolver +class TransformedDataCollectableResolver { public function __construct( protected DataConfig $dataConfig diff --git a/src/Resolvers/TransformedDataResolver.php b/src/Resolvers/TransformedDataResolver.php index 3722b7c3a..e3447c985 100644 --- a/src/Resolvers/TransformedDataResolver.php +++ b/src/Resolvers/TransformedDataResolver.php @@ -161,7 +161,7 @@ protected function resolvePropertyValue( WrapExecutionType::TemporarilyDisabled => WrapExecutionType::Enabled }; - return DataContainer::get()->transformedDataCollectionResolver()->execute( + return DataContainer::get()->transformedDataCollectableResolver()->execute( $value, $fieldContext->setWrapExecutionType($wrapExecutionType) ); diff --git a/src/Support/DataContainer.php b/src/Support/DataContainer.php index 3f27d3e2e..0644d03b5 100644 --- a/src/Support/DataContainer.php +++ b/src/Support/DataContainer.php @@ -5,7 +5,7 @@ use Spatie\LaravelData\Resolvers\DataCollectableFromSomethingResolver; use Spatie\LaravelData\Resolvers\DataFromSomethingResolver; use Spatie\LaravelData\Resolvers\RequestQueryStringPartialsResolver; -use Spatie\LaravelData\Resolvers\TransformedDataCollectionResolver; +use Spatie\LaravelData\Resolvers\TransformedDataCollectableResolver; use Spatie\LaravelData\Resolvers\TransformedDataResolver; use Spatie\LaravelData\Support\Factories\DataClassFactory; @@ -15,7 +15,7 @@ class DataContainer protected ?TransformedDataResolver $transformedDataResolver = null; - protected ?TransformedDataCollectionResolver $transformedDataCollectionResolver = null; + protected ?TransformedDataCollectableResolver $transformedDataCollectableResolver = null; protected ?RequestQueryStringPartialsResolver $requestQueryStringPartialsResolver = null; @@ -43,9 +43,9 @@ public function transformedDataResolver(): TransformedDataResolver return $this->transformedDataResolver ??= app(TransformedDataResolver::class); } - public function transformedDataCollectionResolver(): TransformedDataCollectionResolver + public function transformedDataCollectableResolver(): TransformedDataCollectableResolver { - return $this->transformedDataCollectionResolver ??= app(TransformedDataCollectionResolver::class); + return $this->transformedDataCollectableResolver ??= app(TransformedDataCollectableResolver::class); } public function requestQueryStringPartialsResolver(): RequestQueryStringPartialsResolver @@ -71,7 +71,7 @@ public function dataClassFactory(): DataClassFactory public function reset() { $this->transformedDataResolver = null; - $this->transformedDataCollectionResolver = null; + $this->transformedDataCollectableResolver = null; $this->requestQueryStringPartialsResolver = null; $this->dataFromSomethingResolver = null; $this->dataCollectableFromSomethingResolver = null; diff --git a/src/Support/ResolvedDataPipeline.php b/src/Support/ResolvedDataPipeline.php index 4ae2cf55a..9bbfa2dad 100644 --- a/src/Support/ResolvedDataPipeline.php +++ b/src/Support/ResolvedDataPipeline.php @@ -19,7 +19,7 @@ public function __construct( ) { } - public function execute(mixed $value, CreationContext $creationContext): Collection + public function execute(mixed $value, CreationContext $creationContext): array { $properties = null; @@ -35,8 +35,6 @@ public function execute(mixed $value, CreationContext $creationContext): Collect throw CannotCreateData::noNormalizerFound($this->dataClass->name, $value); } - $properties = collect($properties); - $properties = ($this->dataClass->name)::prepareForPipeline($properties); foreach ($this->pipes as $pipe) { diff --git a/tests/Casts/DateTimeInterfaceCastTest.php b/tests/Casts/DateTimeInterfaceCastTest.php index d75086184..2742f1573 100644 --- a/tests/Casts/DateTimeInterfaceCastTest.php +++ b/tests/Casts/DateTimeInterfaceCastTest.php @@ -28,7 +28,7 @@ $caster->cast( FakeDataStructureFactory::property($class, 'carbon'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(new Carbon('19-05-1994 00:00:00')); @@ -37,7 +37,7 @@ $caster->cast( FakeDataStructureFactory::property($class, 'carbonImmutable'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(new CarbonImmutable('19-05-1994 00:00:00')); @@ -46,7 +46,7 @@ $caster->cast( FakeDataStructureFactory::property($class, 'dateTime'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(new DateTime('19-05-1994 00:00:00')); @@ -55,7 +55,7 @@ $caster->cast( FakeDataStructureFactory::property($class, 'dateTimeImmutable'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(new DateTimeImmutable('19-05-1994 00:00:00')); @@ -72,7 +72,7 @@ $caster->cast( FakeDataStructureFactory::property($class, 'carbon'), '19-05-1994', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(new DateTime('19-05-1994 00:00:00')); @@ -89,7 +89,7 @@ $caster->cast( FakeDataStructureFactory::property($class, 'int'), '1994-05-16 12:20:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(Uncastable::create()); @@ -111,7 +111,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'carbon'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 02:00:00') @@ -120,7 +120,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'carbonImmutable'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 02:00:00') @@ -129,7 +129,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'dateTime'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 02:00:00') @@ -138,7 +138,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'dateTimeImmutable'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 02:00:00') @@ -161,7 +161,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'carbon'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 00:00:00') @@ -170,7 +170,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'carbonImmutable'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 00:00:00') @@ -179,7 +179,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'dateTime'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 00:00:00') @@ -188,7 +188,7 @@ expect($caster->cast( FakeDataStructureFactory::property($class, 'dateTimeImmutable'), '19-05-1994 00:00:00', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() )) ->format('Y-m-d H:i:s')->toEqual('1994-05-19 00:00:00') diff --git a/tests/Casts/EnumCastTest.php b/tests/Casts/EnumCastTest.php index 791ba8d30..219fca57c 100644 --- a/tests/Casts/EnumCastTest.php +++ b/tests/Casts/EnumCastTest.php @@ -20,7 +20,7 @@ $this->caster->cast( FakeDataStructureFactory::property($class, 'enum'), 'foo', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(DummyBackedEnum::FOO); @@ -35,7 +35,7 @@ $this->caster->cast( FakeDataStructureFactory::property($class, 'enum'), 'bar', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(DummyBackedEnum::FOO); @@ -50,7 +50,7 @@ $this->caster->cast( FakeDataStructureFactory::property($class, 'enum'), 'foo', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get() ) )->toEqual(Uncastable::create()); @@ -65,7 +65,7 @@ $this->caster->cast( FakeDataStructureFactory::property($class, 'int'), 'foo', - collect(), + [], CreationContextFactory::createFromConfig($class::class)->get(), ) ) diff --git a/tests/Fakes/CastTransformers/FakeCastTransformer.php b/tests/Fakes/CastTransformers/FakeCastTransformer.php index 1fc80e233..6855b2f30 100644 --- a/tests/Fakes/CastTransformers/FakeCastTransformer.php +++ b/tests/Fakes/CastTransformers/FakeCastTransformer.php @@ -11,7 +11,7 @@ class FakeCastTransformer implements Cast, Transformer { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): mixed + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): mixed { return $value; } diff --git a/tests/Fakes/Castables/SimpleCastable.php b/tests/Fakes/Castables/SimpleCastable.php index 4124c6f2a..416e605d8 100644 --- a/tests/Fakes/Castables/SimpleCastable.php +++ b/tests/Fakes/Castables/SimpleCastable.php @@ -17,7 +17,7 @@ public function __construct(public string $value) public static function dataCastUsing(...$arguments): Cast { return new class () implements Cast { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): mixed + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): mixed { return new SimpleCastable($value); } diff --git a/tests/Fakes/Casts/ConfidentialDataCast.php b/tests/Fakes/Casts/ConfidentialDataCast.php index c551ee66d..e3a9612c0 100644 --- a/tests/Fakes/Casts/ConfidentialDataCast.php +++ b/tests/Fakes/Casts/ConfidentialDataCast.php @@ -10,7 +10,7 @@ class ConfidentialDataCast implements Cast { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): SimpleData + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): SimpleData { return SimpleData::from('CONFIDENTIAL'); } diff --git a/tests/Fakes/Casts/ConfidentialDataCollectionCast.php b/tests/Fakes/Casts/ConfidentialDataCollectionCast.php index d991ab2a3..309787df9 100644 --- a/tests/Fakes/Casts/ConfidentialDataCollectionCast.php +++ b/tests/Fakes/Casts/ConfidentialDataCollectionCast.php @@ -10,7 +10,7 @@ class ConfidentialDataCollectionCast implements Cast { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): array + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): array { return array_map(fn () => SimpleData::from('CONFIDENTIAL'), $value); } diff --git a/tests/Fakes/Casts/ContextAwareCast.php b/tests/Fakes/Casts/ContextAwareCast.php index 6d1976d36..65ff1afc9 100644 --- a/tests/Fakes/Casts/ContextAwareCast.php +++ b/tests/Fakes/Casts/ContextAwareCast.php @@ -9,8 +9,8 @@ class ContextAwareCast implements Cast { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): mixed + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): mixed { - return $value . '+' . $properties->toJson(); + return $value . '+' . json_encode($properties); } } diff --git a/tests/Fakes/Casts/MeaningOfLifeCast.php b/tests/Fakes/Casts/MeaningOfLifeCast.php index 598cc87e0..e473e308a 100644 --- a/tests/Fakes/Casts/MeaningOfLifeCast.php +++ b/tests/Fakes/Casts/MeaningOfLifeCast.php @@ -9,7 +9,7 @@ class MeaningOfLifeCast implements Cast { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): int + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): int { return 42; } diff --git a/tests/Fakes/Casts/StringToUpperCast.php b/tests/Fakes/Casts/StringToUpperCast.php index 3e2fac6ac..ca5184615 100644 --- a/tests/Fakes/Casts/StringToUpperCast.php +++ b/tests/Fakes/Casts/StringToUpperCast.php @@ -9,7 +9,7 @@ class StringToUpperCast implements Cast { - public function cast(DataProperty $property, mixed $value, Collection $properties, CreationContext $context): string + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): string { return strtoupper($value); } diff --git a/tests/PipelineTest.php b/tests/PipelineTest.php index 79ba038a9..caa5efb19 100644 --- a/tests/PipelineTest.php +++ b/tests/PipelineTest.php @@ -1,5 +1,6 @@ put('address', $properties->only(['line_1', 'city', 'state', 'zipcode'])->join(',')); + $properties['address'] = implode(',', Arr::only($properties, ['line_1', 'city', 'state', 'zipcode'])); return $properties; } diff --git a/tests/TestSupport/DataValidationAsserter.php b/tests/TestSupport/DataValidationAsserter.php index 1e8a5df4a..ac33fa4f4 100644 --- a/tests/TestSupport/DataValidationAsserter.php +++ b/tests/TestSupport/DataValidationAsserter.php @@ -167,6 +167,6 @@ private function pipePayload(array $payload): array ->resolve() ->execute($payload, CreationContextFactory::createFromConfig($this->dataClass)->get()); - return $properties->all(); + return $properties; } }