From 1b7ba58534f3c45bb3a1127e9727d8a34db11aa5 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Tue, 23 Jan 2024 10:23:35 +0100 Subject: [PATCH] Docs refactor --- config/data.php | 2 +- docs/_index.md | 2 +- docs/advanced-usage/creating-a-cast.md | 2 +- .../creating-a-rule-inferrer.md | 2 +- docs/advanced-usage/creating-a-transformer.md | 2 +- docs/advanced-usage/pipeline.md | 2 +- docs/advanced-usage/typescript.md | 2 +- docs/advanced-usage/use-with-inertia.md | 2 +- docs/as-a-data-transfer-object/casts.md | 2 +- docs/as-a-data-transfer-object/collections.md | 25 + docs/as-a-data-transfer-object/computed.md | 4 +- .../creating-a-data-object.md | 6 +- docs/as-a-data-transfer-object/defaults.md | 2 +- docs/as-a-data-transfer-object/factories.md | 73 ++ .../mapping-property-names.md | 4 +- docs/as-a-data-transfer-object/nesting.md | 30 +- .../optional-properties.md | 3 +- .../request-to-data-object.md | 641 +----------------- docs/as-a-resource/appending-properties.md | 89 +++ docs/as-a-resource/from-data-to-array.md | 132 ++++ docs/as-a-resource/from-data-to-resource.md | 335 ++------- docs/as-a-resource/lazy-properties.md | 2 +- docs/as-a-resource/mapping-property-names.md | 73 ++ docs/as-a-resource/transformers.md | 4 +- docs/as-a-resource/wrapping.md | 10 +- docs/getting-started/quickstart.md | 80 ++- docs/installation-setup.md | 43 +- docs/third-party-packages.md | 1 + docs/validation/auto-rule-inferring.md | 2 +- docs/validation/introduction.md | 32 +- docs/validation/nesting-data.md | 125 +++- docs/validation/skipping-validation.md | 4 + .../validation/using-validation-attributes.md | 3 +- src/DataPipes/ValidatePropertiesDataPipe.php | 6 +- src/Support/Creation/CreationContext.php | 2 +- .../Creation/CreationContextFactory.php | 20 +- ...idationType.php => ValidationStrategy.php} | 2 +- src/Transformers/Transformer.php | 6 +- tests/CreationFactoryTest.php | 2 +- tests/RequestTest.php | 7 - tests/ValidationTest.php | 6 +- tests/WrapTest.php | 10 + 42 files changed, 742 insertions(+), 1060 deletions(-) create mode 100644 docs/as-a-data-transfer-object/factories.md create mode 100644 docs/as-a-resource/appending-properties.md create mode 100644 docs/as-a-resource/from-data-to-array.md create mode 100644 docs/as-a-resource/mapping-property-names.md rename src/Support/Creation/{ValidationType.php => ValidationStrategy.php} (83%) diff --git a/config/data.php b/config/data.php index d0dca3e3f..90fdbaf65 100644 --- a/config/data.php +++ b/config/data.php @@ -93,7 +93,7 @@ * method. By default, only when a request is passed the data is being validated. This * behaviour can be changed to always validate or to completely disable validation. */ - 'validation_type' => \Spatie\LaravelData\Support\Creation\ValidationType::OnlyRequests->value, + 'validation_strategy' => \Spatie\LaravelData\Support\Creation\ValidationStrategy::OnlyRequests->value, /** * When using an invalid include, exclude, only or except partial, the package will diff --git a/docs/_index.md b/docs/_index.md index ae97fe329..3bc444576 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -1,5 +1,5 @@ --- -title: v3 +title: v4 slogan: Powerful data objects for Laravel githubUrl: https://github.com/spatie/laravel-data branch: main diff --git a/docs/advanced-usage/creating-a-cast.md b/docs/advanced-usage/creating-a-cast.md index f2cfd0902..fe0cc75a4 100644 --- a/docs/advanced-usage/creating-a-cast.md +++ b/docs/advanced-usage/creating-a-cast.md @@ -14,7 +14,7 @@ interface Cast } ``` -The value that should be cast is given, and a `DataProperty` object which represents the property for which the value is cast. You can read more about the internal structures of the package [here](/docs/laravel-data/v3/advanced-usage/internal-structures). +The value that should be cast is given, and a `DataProperty` object which represents the property for which the value is cast. You can read more about the internal structures of the package [here](/docs/laravel-data/v4/advanced-usage/internal-structures). Within the `context` array the complete payload is given. diff --git a/docs/advanced-usage/creating-a-rule-inferrer.md b/docs/advanced-usage/creating-a-rule-inferrer.md index 02495cbf0..a37bb106d 100644 --- a/docs/advanced-usage/creating-a-rule-inferrer.md +++ b/docs/advanced-usage/creating-a-rule-inferrer.md @@ -14,7 +14,7 @@ interface RuleInferrer } ``` -A collection of previous inferred rules is given, and a `DataProperty` object which represents the property for which the value is transformed. You can read more about the internal structures of the package [here](/docs/laravel-data/v3/advanced-usage/internal-structures). +A collection of previous inferred rules is given, and a `DataProperty` object which represents the property for which the value is transformed. You can read more about the internal structures of the package [here](/docs/laravel-data/v4/advanced-usage/internal-structures). The `RulesCollection` contains all the rules for the property represented as `ValidationRule` objects. diff --git a/docs/advanced-usage/creating-a-transformer.md b/docs/advanced-usage/creating-a-transformer.md index 071e91c86..95660b774 100644 --- a/docs/advanced-usage/creating-a-transformer.md +++ b/docs/advanced-usage/creating-a-transformer.md @@ -14,6 +14,6 @@ interface Transformer } ``` -The value that should be transformed is given, and a `DataProperty` object which represents the property for which the value is transformed. You can read more about the internal structures of the package [here](/docs/laravel-data/v3/advanced-usage/internal-structures). +The value that should be transformed is given, and a `DataProperty` object which represents the property for which the value is transformed. You can read more about the internal structures of the package [here](/docs/laravel-data/v4/advanced-usage/internal-structures). In the end, the transformer should return a transformed value. Please note that the given value of a transformer can never be `null`. diff --git a/docs/advanced-usage/pipeline.md b/docs/advanced-usage/pipeline.md index da7b62e6b..333e11b1e 100644 --- a/docs/advanced-usage/pipeline.md +++ b/docs/advanced-usage/pipeline.md @@ -55,7 +55,7 @@ The `handle` method has several arguments: - **payload** the non normalized payload - **class** the `DataClass` object for the data - object [more info](/docs/laravel-data/v3/advanced-usage/internal-structures) + object [more info](/docs/laravel-data/v4/advanced-usage/internal-structures) - **properties** the key-value properties which will be used to construct the data object When using a magic creation methods, the pipeline is not being used (since you manually overwrite how a data object is diff --git a/docs/advanced-usage/typescript.md b/docs/advanced-usage/typescript.md index 5cb3d6866..66e5d29a4 100644 --- a/docs/advanced-usage/typescript.md +++ b/docs/advanced-usage/typescript.md @@ -71,7 +71,7 @@ If you're using the `DtoTransformer` provided by the package, then be sure to pu Annotate each data object that you want to transform to Typescript with a `/** @typescript */` annotation or a `#[TypeScript]` attribute. -To [generate the typescript file](https://spatie.be/docs/typescript-transformer/v3/laravel/executing-the-transform-command) +To [generate the typescript file](https://spatie.be/docs/typescript-transformer/v4/laravel/executing-the-transform-command) , run this command: ```php diff --git a/docs/advanced-usage/use-with-inertia.md b/docs/advanced-usage/use-with-inertia.md index ae4a5e9f2..ada92a266 100644 --- a/docs/advanced-usage/use-with-inertia.md +++ b/docs/advanced-usage/use-with-inertia.md @@ -15,7 +15,7 @@ return Inertia::render('Song', SongsData::from($song)); ## Lazy properties -This package supports [lazy](https://spatie.be/docs/laravel-data/v3/as-a-resource/lazy-properties) properties, which can be manually included or excluded. +This package supports [lazy](https://spatie.be/docs/laravel-data/v4/as-a-resource/lazy-properties) properties, which can be manually included or excluded. Inertia has a similar concept called [lazy data evaluation](https://inertiajs.com/partial-reloads#lazy-data-evaluation), where some properties wrapped in a closure only get evaluated and included in the response when explicitly asked. diff --git a/docs/as-a-data-transfer-object/casts.md b/docs/as-a-data-transfer-object/casts.md index 77b6dd6bb..1a6672015 100644 --- a/docs/as-a-data-transfer-object/casts.md +++ b/docs/as-a-data-transfer-object/casts.md @@ -118,4 +118,4 @@ Tip: we can also remove the `EnumCast` since the package will automatically cast ## Creating your own casts -It is possible to create your casts. You can read more about this in the [advanced chapter](/docs/laravel-data/v3/advanced-usage/creating-a-cast). +It is possible to create your casts. You can read more about this in the [advanced chapter](/docs/laravel-data/v4/advanced-usage/creating-a-cast). diff --git a/docs/as-a-data-transfer-object/collections.md b/docs/as-a-data-transfer-object/collections.md index 242ed4e6b..df1a492eb 100644 --- a/docs/as-a-data-transfer-object/collections.md +++ b/docs/as-a-data-transfer-object/collections.md @@ -101,6 +101,31 @@ There are a few requirements for this to work: - The method cannot be called **collect** - A **return type** must be defined +## Creating a data object with collection + +You can create a data object with a collection of data object just like you would create a data object with a nested data object: + +```php +use App\Data\SongData; +use Illuminate\Support\Collection; + +class AlbumData extends Data +{ + /** @var Collection */ + public Collection $songs; +} + +AlbumData::from([ + 'title' => 'Never Gonna Give You Up', + 'songs' => [ + ['title' => 'Never Gonna Give You Up', 'artist' => 'Rick Astley'], + ['title' => 'Giving Up on Love', 'artist' => 'Rick Astley'], + ] +]); +``` + +Since the collection type here is a `Collection`, the package will automatically convert the array into a collection of data objects. + ## DataCollection's, PaginatedDataCollection's and CursorPaginatedCollection's The package also provides a few collection classes which can be used to create collections of data objects, it was a requirement to use these classes in the past versions of the package when nesting data objects collections in data objects. This is no longer the case and there are still valid use cases for them. diff --git a/docs/as-a-data-transfer-object/computed.md b/docs/as-a-data-transfer-object/computed.md index 2c10f95f0..36ce88803 100644 --- a/docs/as-a-data-transfer-object/computed.md +++ b/docs/as-a-data-transfer-object/computed.md @@ -3,7 +3,7 @@ title: Computed values weight: 8 --- -Earlier we saw how default values can be set for a data object, the same approach can be used to set computed values, although slightly different: +Earlier we saw how default values can be set for a data object, sometimes you want to set a default value based on other properties. For example, you might want to set a `full_name` property based on a `first_name` and `last_name` property. You can do this by using a computed property: ```php use Spatie\LaravelData\Attributes\Computed; @@ -28,6 +28,8 @@ You can now do the following: SongData::from(['first_name' => 'Ruben', 'last_name' => 'Van Assche']); ``` +Please notice: the computed property won't be reevaluated when its dependencies change. If you want to update a computed property, you'll have to create a new object. + Again there are a few conditions for this approach: - You must always use a sole property, a property within the constructor definition won't work diff --git a/docs/as-a-data-transfer-object/creating-a-data-object.md b/docs/as-a-data-transfer-object/creating-a-data-object.md index 6bda54256..905eb84d2 100644 --- a/docs/as-a-data-transfer-object/creating-a-data-object.md +++ b/docs/as-a-data-transfer-object/creating-a-data-object.md @@ -160,7 +160,7 @@ will try to create itself from the following types: - An *Arrayable* by calling `toArray` on it - An *array* -This list can be extended using extra normalizers, find more about it [here](https://spatie.be/docs/laravel-data/v3/advanced-usage/normalizers). +This list can be extended using extra normalizers, find more about it [here](https://spatie.be/docs/laravel-data/v4/advanced-usage/normalizers). When a data object cannot be created using magical methods or the default methods, a `CannotCreateData` exception will be thrown. @@ -185,6 +185,6 @@ You can ignore the magical creation methods when creating a data object as such: SongData::withoutMagicalCreationFrom($song); ``` -## Advanced creation +## Advanced creation using factories -Internally this package is using a pipeline to create a data object from something. This pipeline exists of steps which transform properties into a correct structure and it can be completely customized. You can read more about it [here](/docs/laravel-data/v3/advanced-usage/pipeline). +It is possible to configure how a data object is created, whether it will be validated, which casts to use and more. You can read more about it [here](/docs/laravel-data/v4/advanced-usage/factories). diff --git a/docs/as-a-data-transfer-object/defaults.md b/docs/as-a-data-transfer-object/defaults.md index a083d4bc2..7bc5fe510 100644 --- a/docs/as-a-data-transfer-object/defaults.md +++ b/docs/as-a-data-transfer-object/defaults.md @@ -51,4 +51,4 @@ There are a few conditions for this approach: - You must always use a sole property, a property within the constructor definition won't work - The optional type is technically not required, but it's a good idea to use it otherwise the validation won't work -- Validation won't be performed on the default value, so make sure it's valid +- Validation won't be performed on the default value, so make sure it is valid diff --git a/docs/as-a-data-transfer-object/factories.md b/docs/as-a-data-transfer-object/factories.md new file mode 100644 index 000000000..dde8887d7 --- /dev/null +++ b/docs/as-a-data-transfer-object/factories.md @@ -0,0 +1,73 @@ +--- +title: Factories +weight: 10 +--- + +It is possible to automatically create data objects in all sorts of forms with this package. Sometimes a little bit more +control is required when a data object is being created. This is where factories come in. + +Factories allow you to create data objects like before but allow you to customize the creation process. + +For example, we can create a data object using a factory like this: + +```php +SongData::factory()->from(['title' => 'Never gonna give you up', 'artist' => 'Rick Astley']); +``` + +Collecting a bunch of data objects using a factory can be done as such: + +```php +SongData::factory()->collect(Song::all()) +``` + +## Disable property name mapping + +We saw [earlier](/docs/laravel-data/v4/as-a-data-transfer-object/mapping-property-names) that it is possible to map +property names when creating a data object from an array. This can be disabled when using a factory: + +```php +ContractData::factory()->withoutPropertyNameMapping()->from(['name' => 'Rick Astley', 'record_company' => 'RCA Records']); // record_company will not be mapped to recordCompany +``` + +## Changing the validation strategy + +By default, the package will only validate Requests when creating a data object it is possible to change the validation +strategy to always validate for each type: + +```php +SongData::factory()->alwaysValidate()->from(['title' => 'Never gonna give you up', 'artist' => 'Rick Astley']); +``` + +Or completely disable validation: + +```php +SongData::factory()->withoutValidation()->from(['title' => 'Never gonna give you up', 'artist' => 'Rick Astley']); +``` + +## Disabling magic methods + +A data object can be created +using [magic methods](/docs/laravel-data/v4/as-a-data-transfer-object/creating-a-data-object.md#magical-creation) , this can be disabled +when using a factory: + +```php +SongData::factory()->withoutMagicalCreation()->from('Never gonna give you up'); // Won't work since the magical method creation is disabled +``` + +It is also possible to ignore the magical creation methods when creating a data object as such: + +```php +SongData::factory()->ignoreMagicalMethod('fromString')->from('Never gonna give you up'); // Won't work since the magical method is ignored +``` + +## Adding additional global casts + +When creating a data object, it is possible to add additional casts to the data object: + +```php +SongData::factory()->withCast('string', StringToUpperCast::class)->from(['title' => 'Never gonna give you up', 'artist' => 'Rick Astley']); +``` + +These casts will not replace the other global casts defined in the `data.php` config file, they will though run before +the other global casts. You define them just like you would define them in the config file, the first parameter is the +type of the property that should be cast and the second parameter is the cast class. 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 6ba5be7bb..c089bc743 100644 --- a/docs/as-a-data-transfer-object/mapping-property-names.md +++ b/docs/as-a-data-transfer-object/mapping-property-names.md @@ -20,7 +20,7 @@ class ContractData extends Data Creating the data object can now be done as such: ```php -SongData::from(['name' => 'Rick Astley', 'record_company' => 'RCA Records']); +ContractData::from(['name' => 'Rick Astley', 'record_company' => 'RCA Records']); ``` Changing all property names in a data object to snake_case in the data the object is created from can be done as such: @@ -37,7 +37,7 @@ class ContractData extends Data } ``` -You can also use the `MapName` attribute when you want to combine input (see [transforming data objects](https://spatie.be/docs/laravel-data/v3/as-a-resource/from-data-to-resource#mapping-property-names)) and output property name mapping: +You can also use the `MapName` attribute when you want to combine input (see [transforming data objects](/docs/laravel-data/v4/as-a-resource/from-data-to-resource#mapping-property-names)) and output property name mapping: ```php #[MapName(SnakeCaseMapper::class)] diff --git a/docs/as-a-data-transfer-object/nesting.md b/docs/as-a-data-transfer-object/nesting.md index 434d9b4e5..b71613d84 100644 --- a/docs/as-a-data-transfer-object/nesting.md +++ b/docs/as-a-data-transfer-object/nesting.md @@ -115,11 +115,11 @@ The same is true for Laravel collections, but be sure to use two generic paramet ```php use App\Data\SongData; -use \Illuminate\Support\Collection; +use Illuminate\Support\Collection; class AlbumData extends Data { - /** @var Collection */ + /** @var Collection */ public Collection $songs; } ``` @@ -139,29 +139,3 @@ class AlbumData extends Data ``` This was the old way to define the type of data objects that will be stored within a collection. It is still supported, but we recommend using the annotation. - -### Creating a data object with collection - -You can create a data object with a collection of data object just like you would create a data object with a nested data object: - -```php -new AlbumData( - 'Never gonna give you up', - [ - new SongData('Never gonna give you up', 'Rick Astley'), - new SongData('Giving up on love', 'Rick Astley'), - ] -); -``` - -Or use the magical creation which will automatically create the data objects for you and also works with collections: - -```php -AlbumData::from([ - 'title' => 'Never Gonna Give You Up', - 'songs' => [ - ['title' => 'Never Gonna Give You Up', 'artist' => 'Rick Astley'], - ['title' => 'Giving Up on Love', 'artist' => 'Rick Astley'], - ] -]); -``` diff --git a/docs/as-a-data-transfer-object/optional-properties.md b/docs/as-a-data-transfer-object/optional-properties.md index b37beeea8..94f10af8b 100644 --- a/docs/as-a-data-transfer-object/optional-properties.md +++ b/docs/as-a-data-transfer-object/optional-properties.md @@ -45,7 +45,8 @@ class SongData extends Data ) { } - public static function fromTitle(string $title): static{ + public static function fromTitle(string $title): static + { return new self($title, Optional::create()); } } diff --git a/docs/as-a-data-transfer-object/request-to-data-object.md b/docs/as-a-data-transfer-object/request-to-data-object.md index 621b3008e..971fafe52 100644 --- a/docs/as-a-data-transfer-object/request-to-data-object.md +++ b/docs/as-a-data-transfer-object/request-to-data-object.md @@ -63,335 +63,9 @@ class UpdateSongController } ``` -We have a complete section within these docs dedicated to validation, you can find it [here](/docs/laravel-data/v3/validation). +We have a complete section within these docs dedicated to validation, you can find it [here](/docs/laravel-data/v4/validation). -## Using validation - -When creating a data object from a request, the package can also validate the values from the request that will be used -to construct the data object. - -The package automatically infers rules for certain properties. For example, a `?string` property will automatically have the `nullable` and `string` rules. - -Be aware, first the rules will be generated from the data object you're trying to create, then if the validation is successful a data object will be created with the validated data. This means validation will be run before a data object exists. - -It is possible to add extra rules as attributes to properties of a data object: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - #[Max(20)] - public string $artist, - ) { - } -} -``` - -When you provide an artist with a length of more than 20 characters, the validation will fail just like it would when -you created a custom request class for the endpoint. - -You can find a complete list of available rules [here](/docs/laravel-data/v3/advanced-usage/validation-attributes). - -If you want to have full control over the rules, you can also define them in a dedicated `rules` method on the data object (see later). - -### Referencing route parameters - -Sometimes you need a value within your validation attribute which is a route parameter. -Like the example below where the id should be unique ignoring the current id: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - #[Unique('songs', ignore: new RouteParameterReference('song'))] - public int $id, - ) { - } -} -``` - -If the parameter is a model and another property should be used, then you can do the following: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - #[Unique('songs', ignore: new RouteParameterReference('song', 'uuid'))] - public string $uuid, - ) { - } -} -``` - -### Referencing other fields - -It is possible to reference other fields in validation attributes: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - #[RequiredUnless('title', 'Never Gonna Give You Up')] - public string $artist, - ) { - } -} -``` - -These references are always relative to the current data object. So when being nested like this: - -```php -class AlbumData extends Data -{ - public function __construct( - public string $album_name, - public SongData $song, - ) { - } -} -``` - -The generated rules will look like this: - -```php -[ - 'album_name' => ['required', 'string'], - 'songs' => ['required', 'array'], - 'song.title' => ['required', 'string'], - 'song.artist' => ['string', 'required_if:song.title,"Never Gonna Give You Up"'], -] -``` - -If you want to reference fields starting from the root data object you can do the following: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - #[RequiredUnless(new FieldReference('album', fromRoot: true), 'Whenever You Need Somebody')] - public string $artist, - ) { - } -} -``` - -The rules will now look like this: - -```php -[ - 'album_name' => ['required', 'string'], - 'songs' => ['required', 'array'], - 'song.title' => ['required', 'string'], - 'song.artist' => ['string', 'required_if:album_name,"Whenever You Need Somebody"'], -] -``` - -### Rule attribute - -One special attribute is the `Rule` attribute. With it, you can write rules just like you would when creating a custom -Laravel request: - -```php -// using an array -#[Rule(['required', 'string'])] -public string $property - -// using a string -#[Rule('required|string')] -public string $property - -// using multiple arguments -#[Rule('required', 'string')] -public string $property -``` - -It is also possible to write rules down in a dedicated method on the data object. This can come in handy when you want -to construct a custom rule object which isn't possible with attributes: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function rules(): array - { - return [ - 'title' => ['required', 'string'], - 'artist' => ['required', 'string'], - ]; - } -} -``` - -By overwriting a property's rules within the `rules` method, no other rules will be inferred automatically anymore for that property. - -> Always use the array syntax for defining rules and not a single string which spits the rules by | characters. -> This is needed when using regexes those | can be seen as part of the regex - -It is even possible to use the validationAttribute objects within the `rules` method: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function rules(): array - { - return [ - 'title' => [new Required(), new StringType()], - 'artist' => [new Required(), new StringType()], - ]; - } -} -``` - -Rules defined within the `rules` method will always overwrite automatically generated rules. - -You can even add dependencies to be automatically injected: - -```php -use SongSettingsRepository; - -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function rules(SongSettingsRepository $settings): array - { - return [ - 'title' => [new RequiredIf($settings->forUser(auth()->user())->title_required), new StringType()], - 'artist' => [new Required(), new StringType()], - ]; - } -} -``` - -Sometimes a bit more context is required, in such case a `ValidationContext` parameter can be injected as such: -Additionally, if you need to access the data payload, you can use `$payload` parameter: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function rules(ValidationContext $context): array - { - return [ - 'title' => ['required'], - 'artist' => Rule::requiredIf($context->fullPayload['title'] !== 'Never Gonna Give You Up'), - ]; - } -} -``` - -By default, the provided payload is the whole request payload provided to the data object. -If you want to generate rules in nested data objects then a relative payload can be more useful: - -```php -class AlbumData extends Data -{ - public function __construct( - public string $title, - #[DataCollectionOf(SongData::class)] - public DataCollection $songs, - ) { - } -} - -class SongData extends Data -{ - public function __construct( - public string $title, - public ?string $artist, - ) { - } - - public static function rules(ValidationContext $context): array - { - return [ - 'title' => ['required'], - 'artist' => Rule::requiredIf($context->payload['title'] !== 'Never Gonna Give You Up'), - ]; - } -} -``` - -When providing such payload: - -```php -[ - 'title' => 'Best songs ever made', - 'songs' => [ - ['title' => 'Never Gonna Give You Up'], - ['title' => 'Heroes', 'artist' => 'David Bowie'], - ], -]; -``` - -The rules will be: - -```php -[ - 'title' => ['string', 'required'], - 'songs' => ['present', 'array'], - 'songs.*.title' => ['string', 'required'], - 'songs.*.artist' => ['string', 'nullable'], - 'songs.*' => [NestedRules(...)], -] -``` - -It is also possible to retrieve the current path in the data object chain we're generating rules for right now by calling `$context->path`. In the case of our previous example this would be `songs.0` and `songs.1`; - -Make sure the name of the parameter is `$context` in the `rules` method, otherwise no context will be injected. - -## Mapping a request onto a data object - -By default, the package will do a one to one mapping from request to the data object, which means that for each property -within the data object, a value with the same key will be searched within the request values. - -If you want to customize this mapping, then you can always add a magical creation method like this: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function fromRequest(Request $request): static - { - return new self( - $request->input('title_of_song'), - $request->input('artist_name') - ); - } -} -``` - -### Getting the data object filled with request data from anywhere +## Getting the data object filled with request data from anywhere You can resolve a data object from the container. @@ -402,278 +76,6 @@ app(SongData::class); We resolve a data object from the container, it's properties will allready be filled by the values of the request with matching key names. If the request contains data that is not compatible with the data object, a validation exception will be thrown. -### Automatically inferring rules for properties - -Since we have such strongly typed data objects, we can infer some validation rules from them. Rule inferrers will take -information about the type of the property and will create validation rules from that information. - -Rule inferrers are configured in the `data.php` config file: - -```php -/* - * Rule inferrers can be configured here. They will automatically add - * validation rules to properties of a data object based upon - * the type of the property. - */ -'rule_inferrers' => [ - Spatie\LaravelData\RuleInferrers\SometimesRuleInferrer::class, - Spatie\LaravelData\RuleInferrers\NullableRuleInferrer::class, - Spatie\LaravelData\RuleInferrers\RequiredRuleInferrer::class, - Spatie\LaravelData\RuleInferrers\BuiltInTypesRuleInferrer::class, - Spatie\LaravelData\RuleInferrers\AttributesRuleInferrer::class, -], -``` - -By default, four rule inferrers are enabled: - -- **SometimesRuleInferrer** will add a `sometimes` rule when the property is optional -- **NullableRuleInferrer** will add a `nullable` rule when the property is nullable -- **RequiredRuleInferrer** will add a `required` rule when the property is not nullable -- **BuiltInTypesRuleInferrer** will add a rules which are based upon the built-in php types: - - An `int` or `float` type will add the `numeric` rule - - A `bool` type will add the `boolean` rule - - A `string` type will add the `string` rule - - A `array` type will add the `array` rule -- **AttributesRuleInferrer** will make sure that rule attributes we described above will also add their rules - -It is possible to write your rule inferrers. You can find more -information [here](/docs/laravel-data/v3/advanced-usage/creating-a-rule-inferrer). - -### Skipping validation - -Sometimes you don't want properties to be automatically validated, for instance when you're manually overwriting the -rules method like this: - -```php -class SongData extends Data -{ - public function __construct( - public string $name, - ) { - } - - public static function fromRequest(Request $request): static{ - return new self("{$request->input('first_name')} {$request->input('last_name')}") - } - - public static function rules(): array - { - return [ - 'first_name' => ['required', 'string'], - 'last_name' => ['required', 'string'], - ]; - } -} -``` - -When a request is being validated, the rules will look like this: - -```php -[ - 'name' => ['required', 'string'], - 'first_name' => ['required', 'string'], - 'last_name' => ['required', 'string'], -] -``` - -We know we never want to validate the `name` property since it won't be in the request payload, this can be done as -such: - -```php -class SongData extends Data -{ - public function __construct( - #[WithoutValidation] - public string $name, - ) { - } -} -``` - -Now the validation rules will look like this: - -```php -[ - 'first_name' => ['required', 'string'], - 'last_name' => ['required', 'string'], -] -``` - -### Overwriting the validator - -Before validating the values, it is possible to plugin into the validator. This can be done as such: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function withValidator(Validator $validator): void - { - $validator->after(function ($validator) { - $validator->errors()->add('field', 'Something is wrong with this field!'); - }); - } -} -``` - -### Overwriting messages - -It is possible to overwrite the error messages that will be returned when an error fails: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function messages(): array - { - return [ - 'title.required' => 'A title is required', - 'artist.required' => 'An artist is required', - ]; - } -} -``` - -### Overwriting attributes - -In the default Laravel validation rules, you can overwrite the name of the attribute as such: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function attributes(): array - { - return [ - 'title' => 'titel', - 'artist' => 'artiest', - ]; - } -} -``` - -### Overwriting other validation functionality - -Next to overwriting the validator, attributes and messages it is also possible to overwrite the following functionality. - -The redirect when a validation failed: - -```php -class SongData extends Data -{ - // ... - - public static function redirect(): string - { - return action(HomeController::class); - } -} -``` - -Or the route which will be used to redirect after a validation failed: - -```php -class SongData extends Data -{ - // ... - - public static function redirectRoute(): string - { - return 'home'; - } -} -``` - -Whether to stop validating on the first failure: - -```php -class SongData extends Data -{ - // ... - - public static function stopOnFirstFailure(): bool - { - return true; - } -} -``` - -The name of the error bag: - -```php -class SongData extends Data -{ - // ... - - public static function errorBag(): string - { - return 'never_gonna_give_an_error_up'; - } -} -``` - -### Using dependencies in overwritten functionality - -You can also provide dependencies to be injected in the overwritten validator functionality methods like `messages` -, `attributes`, `redirect`, `redirectRoute`, `stopOnFirstFailure`, `errorBag`: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function attributes( - ValidationAttributesLanguageRepository $validationAttributesLanguageRepository - ): array - { - return [ - 'title' => $validationAttributesLanguageRepository->get('title'), - 'artist' => $validationAttributesLanguageRepository->get('artist'), - ]; - } -} -``` - -## Authorizing a request - -Just like with Laravel requests, it is possible to authorize an action for certain people only: - -```php -class SongData extends Data -{ - public function __construct( - public string $title, - public string $artist, - ) { - } - - public static function authorize(): bool - { - return Auth::user()->name === 'Ruben'; - } -} -``` - -If the method returns `false`, then an `AuthorizationException` is thrown. ## Validating a collection of data objects: @@ -704,42 +106,3 @@ In this case the validation rules for `AlbumData` would look like this: 'songs.*.artist' => ['required', 'string'], ] ``` - -## Validating a data object without request - -It is also possible to validate values for a data object without using a request: - -```php -SongData::validate(['title' => 'Never gonna give you up', 'artist' => 'Rick Astley']); -``` - -This will either throw a `ValidationException` or return a validated version of the payload. - -It is possible to return a data object when the payload is valid when calling: - -```php -SongData::validateAndCreate(['title' => 'Never gonna give you up', 'artist' => 'Rick Astley']); -``` - -## Retrieving validation rules for a data object - -You can retrieve the validation rules a data object will generate as such: - -```php -AlbumData::getValidationRules($payload); -``` - -This will produce the following array with rules: - -```php -[ - 'title' => ['required', 'string'], - 'songs' => ['required', 'array'], - 'songs.*.title' => ['required', 'string'], - 'songs.*.artist' => ['required', 'string'], -] -``` - -## Payload requirement - -We suggest always to provide a payload when generating validation rules. Because such a payload is used to determine which rules will be generated and which can be skipped. diff --git a/docs/as-a-resource/appending-properties.md b/docs/as-a-resource/appending-properties.md new file mode 100644 index 000000000..e6cae89b2 --- /dev/null +++ b/docs/as-a-resource/appending-properties.md @@ -0,0 +1,89 @@ +--- +title: Appending properties +weight: 4 +--- + +It is possible to add some extra properties to your data objects when they are transformed into a resource: + +```php +SongData::from(Song::first())->additional([ + 'year' => 1987, +]); +``` + +This will output the following array: + +```php +[ + 'name' => 'Never gonna give you up', + 'artist' => 'Rick Astley', + 'year' => 1987, +] +``` + +When using a closure, you have access to the underlying data object: + +```php +SongData::from(Song::first())->additional([ + 'slug' => fn(SongData $songData) => Str::slug($songData->title), +]); +``` + +Which produces the following array: + +```php +[ + 'name' => 'Never gonna give you up', + 'artist' => 'Rick Astley', + 'slug' => 'never-gonna-give-you-up', +] +``` + +It is also possible to add extra properties by overwriting the `with` method within your data object: + +```php +class SongData extends Data +{ + public function __construct( + public int $id, + public string $title, + public string $artist + ) { + } + + public static function fromModel(Song $song): self + { + return new self( + $song->id, + $song->title, + $song->artist + ); + } + + public function with() + { + return [ + 'endpoints' => [ + 'show' => action([SongsController::class, 'show'], $this->id), + 'edit' => action([SongsController::class, 'edit'], $this->id), + 'delete' => action([SongsController::class, 'delete'], $this->id), + ] + ]; + } +} +``` + +Now each transformed data object contains an `endpoints` key with all the endpoints for that data object: + +```php +[ + 'id' => 1, + 'name' => 'Never gonna give you up', + 'artist' => 'Rick Astley', + 'endpoints' => [ + 'show' => 'https://spatie.be/songs/1', + 'edit' => 'https://spatie.be/songs/1', + 'delete' => 'https://spatie.be/songs/1', + ], +] +``` diff --git a/docs/as-a-resource/from-data-to-array.md b/docs/as-a-resource/from-data-to-array.md new file mode 100644 index 000000000..d58449a5d --- /dev/null +++ b/docs/as-a-resource/from-data-to-array.md @@ -0,0 +1,132 @@ +--- +title: From data to array +weight: 1 +--- + +A data object can automatically be transformed into an array as such: + +```php +SongData::from(Song::first())->toArray(); +``` + +Which will output the following array: + +```php +[ + 'name' => 'Never gonna give you up', + 'artist' => 'Rick Astley' +] +``` + +You can manually transform a data object to JSON: + +```php +SongData::from(Song::first())->toJson(); +``` + +Or transform a data object to an array: + +```php +SongData::from(Song::first())->toArray(); +``` + +By default, calling `toArray` on a data object will transform all properties to an array. This means that nested data objects and collections of data objects will also be transformed to arrays. Other complex types like `Carbon`, `DateTime`, `Enums`, ... will be transformed into a string. We'll see in the [transformers](/docs/laravel-data/v4/as-a-resource/transformers) section how to configure and customize this behavior. + +If you only want to transform a data object to an array without transforming the properties, you can call the `all` method: + +```php +SongData::from(Song::first())->all(); +``` + +## Using collections + +Here's how to create a collection of data objects: + +```php +SongData::collect(Song::all()); +``` + +A collection can be transformed to array: + +```php +SongData::collect(Song::all())->toArray(); +``` + +Which will output the following array: + +```php +[ + [ + "name": "Never Gonna Give You Up", + "artist": "Rick Astley" + ], + [ + "name": "Giving Up on Love", + "artist": "Rick Astley" + ] +] +``` + +## Nesting + +It is possible to nest data objects. + +```php +class UserData extends Data +{ + public function __construct( + public string $title, + public string $email, + public SongData $favorite_song, + ) { + } + + public static function fromModel(User $user): self + { + return new self( + $user->title, + $user->email, + SongData::from($user->favorite_song) + ); + } +} +``` + +When transformed to an array, this will look like the following: + +```php +[ + "name": "Ruben", + "email": "ruben@spatie.be", + "favorite_song": [ + "name" : "Never Gonna Give You Up", + "artist" : "Rick Astley" + ] +] +``` + +You can also nest a collection of data objects: + +```php +class AlbumData extends Data +{ + /** + * @param Collection $songs + */ + public function __construct( + public string $title, + public array $songs, + ) { + } + + public static function fromModel(Album $album): self + { + return new self( + $album->title, + SongData::collect($album->songs) + ); + } +} +``` + +As always, don't forget to type collections of data objects by annotation or the `DataCollectionOf` attribute, this is essential to transform these collections correctly. diff --git a/docs/as-a-resource/from-data-to-resource.md b/docs/as-a-resource/from-data-to-resource.md index e5f53cb8d..efbe389fe 100644 --- a/docs/as-a-resource/from-data-to-resource.md +++ b/docs/as-a-resource/from-data-to-resource.md @@ -1,6 +1,6 @@ --- title: From data to resource -weight: 1 +weight: 2 --- A data object will automatically be transformed to a JSON response when returned in a controller: @@ -24,164 +24,15 @@ The JSON then will look like this: } ``` -You can manually transform a data object to JSON: +### Collections -```php -SongData::from(Song::first())->toJson(); -``` - -Or transform a data object to an array: - -```php -SongData::from(Song::first())->toArray(); -``` - -## Transforming empty objects - -When creating a new model, you probably want to provide a blueprint to the frontend with the required data to create a model. For example: - -```json -{ - "name": null, - "artist": null -} -``` - -You could make each property of the data object nullable like this: - -```php -class SongData extends Data -{ - public function __construct( - public ?string $title, - public ?string $artist, - ) { - } - - // ... -} -``` - -This approach would work, but as soon as the model is created, the properties won't be `null`, which doesn't follow our data model. So it is considered a bad practice. - -That's why in such cases, you can return an empty representation of the data object: - -```php -class SongsController -{ - public function create(): array - { - return SongData::empty(); - } -} -``` - -Which will output the following JSON: - -```json -{ - "name": null, - "artist": null -} -``` - -The `empty` method on a data object will return an array with default empty values for the properties in the data object. - -It is possible to change the default values within this array by providing them in the constructor of the data object: - - ```php - class SongData extends Data -{ - public function __construct( - public string $title = 'Title of the song here', - public string $artist = "An artist", - ) { - } - - // ... -} - ``` - -Now when we call `empty`, our JSON looks like this: - -```json -{ - "name": "Title of the song here", - "artist": "An artist" -} -``` - -You can also pass defaults within the `empty` call: - -```php -SongData::empty([ - 'name' => 'Title of the song here', - 'artist' => 'An artist' -]); -``` - -## Mapping property names - -Sometimes you might want to change the name of a property, with attributes this is possible: - -```php -class ContractData extends Data -{ - public function __construct( - public string $name, - #[MapOutputName('record_company')] - public string $recordCompany, - ) { - } -} -``` - -Now our JSON looks like this: - -```json -{ - "name": "Rick Astley", - "record_company": "RCA Records" -} -``` - -Changing all property names in a data object to snake_case as output data can be done as such: - -```php -#[MapOutputName(SnakeCaseMapper::class)] -class ContractData extends Data -{ - public function __construct( - public string $name, - public string $recordCompany, - ) { - } -} -``` - -You can also use the `MapName` attribute when you want to combine input and output property name mapping: +Returning a data collection from the controller like this: ```php -#[MapName(SnakeCaseMapper::class)] -class ContractData extends Data -{ - public function __construct( - public string $name, - public string $recordCompany, - ) { - } -} +SongData::collect(Song::all()); ``` -## Using collections - -Here's how to create a collection of data objects: - -```php -SongData::collection(Song::all()); -``` - -A collection can be returned in a controller and will automatically be transformed to JSON: +Will return a collection automatically transformed to JSON: ```json [ @@ -196,16 +47,12 @@ A collection can be returned in a controller and will automatically be transform ] ``` -You can also transform a collection of data objects into an array: - -```php -SongData::collection(Song::all())->toArray(); -``` +### Paginators -It is also possible to provide a paginated collection: +It is also possible to provide a paginator: ```php -SongData::collection(Song::paginate()); +SongData::collect(Song::paginate()); ``` The data object is smart enough to create a paginated response from this with links to the next, previous, last, ... pages: @@ -238,174 +85,92 @@ The data object is smart enough to create a paginated response from this with li } ``` -It is possible to change data objects in a collection: - -```php -$allSongs = Song::all(); - -SongData::collection($allSongs)->through(function(SongData $song){ - $song->artist = 'Abba'; - - return $song; -}); -``` - -You can filter non-paginated collections: -```php -SongData::collection($allSongs)->filter( - fn(SongData $song) => $song->artist === 'Rick Astley' -); -``` - -## Nesting - -It is possible to nest data objects. - -```php -class UserData extends Data -{ - public function __construct( - public string $title, - public string $email, - public SongData $favorite_song, - ) { - } - - public static function fromModel(User $user): self - { - return new self( - $user->title, - $user->email, - SongData::from($user->favorite_song) - ); - } -} -``` +## Transforming empty objects -When transformed to JSON, this will look like the following: +When creating a new model, you probably want to provide a blueprint to the frontend with the required data to create a model. For example: ```json { - "name": "Ruben", - "email": "ruben@spatie.be", - "favorite_song": { - "name" : "Never Gonna Give You Up", - "artist" : "Rick Astley" - } + "name": null, + "artist": null } ``` -You can also nest a collection of resources: +You could make each property of the data object nullable like this: ```php -class AlbumData extends Data +class SongData extends Data { public function __construct( - public string $title, - #[DataCollectionOf(SongData::class)] - public DataCollection $songs, + public ?string $title, + public ?string $artist, ) { } - public static function fromModel(Album $album): self - { - return new self( - $album->title, - SongData::collection($album->songs) - ); - } + // ... } ``` -We're using a `DataCollection` type here in the data object definition. It would be best always to use a `DataCollection` type when nesting a collection of data objects. The package requires this for internal state management. - -## Appending properties +This approach would work, but as soon as the model is created, the properties won't be `null`, which doesn't follow our data model. So it is considered a bad practice. -It is possible to add some extra properties to your data objects when they are transformed into a resource: +That's why in such cases, you can return an empty representation of the data object: ```php -SongData::from(Song::first())->additional([ - 'year' => 1987, -]); -``` - -This will output the following JSON: - -```json +class SongsController { - "name": "Never gonna give you up", - "artist": "Rick Astley", - "year": 1987 + public function create(): array + { + return SongData::empty(); + } } ``` -When using a closure, you have access to the underlying data object: - -```php -SongData::from(Song::first())->additional([ - 'slug' => fn(SongData $songData) => Str::slug($songData->title), -]); -``` - -Which produces the following: +Which will output the following JSON: ```json { - "name": "Never gonna give you up", - "artist": "Rick Astley", - "slug": "never-gonna-give-you-up" + "name": null, + "artist": null } ``` -It is also possible to add extra properties by overwriting the `with` method within your data object: +The `empty` method on a data object will return an array with default empty values for the properties in the data object. -```php -class SongData extends Data +It is possible to change the default values within this array by providing them in the constructor of the data object: + + ```php + class SongData extends Data { public function __construct( - public int $id, - public string $title, - public string $artist + public string $title = 'Title of the song here', + public string $artist = "An artist", ) { } - - public static function fromModel(Song $song): self - { - return new self( - $song->id, - $song->title, - $song->artist - ); - } - public function with(){ - return [ - 'endpoints' => [ - 'show' => action([SongsController::class, 'show'], $this->id), - 'edit' => action([SongsController::class, 'edit'], $this->id), - 'delete' => action([SongsController::class, 'delete'], $this->id), - ] - ]; - } + // ... } -``` + ``` -Now each transformed data object contains an `endpoints` key with all the endpoints for that data object: +Now when we call `empty`, our JSON looks like this: ```json { - "id": 1, - "name": "Never gonna give you up", - "artist": "Rick Astley", - "endpoints": { - "show": "https://spatie.be/songs/1", - "edit": "https://spatie.be/songs/1", - "delete": "https://spatie.be/songs/1" - } + "name": "Title of the song here", + "artist": "An artist" } +``` + +You can also pass defaults within the `empty` call: + +```php +SongData::empty([ + 'name' => 'Title of the song here', + 'artist' => 'An artist' +]); ``` + ## Response status code -When a resource is being returned from a controller, the status code of the response will automatically be set to `201 CREATED` when Laravel data detectes that the request's method is `POST`. In all other cases, `200 OK` will be returned. +When a resource is being returned from a controller, the status code of the response will automatically be set to `201 CREATED` when Laravel data detects that the request's method is `POST`. In all other cases, `200 OK` will be returned. diff --git a/docs/as-a-resource/lazy-properties.md b/docs/as-a-resource/lazy-properties.md index 32b7bf2a2..d812b5482 100644 --- a/docs/as-a-resource/lazy-properties.md +++ b/docs/as-a-resource/lazy-properties.md @@ -1,6 +1,6 @@ --- title: Including and excluding properties -weight: 2 +weight: 6 --- Sometimes you don't want all the properties included when transforming a data object to an array, for example: diff --git a/docs/as-a-resource/mapping-property-names.md b/docs/as-a-resource/mapping-property-names.md new file mode 100644 index 000000000..b656c0a2e --- /dev/null +++ b/docs/as-a-resource/mapping-property-names.md @@ -0,0 +1,73 @@ +--- +title: Mapping property names +weight: 3 +--- + +Sometimes you might want to change the name of a property in the transformed payload, with attributes this is possible: + +```php +class ContractData extends Data +{ + public function __construct( + public string $name, + #[MapOutputName('record_company')] + public string $recordCompany, + ) { + } +} +``` + +Now our array looks like this: + +```php +[ + 'name' => 'Rick Astley', + 'record_company' => 'RCA Records', +] +``` + +Changing all property names in a data object to snake_case as output data can be done as such: + +```php +#[MapOutputName(SnakeCaseMapper::class)] +class ContractData extends Data +{ + public function __construct( + public string $name, + public string $recordCompany, + ) { + } +} +``` + +You can also use the `MapName` attribute when you want to combine input and output property name mapping: + +```php +#[MapName(SnakeCaseMapper::class)] +class ContractData extends Data +{ + public function __construct( + public string $name, + public string $recordCompany, + ) { + } +} +``` + +You can now create a data object as such: + +```php +$contract = new ContractData( + name: 'Rick Astley', + record_company: 'RCA Records', +); +``` + +And a transformed version of the data object will look like this: + +```php +[ + 'name' => 'Rick Astley', + 'record_company' => 'RCA Records', +] +``` diff --git a/docs/as-a-resource/transformers.md b/docs/as-a-resource/transformers.md index f97e08f95..988184bff 100644 --- a/docs/as-a-resource/transformers.md +++ b/docs/as-a-resource/transformers.md @@ -1,6 +1,6 @@ --- title: Transforming data -weight: 4 +weight: 7 --- Each property of a data object should be transformed into a usable type to communicate via JSON. @@ -43,7 +43,7 @@ class ArtistData extends Data{ Next to a `DateTimeInterfaceTransformer` the package also ships with an `ArrayableTransformer` that transforms an `Arrayable` object to an array. -It is possible to create transformers for your specific types. You can find more info [here](/docs/laravel-data/v3/advanced-usage/creating-a-transformer). +It is possible to create transformers for your specific types. You can find more info [here](/docs/laravel-data/v4/advanced-usage/creating-a-transformer). ## Global transformers diff --git a/docs/as-a-resource/wrapping.md b/docs/as-a-resource/wrapping.md index 1645e08ca..d0c427554 100644 --- a/docs/as-a-resource/wrapping.md +++ b/docs/as-a-resource/wrapping.md @@ -1,6 +1,6 @@ --- title: Wrapping -weight: 3 +weight: 5 --- By default, when a data object is transformed into JSON in your controller it looks like this: @@ -61,7 +61,7 @@ Or you can set a global wrap key inside the `data.php` config file: Collections can be wrapped just like data objects: ```php -SongData::collection(Song::all())->wrap('data'); +SongData::collect(Song::all())->wrap('data'); ``` The JSON will now look like this: @@ -84,7 +84,7 @@ The JSON will now look like this: It is possible to set the data key in paginated collections: ```php -SongData::collection(Song::paginate())->wrap('paginated_data'); +SongData::collect(Song::paginate())->wrap('paginated_data'); ``` Which will let the JSON look like this: @@ -160,7 +160,7 @@ UserData::from(User::first())->wrap('data'); A data collection inside a data object WILL get wrapped when a wrapping key is set: ```php -use Spatie\LaravelData\Attributes\DataCollectionOf; +use Spatie\LaravelData\Attributes\DataCollectionOf;use Spatie\LaravelData\DataCollection; class AlbumData extends Data { @@ -175,7 +175,7 @@ class AlbumData extends Data { return new self( $album->title, - SongData::collection($album->songs)->wrap('data') + SongData::collect($album->songs, DataCollection::class)->wrap('data') ); } } diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index a1bb11602..284b3218d 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -5,7 +5,7 @@ weight: 1 In this quickstart, we'll guide you through the most important functionalities of the package and how to use them. -First, you should [install the package](/docs/laravel-data/v3/installation-setup). +First, you should [install the package](/docs/laravel-data/v4/installation-setup). We will create a blog with different posts, so let's start with the `PostData` object. A post has a title, some content, a status and a date when it was published: @@ -189,7 +189,7 @@ As you can see, we're missing the `date` rule on the `published_at` property. By - `array` when a property type is `array` - `enum:*` when a property type is a native enum -You can read more about the process of automated rule generation [here](/docs/laravel-data/v3/as-a-data-transfer-object/request-to-data-object#content-automatically-inferring-rules-for-properties-1). +You can read more about the process of automated rule generation [here](/docs/laravel-data/v4/as-a-data-transfer-object/request-to-data-object#content-automatically-inferring-rules-for-properties-1). We can easily add the date rule by using an attribute to our data object: @@ -232,7 +232,7 @@ array:4 [ ] ``` -There are [tons](/docs/laravel-data/v3/advanced-usage/validation-attributes) of validation rule attributes you can add to data properties. There's still much more you can do with validating data objects. Read more about it [here](/docs/laravel-data/v3/as-a-data-transfer-object/request-to-data-object#validating-a-request). +There are [tons](/docs/laravel-data/v4/advanced-usage/validation-attributes) of validation rule attributes you can add to data properties. There's still much more you can do with validating data objects. Read more about it [here](/docs/laravel-data/v4/as-a-data-transfer-object/request-to-data-object#validating-a-request). Tip: By default, when creating a data object in a non request context, no validation is executed: @@ -275,7 +275,7 @@ It is possible to define casts within the `data.php` config file. By default, th This code means that if a class property is of type `DateTime`, `Carbon`, `CarbonImmutable`, ... it will be automatically cast. -You can create your own casts; read more about it [here](/docs/laravel-data/v3/advanced-usage/creating-a-cast). +You can create your own casts; read more about it [here](/docs/laravel-data/v4/advanced-usage/creating-a-cast). ### Local casts @@ -310,7 +310,7 @@ use Str; class ImageCast implements Cast { - public function cast(DataProperty $property, mixed $value, array $context): Image + public function cast(DataProperty $property, mixed $value, array $properties, CreationContext $context): Image { // Scenario A if ($value instanceof UploadedFile) { @@ -356,7 +356,7 @@ class PostData extends Data } ``` -You can read more about casting [here](/docs/laravel-data/v3/as-a-data-transfer-object/casts). +You can read more about casting [here](/docs/laravel-data/v4/as-a-data-transfer-object/casts). ## Customizing the creation of a data object @@ -406,44 +406,54 @@ class PostData extends Data ``` Magic creation methods allow you to create data objects from any type by passing them to the `from` method of a data -object, you can read more about it [here](/laravel-data/v3/as-a-data-transfer-object/creating-a-data-object#magical-creation). +object, you can read more about it [here](/laravel-data/v4/as-a-data-transfer-object/creating-a-data-object#magical-creation). It can be convenient to transform more complex models than our `Post` into data objects because you can decide how a model would be mapped onto a data object. -## Nesting data objects and collections +## Nesting data objects and arrays of data objects -Now that we have a fully functional post-data object. We're going to create a new data object, `AuthorData`, that will store the name of an author and a collection of posts the author wrote: +Now that we have a fully functional post-data object. We're going to create a new data object, `AuthorData`, that will store the name of an author and an array of posts the author wrote: ```php use Spatie\LaravelData\Attributes\DataCollectionOf; class AuthorData extends Data { + /** + * @param array $posts + */ public function __construct( public string $name, - #[DataCollectionOf(PostData::class)] - public DataCollection $posts + public array $posts ) { } } ``` -Instead of using an array to store all the posts, we use a `DataCollection .`This will be very useful later on! The package always needs to know what type of data is stored in a `DataCollection`, so we use the `DataCollectionOf` attribute to tell it is a collection of `PostData` objects. +Notice that we've typed the `$posts` property as an array of `PostData` objects using a docblock. This will be very useful later on! The package always needs to know what type of data objects are stored in an array. Off course, when you're storing other types then data objects this is not required but recommended. We can now create an author object as such: ```php new AuthorData( 'Ruben Van Assche', - PostData::collection([ - new PostData('Hello laravel-data', 'This is an introduction post for the new package,' PostStatus::draft, null, null), - new PostData('What is a data object', 'How does it work?', PostStatus::draft, null, null), + PostData::collect([ + [ + 'title' => 'Hello laravel-data', + 'content' => 'This is an introduction post for the new package', + 'status' => PostStatus::draft, + ], + [ + 'title' => 'What is a data object', + 'content' => 'How does it work', + 'status' => PostStatus::published, + ], ]) ); ``` -As you can see, the `collection` method can create a new `DataCollection` of the `PostData` object. +As you can see, the `collect` method can create an array of the `PostData` objects. But there's another way; thankfully, our `from` method makes this process even more straightforward: @@ -465,9 +475,7 @@ AuthorData::from([ ]); ``` -The data object is smart enough to convert an array of posts into a data collection of post data. Mapping data coming from the front end was never that easy! - -You can do a lot more with data collections. Read more about it [here](/docs/laravel-data/v3/as-a-data-transfer-object/collections). +The data object is smart enough to convert an array of posts into an array of post data. Mapping data coming from the front end was never that easy! ### Nesting objects @@ -674,12 +682,12 @@ This `DateTimeInterfaceTransformer` is registered in the `data.php` config file ], ``` -Rember the image object we created earlier; we stored a file size and filename in the object. But that could be more useful; let's provide the URL to the file when transforming the object. Just like casts, transformers also can be local. Let's implement one for `Image`: +Remember the image object we created earlier; we stored a file size and filename in the object. But that could be more useful; let's provide the URL to the file when transforming the object. Just like casts, transformers also can be local. Let's implement one for `Image`: ```php class ImageTransformer implements Transformer { - public function transform(DataProperty $property, mixed $value): string + public function transform(DataProperty $property, mixed $value, TransformationContext $context): string { if (! $value instanceof Image) { throw new Exception("Not an image"); @@ -739,7 +747,7 @@ Which leads to the following JSON: } ``` -You can read more about transformers [here](/docs/laravel-data/v3/as-a-resource/transformers). +You can read more about transformers [here](/docs/laravel-data/v4/as-a-resource/transformers). ## Generating a blueprint @@ -842,10 +850,12 @@ This functionality can be achieved with lazy properties. Lazy properties are onl ```php class AuthorData extends Data { + /** + * @param Collection|Lazy $posts + */ public function __construct( public string $name, - #[DataCollectionOf(PostData::class)] - public DataCollection|Lazy $posts + public Collection|Lazy $posts ) { } @@ -853,7 +863,7 @@ class AuthorData extends Data { return new self( $author->name, - Lazy::create(fn() => PostData::collection($author->posts)) + Lazy::create(fn() => PostData::collect($author->posts)) ); } } @@ -1005,19 +1015,19 @@ You can do quite a lot with lazy properties like including them: - when they are requested in the URL query - by default, with an option to exclude them -And a lot more. You can read all about it [here](/docs/laravel-data/v3/as-a-resource/lazy-properties). +And a lot more. You can read all about it [here](/docs/laravel-data/v4/as-a-resource/lazy-properties). ## Conclusion So that's it, a quick overview of this package. We barely scratched the surface of what's possible with the package. There's still a lot more you can do with data objects like: -- [casting](/docs/laravel-data/v3/advanced-usage/eloquent-casting) them into Eloquent models -- [transforming](/docs/laravel-data/v3/advanced-usage/typescript) the structure to typescript -- [working](/docs/laravel-data/v3/as-a-data-transfer-object/collections) with `DataCollections` -- [optional properties](/docs/laravel-data/v3/as-a-data-transfer-object/optional-properties) not always required when creating a data object -- [wrapping](/docs/laravel-data/v3/as-a-resource/wrapping) transformed data into keys -- [mapping](/docs/laravel-data/v3/as-a-data-transfer-object/creating-a-data-object#content-mapping-property-names) property names when creating or transforming a data object -- [appending](/docs/laravel-data/v3/as-a-resource/from-data-to-resource#content-appending-properties) extra data -- [including](/docs/laravel-data/v3/as-a-resource/lazy-properties#content-using-query-strings) properties using the URL query string -- [inertia](https://spatie.be/docs/laravel-data/v3/advanced-usage/use-with-inertia) support for lazy properties +- [casting](/docs/laravel-data/v4/advanced-usage/eloquent-casting) them into Eloquent models +- [transforming](/docs/laravel-data/v4/advanced-usage/typescript) the structure to typescript +- [working](/docs/laravel-data/v4/as-a-data-transfer-object/collections) with `DataCollections` +- [optional properties](/docs/laravel-data/v4/as-a-data-transfer-object/optional-properties) not always required when creating a data object +- [wrapping](/docs/laravel-data/v4/as-a-resource/wrapping) transformed data into keys +- [mapping](/docs/laravel-data/v4/as-a-data-transfer-object/mapping-property-names) property names when creating or transforming a data object +- [appending](/docs/laravel-data/v4/as-a-resource/from-data-to-resource#content-appending-properties) extra data +- [including](/docs/laravel-data/v4/as-a-resource/lazy-properties#content-using-query-strings) properties using the URL query string +- [inertia](https://spatie.be/docs/laravel-data/v4/advanced-usage/use-with-inertia) support for lazy properties - and so much more ... you'll find all the information here in the docs diff --git a/docs/installation-setup.md b/docs/installation-setup.md index e35eeba2d..7c03dc9d3 100644 --- a/docs/installation-setup.md +++ b/docs/installation-setup.md @@ -19,14 +19,14 @@ This is the contents of the published config file: ```php return [ - /* + /** * The package will use this format when working with dates. If this option * is an array, it will try to convert from the first format that works, * and will serialize dates using the first format from the array. */ 'date_format' => DATE_ATOM, - /* + /** * Global transformers will take complex types and transform them into simple * types. */ @@ -36,7 +36,7 @@ return [ BackedEnum::class => Spatie\LaravelData\Transformers\EnumTransformer::class, ], - /* + /** * Global casts will cast values into complex types when creating a data * object from simple types. */ @@ -45,7 +45,7 @@ return [ BackedEnum::class => Spatie\LaravelData\Casts\EnumCast::class, ], - /* + /** * Rule inferrers can be configured here. They will automatically add * validation rules to properties of a data object based upon * the type of the property. @@ -65,13 +65,14 @@ return [ */ 'normalizers' => [ Spatie\LaravelData\Normalizers\ModelNormalizer::class, + // Spatie\LaravelData\Normalizers\FormRequestNormalizer::class, Spatie\LaravelData\Normalizers\ArrayableNormalizer::class, Spatie\LaravelData\Normalizers\ObjectNormalizer::class, Spatie\LaravelData\Normalizers\ArrayNormalizer::class, Spatie\LaravelData\Normalizers\JsonNormalizer::class, ], - /* + /** * Data objects can be wrapped into a key like 'data' when used as a resource, * this key can be set globally here for all data objects. You can pass in * `null` if you want to disable wrapping. @@ -85,5 +86,37 @@ return [ * which will only enable the caster locally. */ 'var_dumper_caster_mode' => 'development', + + /** + * It is possible to skip the PHP reflection analysis of data objects + * 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. + */ + 'structure_caching' => [ + 'directories' => [app_path('Data')], + 'cache' => [ + 'store' => env('CACHE_DRIVER', 'file'), + 'prefix' => 'laravel-data', + ], + 'reflection_discovery' => [ + 'enabled' => true, + 'base_path' => base_path(), + 'root_namespace' => null, + ], + ], + + /** + * A data object can be validated when created using a factory or when calling the from + * method. By default, only when a request is passed the data is being validated. This + * behaviour can be changed to always validate or to completely disable validation. + */ + 'validation_strategy' => \Spatie\LaravelData\Support\Creation\ValidationStrategy::OnlyRequests->value, + + /** + * When using an invalid include, exclude, only or except partial, the package will + * throw an + */ + 'ignore_invalid_partials' => false, ]; ``` diff --git a/docs/third-party-packages.md b/docs/third-party-packages.md index 8ef7ca7cc..425fc9477 100644 --- a/docs/third-party-packages.md +++ b/docs/third-party-packages.md @@ -5,6 +5,7 @@ weight: 5 Some community members created packages that extend the functionality of Laravel Data. Here's a list of them: +- [laravel-typescript-transformer](https://github.com/spatie/laravel-typescript-transformer) - [laravel-data-openapi-generator](https://github.com/xolvionl/laravel-data-openapi-generator) Created a package yourself that you want to add to this list? Send us a PR! diff --git a/docs/validation/auto-rule-inferring.md b/docs/validation/auto-rule-inferring.md index 516c5fed5..50cadefe9 100644 --- a/docs/validation/auto-rule-inferring.md +++ b/docs/validation/auto-rule-inferring.md @@ -57,4 +57,4 @@ By default, five rule inferrers are enabled: - A `array` type will add the `array` rule - **AttributesRuleInferrer** will make sure that rule attributes we described above will also add their rules -It is possible to write your rule inferrers. You can find more information [here](/docs/laravel-data/v3/advanced-usage/creating-a-rule-inferrer). +It is possible to write your rule inferrers. You can find more information [here](/docs/laravel-data/v4/advanced-usage/creating-a-rule-inferrer). diff --git a/docs/validation/introduction.md b/docs/validation/introduction.md index 99aad5773..cf2c5f3d3 100644 --- a/docs/validation/introduction.md +++ b/docs/validation/introduction.md @@ -3,7 +3,7 @@ title: Introduction weight: 1 --- -Laravel data allows you to create data objects from all sorts of data. One of the most common ways to create a data object is from a request and the data from a request cannot always be trusted. +Laravel data, allows you to create data objects from all sorts of data. One of the most common ways to create a data object is from a request and the data from a request cannot always be trusted. That's why it is possible to validate the data before creating the data object. You can validate requests but also arrays and other structures. @@ -13,7 +13,7 @@ The package will try to automatically infer validation rules from the data objec Validation is probably one of the coolest features of this package, but it is also the most complex one. We'll try to make it as straightforward as possible to validate data but in the end the Laravel validator was not written to be used in this way. So there are some limitations and quirks you should be aware of. -In some cases it might be easier to just create a custom request class with validation rules and then call `toArray` on the request to create a data object than trying to validate the data with this package. +In a few cases it might be easier to just create a custom request class with validation rules and then call `toArray` on the request to create a data object than trying to validate the data with this package. ## When does validation happen? @@ -42,6 +42,22 @@ SongData::validateAndCreate( ); // returns a SongData object ``` +### Validate everything + +It is possible to validate all payloads injected or passed to the `from` method by setting the `validation_strategy` config option to `Always`: + +```php +'validation_strategy' => \Spatie\LaravelData\Support\Creation\ValidationStrategy::Always->value, +``` + +Completely disabling validation can be done by setting the `validation_strategy` config option to `Disabled`: + +```php +'validation_strategy' => \Spatie\LaravelData\Support\Creation\ValidationStrategy::Disabled->value, +``` + +If you require a more fine-grained control over when validation should happen, you can use [data factories](/docs/laravel-data/v4//as-a-data-transfer-object/factories.md) to manually specify the validation strategy. + ## A quick glance at the validation functionality We've got a lot of documentation about validation and we suggest you read it all, but if you want to get a quick glance at the validation functionality, here's a quick overview: @@ -71,7 +87,7 @@ The package will generate the following validation rules: ] ``` -The package follows an algorithm to infer rules from the data object, you can read more about it [here](/docs/laravel-data/v3/validation/auto-rule-inferring). +The package follows an algorithm to infer rules from the data object, you can read more about it [here](/docs/laravel-data/v4/validation/auto-rule-inferring). ### Validation attributes @@ -91,7 +107,7 @@ class SongData extends Data When you provide an artist with a length of more than 20 characters, the validation will fail. -There's a complete [chapter](/docs/laravel-data/v3/validation/using-attributes) dedicated to validation attributes. +There's a complete [chapter](/docs/laravel-data/v4/validation/using-attributes) dedicated to validation attributes. ### Manual rules @@ -116,7 +132,7 @@ class SongData extends Data } ``` -You can read more about manual rules in its [dedicated chapter](/docs/laravel-data/v3/validation/manual-rules). +You can read more about manual rules in its [dedicated chapter](/docs/laravel-data/v4/validation/manual-rules). ### Using the container @@ -131,7 +147,7 @@ If the request contains data that is not compatible with the data object, a vali ### Working with the validator -We provide a few points where you can hook into the validation process. You can read more about it in the [dedicated chapter](/docs/laravel-data/v3/validation/working-with-the-validator). +We provide a few points where you can hook into the validation process. You can read more about it in the [dedicated chapter](/docs/laravel-data/v4/validation/working-with-the-validator). It is for example to: @@ -191,7 +207,7 @@ The validation rules for this class will be: ] ``` -There are a few quirky things to keep in mind when working with nested data objects, you can read all about it [here](/docs/laravel-data/v3/validation/nesting-data). +There are a few quirky things to keep in mind when working with nested data objects, you can read all about it [here](/docs/laravel-data/v4/validation/nesting-data). ## Validation of nested data collections @@ -225,7 +241,7 @@ In this case the validation rules for `AlbumData` would look like this: ] ``` -More info about nested data collections can be found [here](/docs/laravel-data/v3/validation/nesting-data). +More info about nested data collections can be found [here](/docs/laravel-data/v4/validation/nesting-data). ## Default values diff --git a/docs/validation/nesting-data.md b/docs/validation/nesting-data.md index 6f5ba1527..b7e28a848 100644 --- a/docs/validation/nesting-data.md +++ b/docs/validation/nesting-data.md @@ -3,4 +3,127 @@ title: Nesting Data weight: 6 --- -Work in progress +A data object can contain other data objects or collections of data objects. The package will make sure that also for these data objects validation rules will be generated. + +When we again have a look at the data object from the [nesting](/docs/laravel-data/v4/as-a-data-transfer-object/nesting) section: + +```php +class AlbumData extends Data +{ + public function __construct( + public string $title, + public ArtistData $artist, + ) { + } +} +``` + +The validation rules for this class would be: + +```php +[ + 'title' => ['required', 'string'], + 'artist' => ['array'], + 'artist.name' => ['required', 'string'], + 'artist.age' => ['required', 'integer'], +] +``` + +## Validating a nested collection of data objects + +When validating a data object like this + +```php +class AlbumData extends Data +{ + /** + * @param array $songs + */ + public function __construct( + public string $title, + public array $songs, + ) { + } +} +``` + +In this case the validation rules for `AlbumData` would look like this: + +```php +[ + 'title' => ['required', 'string'], + 'songs' => ['present', 'array', new NestedRules()], +] +``` + +The `NestedRules` class is a Laravel validation rule that will validate each item within the collection for the rules defined on the data class for that collection. + +## Nullable and Optional nested data + +If we make the nested data object nullable , the validation rules will change depending on the payload provided: + +```php +class AlbumData extends Data +{ + public function __construct( + public string $title, + public ?ArtistData $artist, + ) { + } +} +``` + +If no value for the nested object key was provided or the value is `null`, the validation rules will be: + +```php +[ + 'title' => ['required', 'string'], + 'artist' => ['nullable'], +] +``` + +If however a value was provided (even an empty array), the validation rules will be: + +```php +[ + 'title' => ['required', 'string'], + 'artist' => ['array'], + 'artist.name' => ['required', 'string'], + 'artist.age' => ['required', 'integer'], +] +``` + +The same happens when a property is made optional: + +```php +class AlbumData extends Data +{ + public function __construct( + public string $title, + public ArtistData $artist, + ) { + } +} +``` + +There's a small difference though compared against nullable, when no value was provided for the nested object key, the validation rules will be: + +```php +[ + 'title' => ['required', 'string'], + 'artist' => ['present', 'array', new NestedRules()], +] +``` + +However, when a value was provided (even an empty array or null), the validation rules will be: + +```php +[ + 'title' => ['required', 'string'], + 'artist' => ['array'], + 'artist.name' => ['required', 'string'], + 'artist.age' => ['required', 'integer'], +] +``` + +We've written a [blog post](https://flareapp.io/blog/fixing-nested-validation-in-laravel) on the reasoning behind these variable validation rules based upon payload. And they are also the reason why calling `getValidationRules` on a data object always requires a payload to be provided. diff --git a/docs/validation/skipping-validation.md b/docs/validation/skipping-validation.md index c8bb52fac..b5e2c6220 100644 --- a/docs/validation/skipping-validation.md +++ b/docs/validation/skipping-validation.md @@ -60,3 +60,7 @@ Now the validation rules will look like this: 'last_name' => ['required', 'string'], ] ``` + +## Skipping validation for all properties + +By using [data factories](/docs/laravel-data/v4/as-a-data-transfer-object/factories.md) or setting the `validation_strategy` in the `data.php` config you can skip validation for all properties of a data class. diff --git a/docs/validation/using-validation-attributes.md b/docs/validation/using-validation-attributes.md index 5e4372276..d8f2741db 100644 --- a/docs/validation/using-validation-attributes.md +++ b/docs/validation/using-validation-attributes.md @@ -29,8 +29,7 @@ So it is not required to add the `required` and `string` rule, these will be add ] ``` -For each Laravel validation rule we've got a matching validation attribute, you can find a list of them [here](/docs/laravel-data/v3/advanced-usage/using-attributes). - +For each Laravel validation rule we've got a matching validation attribute, you can find a list of them [here](/docs/laravel-data/v4/advanced-usage/using-attributes). ## Referencing route parameters diff --git a/src/DataPipes/ValidatePropertiesDataPipe.php b/src/DataPipes/ValidatePropertiesDataPipe.php index b1baa4a0b..07ea8c106 100644 --- a/src/DataPipes/ValidatePropertiesDataPipe.php +++ b/src/DataPipes/ValidatePropertiesDataPipe.php @@ -4,7 +4,7 @@ use Illuminate\Http\Request; use Spatie\LaravelData\Support\Creation\CreationContext; -use Spatie\LaravelData\Support\Creation\ValidationType; +use Spatie\LaravelData\Support\Creation\ValidationStrategy; use Spatie\LaravelData\Support\DataClass; class ValidatePropertiesDataPipe implements DataPipe @@ -15,11 +15,11 @@ public function handle( array $properties, CreationContext $creationContext ): array { - if ($creationContext->validationType === ValidationType::Disabled) { + if ($creationContext->validationStrategy === ValidationStrategy::Disabled) { return $properties; } - if ($creationContext->validationType === ValidationType::OnlyRequests && ! $payload instanceof Request) { + if ($creationContext->validationStrategy === ValidationStrategy::OnlyRequests && ! $payload instanceof Request) { return $properties; } diff --git a/src/Support/Creation/CreationContext.php b/src/Support/Creation/CreationContext.php index 255fe41af..030b67097 100644 --- a/src/Support/Creation/CreationContext.php +++ b/src/Support/Creation/CreationContext.php @@ -29,7 +29,7 @@ class CreationContext */ public function __construct( public string $dataClass, - public readonly ValidationType $validationType, + public readonly ValidationStrategy $validationStrategy, public readonly bool $mapPropertyNames, public readonly bool $withoutMagicalCreation, public readonly ?array $ignoredMagicalMethods, diff --git a/src/Support/Creation/CreationContextFactory.php b/src/Support/Creation/CreationContextFactory.php index ed1484858..f630a4094 100644 --- a/src/Support/Creation/CreationContextFactory.php +++ b/src/Support/Creation/CreationContextFactory.php @@ -30,7 +30,7 @@ class CreationContextFactory */ public function __construct( public string $dataClass, - public ValidationType $validationType, + public ValidationStrategy $validationStrategy, public bool $mapPropertyNames, public bool $withoutMagicalCreation, public ?array $ignoredMagicalMethods, @@ -46,7 +46,7 @@ public static function createFromConfig( return new self( dataClass: $dataClass, - validationType: ValidationType::from($config['validation_type']), + validationStrategy: ValidationStrategy::from($config['validation_strategy']), mapPropertyNames: true, withoutMagicalCreation: false, ignoredMagicalMethods: null, @@ -59,7 +59,7 @@ public static function createFromContext( ) { return new self( dataClass: $context->dataClass, - validationType: $context->validationType, + validationStrategy: $context->validationStrategy, mapPropertyNames: $context->mapPropertyNames, withoutMagicalCreation: $context->withoutMagicalCreation, ignoredMagicalMethods: $context->ignoredMagicalMethods, @@ -67,30 +67,30 @@ public static function createFromContext( ); } - public function validationType(ValidationType $validationType): self + public function validationStrategy(ValidationStrategy $validationStrategy): self { - $this->validationType = $validationType; + $this->validationStrategy = $validationStrategy; return $this; } - public function disableValidation(): self + public function withoutValidation(): self { - $this->validationType = ValidationType::Disabled; + $this->validationStrategy = ValidationStrategy::Disabled; return $this; } public function onlyValidateRequests(): self { - $this->validationType = ValidationType::OnlyRequests; + $this->validationStrategy = ValidationStrategy::OnlyRequests; return $this; } public function alwaysValidate(): self { - $this->validationType = ValidationType::Always; + $this->validationStrategy = ValidationStrategy::Always; return $this; } @@ -155,7 +155,7 @@ public function get(): CreationContext { return new CreationContext( dataClass: $this->dataClass, - validationType: $this->validationType, + validationStrategy: $this->validationStrategy, mapPropertyNames: $this->mapPropertyNames, withoutMagicalCreation: $this->withoutMagicalCreation, ignoredMagicalMethods: $this->ignoredMagicalMethods, diff --git a/src/Support/Creation/ValidationType.php b/src/Support/Creation/ValidationStrategy.php similarity index 83% rename from src/Support/Creation/ValidationType.php rename to src/Support/Creation/ValidationStrategy.php index fbef21652..a4de166d7 100644 --- a/src/Support/Creation/ValidationType.php +++ b/src/Support/Creation/ValidationStrategy.php @@ -2,7 +2,7 @@ namespace Spatie\LaravelData\Support\Creation; -enum ValidationType: string +enum ValidationStrategy: string { case Always = 'always'; case OnlyRequests = 'only_requests'; diff --git a/src/Transformers/Transformer.php b/src/Transformers/Transformer.php index 6ef510f3f..0ba443cc2 100644 --- a/src/Transformers/Transformer.php +++ b/src/Transformers/Transformer.php @@ -7,9 +7,5 @@ interface Transformer { - public function transform( - DataProperty $property, - mixed $value, - TransformationContext $context - ): mixed; + public function transform(DataProperty $property, mixed $value, TransformationContext $context): mixed; } diff --git a/tests/CreationFactoryTest.php b/tests/CreationFactoryTest.php index 2c4836baf..985d7f173 100644 --- a/tests/CreationFactoryTest.php +++ b/tests/CreationFactoryTest.php @@ -92,7 +92,7 @@ public static function fromArray(array $payload) expect(fn () => $dataClass::factory()->from($request)) ->toThrow(ValidationException::class); - expect($dataClass::factory()->disableValidation()->from($request)) + expect($dataClass::factory()->withoutValidation()->from($request)) ->toBeInstanceOf(Data::class) ->string->toEqual('nowp'); }); diff --git a/tests/RequestTest.php b/tests/RequestTest.php index 1a2437bad..d2a1f7ac2 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -15,13 +15,6 @@ use Spatie\LaravelData\Tests\Fakes\SimpleData; use Spatie\LaravelData\Tests\Fakes\SimpleDataWithExplicitValidationRuleAttributeData; -function performRequest(string $string): TestResponse -{ - return postJson('/example-route', [ - 'string' => $string, - ]); -} - beforeEach(function () { handleExceptions([ AuthenticationException::class, diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index 1b57fc0c8..65077c325 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -41,7 +41,7 @@ use Spatie\LaravelData\DataCollection; use Spatie\LaravelData\Mappers\SnakeCaseMapper; use Spatie\LaravelData\Optional; -use Spatie\LaravelData\Support\Creation\ValidationType; +use Spatie\LaravelData\Support\Creation\ValidationStrategy; use Spatie\LaravelData\Support\Validation\References\FieldReference; use Spatie\LaravelData\Support\Validation\References\RouteParameterReference; use Spatie\LaravelData\Support\Validation\ValidationContext; @@ -2340,7 +2340,7 @@ public static function rules(ValidationContext $context): array expect($dataClass::validateAndCreate([])->toArray())->toBe([]); }); -it('is possible to define the validation type for each data object globally using config', function () { +it('is possible to define the validation strategy for each data object globally using config', function () { $dataClass = new class () extends Data { #[In('Hello World')] public string $string; @@ -2350,7 +2350,7 @@ public static function rules(ValidationContext $context): array ->toBeInstanceOf(Data::class) ->string->toBe('Nowp'); - config()->set('data.validation_type', ValidationType::Always->value); + config()->set('data.validation_strategy', ValidationStrategy::Always->value); expect(fn () => $dataClass::from(['string' => 'Nowp'])) ->toThrow(ValidationException::class); diff --git a/tests/WrapTest.php b/tests/WrapTest.php index 50c395270..66ab9250a 100644 --- a/tests/WrapTest.php +++ b/tests/WrapTest.php @@ -1,12 +1,21 @@ $string, + ]); +} it('can wrap data objects by method call', function () { expect( @@ -231,3 +240,4 @@ public function with(): array ], ]); }); +