diff --git a/config/data.php b/config/data.php index 72cd2e4a..3be386d1 100644 --- a/config/data.php +++ b/config/data.php @@ -1,8 +1,5 @@ \Spatie\LaravelData\Support\Creation\ValidationStrategy::OnlyRequests->value, /** - * The default name mapping strategy for data objects' keys. - * This has to be a class implementing the `Spatie\LaravelData\Mappers\NameMapper` interface. + * A data object can map the names of its properties when transforming (output) or when + * creating (input). By default, the package will not map any names. You can set a + * global strategy here, or override it on a specific data object. */ - 'naming_strategy' => [ + 'name_mapping_strategy' => [ 'input' => null, 'output' => null, ], diff --git a/docs/as-a-data-transfer-object/mapping-property-names.md b/docs/as-a-data-transfer-object/mapping-property-names.md index 4a5cf871..ac72a2df 100644 --- a/docs/as-a-data-transfer-object/mapping-property-names.md +++ b/docs/as-a-data-transfer-object/mapping-property-names.md @@ -51,6 +51,15 @@ class ContractData extends Data } ``` +It is possible to set a default name mapping strategy for all data objects in the `data.php` config file: + +```php +'name_mapping_strategy' => [ + 'input' => SnakeCaseMapper::class, + 'output' => null, +], +``` + ## Mapping Nested Properties diff --git a/docs/as-a-resource/mapping-property-names.md b/docs/as-a-resource/mapping-property-names.md index 1bcab65b..770107b6 100644 --- a/docs/as-a-resource/mapping-property-names.md +++ b/docs/as-a-resource/mapping-property-names.md @@ -54,6 +54,15 @@ class ContractData extends Data } ``` +It is possible to set a default name mapping strategy for all data objects in the `data.php` config file: + +```php +'name_mapping_strategy' => [ + 'input' => null, + 'output' => SnakeCaseMapper::class, +], +``` + You can now create a data object as such: ```php diff --git a/docs/installation-setup.md b/docs/installation-setup.md index 563f7402..fb826014 100644 --- a/docs/installation-setup.md +++ b/docs/installation-setup.md @@ -18,6 +18,7 @@ php artisan vendor:publish --provider="Spatie\LaravelData\LaravelDataServiceProv This is the contents of the published config file: ```php + return [ /** * The package will use this format when working with dates. If this option @@ -26,6 +27,29 @@ return [ */ 'date_format' => DATE_ATOM, + /** + * When transforming or casting dates, the following timezone will be used to + * convert the date to the correct timezone. If set to null no timezone will + * be passed. + */ + 'date_timezone' => null, + + /** + * It is possible to enable certain features of the package, these would otherwise + * be breaking changes, and thus they are disabled by default. In the next major + * version of the package, these features will be enabled by default. + */ + 'features' => [ + 'cast_and_transform_iterables' => false, + + /** + * When trying to set a computed property value, the package will throw an exception. + * You can disable this behaviour by setting this option to true, which will then just + * ignore the value being passed into the computed property and recalculate it. + */ + 'ignore_exception_when_trying_to_set_computed_property_value' => false, + ], + /** * Global transformers will take complex types and transform them into simple * types. @@ -43,6 +67,7 @@ return [ 'casts' => [ DateTimeInterface::class => Spatie\LaravelData\Casts\DateTimeInterfaceCast::class, BackedEnum::class => Spatie\LaravelData\Casts\EnumCast::class, +// Enumerable::class => Spatie\LaravelData\Casts\EnumerableCast::class, ], /** @@ -92,13 +117,19 @@ return [ * when running in production. This will speed up the package. You * can configure where data objects are stored and which cache * store should be used. + * + * Structures are cached forever as they'll become stale when your + * application is deployed with changes. You can set a duration + * in seconds if you want the cache to clear after a certain + * timeframe. */ 'structure_caching' => [ 'enabled' => true, 'directories' => [app_path('Data')], 'cache' => [ - 'store' => env('CACHE_STORE', 'file'), + 'store' => env('CACHE_STORE', env('CACHE_DRIVER', 'file')), 'prefix' => 'laravel-data', + 'duration' => null, ], 'reflection_discovery' => [ 'enabled' => true, @@ -114,10 +145,71 @@ return [ */ 'validation_strategy' => \Spatie\LaravelData\Support\Creation\ValidationStrategy::OnlyRequests->value, + /** + * A data object can map the names of its properties when transforming (output) or when + * creating (input). By default, the package will not map any names. You can set a + * global strategy here, or override it on a specific data object. + */ + 'name_mapping_strategy' => [ + 'input' => null, + 'output' => null, + ], + /** * When using an invalid include, exclude, only or except partial, the package will - * throw an + * throw an exception. You can disable this behaviour by setting this option to true. */ 'ignore_invalid_partials' => false, + + /** + * When transforming a nested chain of data objects, the package can end up in an infinite + * loop when including a recursive relationship. The max transformation depth can be + * set as a safety measure to prevent this from happening. When set to null, the + * package will not enforce a maximum depth. + */ + 'max_transformation_depth' => null, + + /** + * When the maximum transformation depth is reached, the package will throw an exception. + * You can disable this behaviour by setting this option to true which will return an + * empty array. + */ + 'throw_when_max_transformation_depth_reached' => true, + + /** + * When using the `make:data` command, the package will use these settings to generate + * the data classes. You can override these settings by passing options to the command. + */ + 'commands' => [ + /** + * Provides default configuration for the `make:data` command. These settings can be overridden with options + * passed directly to the `make:data` command for generating single Data classes, or if not set they will + * automatically fall back to these defaults. See `php artisan make:data --help` for more information + */ + 'make' => [ + /** + * The default namespace for generated Data classes. This exists under the application's root namespace, + * so the default 'Data` will end up as '\App\Data', and generated Data classes will be placed in the + * app/Data/ folder. Data classes can live anywhere, but this is where `make:data` will put them. + */ + 'namespace' => 'Data', + + /** + * This suffix will be appended to all data classes generated by make:data, so that they are less likely + * to conflict with other related classes, controllers or models with a similar name without resorting + * to adding an alias for the Data object. Set to a blank string (not null) to disable. + */ + 'suffix' => 'Data', + ], + ], + + /** + * When using Livewire, the package allows you to enable or disable the synths + * these synths will automatically handle the data objects and their + * properties when used in a Livewire component. + */ + 'livewire' => [ + 'enable_synths' => false, + ], ]; ``` diff --git a/src/Resolvers/NameMappersResolver.php b/src/Resolvers/NameMappersResolver.php index aa1a93eb..5ca820d1 100644 --- a/src/Resolvers/NameMappersResolver.php +++ b/src/Resolvers/NameMappersResolver.php @@ -32,31 +32,29 @@ public function execute( protected function resolveInputNameMapper( Collection $attributes ): ?NameMapper { - /** @var \Spatie\LaravelData\Attributes\MapInputName|\Spatie\LaravelData\Attributes\MapName|null $mapper */ + /** @var MapInputName|MapName|null $mapper */ $mapper = $attributes->first(fn (object $attribute) => $attribute instanceof MapInputName) - ?? $attributes->first(fn (object $attribute) => $attribute instanceof MapName) - ?? $this->resolveDefaultNameMapper(input: true); + ?? $attributes->first(fn (object $attribute) => $attribute instanceof MapName); if ($mapper) { return $this->resolveMapper($mapper->input); } - return null; + return $this->resolveDefaultNameMapper(config('data.name_mapping_strategy.input')); } protected function resolveOutputNameMapper( Collection $attributes ): ?NameMapper { - /** @var \Spatie\LaravelData\Attributes\MapOutputName|\Spatie\LaravelData\Attributes\MapName|null $mapper */ + /** @var MapOutputName|MapName|null $mapper */ $mapper = $attributes->first(fn (object $attribute) => $attribute instanceof MapOutputName) - ?? $attributes->first(fn (object $attribute) => $attribute instanceof MapName) - ?? $this->resolveDefaultNameMapper(input: false); + ?? $attributes->first(fn (object $attribute) => $attribute instanceof MapName); if ($mapper) { return $this->resolveMapper($mapper->output); } - return null; + return $this->resolveDefaultNameMapper(config('data.name_mapping_strategy.output')); } protected function resolveMapper(string|int|NameMapper $value): ?NameMapper @@ -89,17 +87,13 @@ protected function resolveMapperClass(int|string|NameMapper $value): NameMapper return new ProvidedNameMapper($value); } - private function resolveDefaultNameMapper(bool $input): null|MapInputName|MapOutputName - { - $nameMapper = $input ? config('data.naming_strategy.input') : config('data.naming_strategy.output'); - - if ($nameMapper === null) { + protected function resolveDefaultNameMapper( + ?string $value, + ): ?NameMapper { + if ($value === null) { return null; } - return match ($input) { - true => new MapInputName($nameMapper), - false => new MapOutputName($nameMapper), - }; + return $this->resolveMapperClass($value); } } diff --git a/tests/Resolvers/NameMappersResolverTest.php b/tests/Resolvers/NameMappersResolverTest.php index 9e782cd5..31a260fd 100644 --- a/tests/Resolvers/NameMappersResolverTest.php +++ b/tests/Resolvers/NameMappersResolverTest.php @@ -104,8 +104,8 @@ function getAttributes(object $class): Collection }); it('can have default mappers', function () { - config()->set('data.naming_strategy.input', CamelCaseMapper::class); - config()->set('data.naming_strategy.output', SnakeCaseMapper::class); + config()->set('data.name_mapping_strategy.input', CamelCaseMapper::class); + config()->set('data.name_mapping_strategy.output', SnakeCaseMapper::class); $attributes = getAttributes(new class () { public $property; @@ -118,8 +118,8 @@ function getAttributes(object $class): Collection }); it('input name mappers only work when no mappers are specified', function () { - config()->set('data.naming_strategy.input', CamelCaseMapper::class); - config()->set('data.naming_strategy.output', SnakeCaseMapper::class); + config()->set('data.name_mapping_strategy.input', CamelCaseMapper::class); + config()->set('data.name_mapping_strategy.output', SnakeCaseMapper::class); $attributes = getAttributes(new class () { #[MapInputName(StudlyCaseMapper::class)] @@ -133,8 +133,8 @@ function getAttributes(object $class): Collection }); it('output name mappers only work when no mappers are specified', function () { - config()->set('data.naming_strategy.input', CamelCaseMapper::class); - config()->set('data.naming_strategy.output', SnakeCaseMapper::class); + config()->set('data.name_mapping_strategy.input', CamelCaseMapper::class); + config()->set('data.name_mapping_strategy.output', SnakeCaseMapper::class); $attributes = getAttributes(new class () { #[MapOutputName(StudlyCaseMapper::class)]