From 4c72103926f7e24ec4cfd4875129b05e35c40c88 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Wed, 27 Oct 2021 17:22:06 +0200 Subject: [PATCH 1/5] Add a WithData traiy --- .../creating-a-data-object.md | 33 ++++++++++ src/Exceptions/InvalidDataClass.php | 17 +++++ src/WithData.php | 28 ++++++++ tests/DataTest.php | 64 +++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 src/Exceptions/InvalidDataClass.php create mode 100644 src/WithData.php 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 9b55a0e6..caf23161 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 @@ -125,3 +125,36 @@ SongData::optional(null); // returns null ``` Underneath the optional method will call the `from` method when a value is given, so you can still magically create data objects. When a null value is given, it will return null. + +## Quickly getting data from Models, Requests, ... + +By adding the `WithData` trait to a Model, Request or any class that can be magically be converted to a data object, you'll enable support for the `getData` method. This method will automatically generate a data object for the object it is called upon. + +For example, let's retake a look at the `Song` model we saw earlier. We can add the `WithData` trait as follows: + +```php +class Song extends Model{ + use WithData; + + protected $dataClass = SongData::class; +} +``` + +Now we can quickly get the data object for the model as such: + +```php +Song::firstOrFail($id)->getData(); // A SongData object +``` + +You can also use a method to define the data class: + +```php +class Song extends Model{ + use WithData; + + protected function dataClass(): string + { + return SongData::class; + } +} +``` diff --git a/src/Exceptions/InvalidDataClass.php b/src/Exceptions/InvalidDataClass.php new file mode 100644 index 00000000..cd933113 --- /dev/null +++ b/src/Exceptions/InvalidDataClass.php @@ -0,0 +1,17 @@ + $this->dataClass, + method_exists($this, 'dataClass') => $this->dataClass(), + default => null, + }; + + if (! is_a($dataClass, Data::class, true)) { + throw InvalidDataClass::create($dataClass); + } + + return resolve(DataFromSomethingResolver::class)->execute( + $dataClass, + $this + ); + } +} diff --git a/tests/DataTest.php b/tests/DataTest.php index 0c4a2984..d52a9e47 100644 --- a/tests/DataTest.php +++ b/tests/DataTest.php @@ -5,6 +5,9 @@ use Carbon\Carbon; use Carbon\CarbonImmutable; use DateTime; +use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\ValidationException; use Spatie\LaravelData\Attributes\WithTransformer; use Spatie\LaravelData\Data; @@ -23,6 +26,7 @@ use Spatie\LaravelData\Tests\Fakes\SimpleData; use Spatie\LaravelData\Tests\Fakes\SimpleDataWithoutConstructor; use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer; +use Spatie\LaravelData\WithData; class DataTest extends TestCase { @@ -610,4 +614,64 @@ public function it_can_create_a_data_object_from_a_model() $this->assertNull($data->nullable_date); $this->assertTrue($data->enum->equals(DummyEnum::published())); } + + /** @test */ + public function it_can_add_the_with_data_trait_to_a_request() + { + $formRequest = new class extends FormRequest{ + use WithData; + + public string $dataClass = SimpleData::class; + }; + + $formRequest->replace([ + 'string' => 'Hello World' + ]); + + $data = $formRequest->getData(); + + $this->assertEquals(SimpleData::from('Hello World'), $data); + } + + /** @test */ + public function it_can_add_the_with_data_trait_to_a_model() + { + $model = new class extends Model { + use WithData; + + protected string $dataClass = SimpleData::class; + }; + + $model->fill([ + 'string' => 'Hello World' + ]); + + $data = $model->getData(); + + $this->assertEquals(SimpleData::from('Hello World'), $data); + } + + /** @test */ + public function it_can_define_the_with_data_trait_data_class_by_method() + { + $arrayable = new class implements Arrayable { + use WithData; + + public function toArray() + { + return [ + 'string' => 'Hello World' + ]; + } + + protected function dataClass(): string + { + return SimpleData::class; + } + }; + + $data = $arrayable->getData(); + + $this->assertEquals(SimpleData::from('Hello World'), $data); + } } From 5ab5700131c89c034046941dde95aa26ed3427df Mon Sep 17 00:00:00 2001 From: rubenvanassche Date: Wed, 27 Oct 2021 15:22:35 +0000 Subject: [PATCH 2/5] Fix styling --- tests/DataTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/DataTest.php b/tests/DataTest.php index d52a9e47..4da1e7a7 100644 --- a/tests/DataTest.php +++ b/tests/DataTest.php @@ -618,14 +618,14 @@ public function it_can_create_a_data_object_from_a_model() /** @test */ public function it_can_add_the_with_data_trait_to_a_request() { - $formRequest = new class extends FormRequest{ + $formRequest = new class() extends FormRequest { use WithData; public string $dataClass = SimpleData::class; }; $formRequest->replace([ - 'string' => 'Hello World' + 'string' => 'Hello World', ]); $data = $formRequest->getData(); @@ -636,14 +636,14 @@ public function it_can_add_the_with_data_trait_to_a_request() /** @test */ public function it_can_add_the_with_data_trait_to_a_model() { - $model = new class extends Model { + $model = new class() extends Model { use WithData; protected string $dataClass = SimpleData::class; }; $model->fill([ - 'string' => 'Hello World' + 'string' => 'Hello World', ]); $data = $model->getData(); @@ -654,13 +654,13 @@ public function it_can_add_the_with_data_trait_to_a_model() /** @test */ public function it_can_define_the_with_data_trait_data_class_by_method() { - $arrayable = new class implements Arrayable { + $arrayable = new class() implements Arrayable { use WithData; public function toArray() { return [ - 'string' => 'Hello World' + 'string' => 'Hello World', ]; } From bfa7c32d69e35d74e17ba6ef16e8b97cd6ea8d1b Mon Sep 17 00:00:00 2001 From: rubenvanassche Date: Tue, 2 Nov 2021 12:39:06 +0000 Subject: [PATCH 3/5] Fix styling --- tests/DataTest.php | 6 +++--- tests/RuleInferrers/RequiredRuleInferrerTest.php | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/DataTest.php b/tests/DataTest.php index 4da1e7a7..2595f464 100644 --- a/tests/DataTest.php +++ b/tests/DataTest.php @@ -618,7 +618,7 @@ public function it_can_create_a_data_object_from_a_model() /** @test */ public function it_can_add_the_with_data_trait_to_a_request() { - $formRequest = new class() extends FormRequest { + $formRequest = new class () extends FormRequest { use WithData; public string $dataClass = SimpleData::class; @@ -636,7 +636,7 @@ public function it_can_add_the_with_data_trait_to_a_request() /** @test */ public function it_can_add_the_with_data_trait_to_a_model() { - $model = new class() extends Model { + $model = new class () extends Model { use WithData; protected string $dataClass = SimpleData::class; @@ -654,7 +654,7 @@ public function it_can_add_the_with_data_trait_to_a_model() /** @test */ public function it_can_define_the_with_data_trait_data_class_by_method() { - $arrayable = new class() implements Arrayable { + $arrayable = new class () implements Arrayable { use WithData; public function toArray() diff --git a/tests/RuleInferrers/RequiredRuleInferrerTest.php b/tests/RuleInferrers/RequiredRuleInferrerTest.php index 8c4cccae..563f5be2 100644 --- a/tests/RuleInferrers/RequiredRuleInferrerTest.php +++ b/tests/RuleInferrers/RequiredRuleInferrerTest.php @@ -24,7 +24,7 @@ public function setUp(): void /** @test */ public function it_wont_add_a_required_rule_when_a_property_is_non_nullable() { - $dataProperty = $this->getProperty(new class() extends Data { + $dataProperty = $this->getProperty(new class () extends Data { public string $string; }); @@ -36,7 +36,7 @@ public function it_wont_add_a_required_rule_when_a_property_is_non_nullable() /** @test */ public function it_wont_add_a_required_rule_when_a_property_is_nullable() { - $dataProperty = $this->getProperty(new class() extends Data { + $dataProperty = $this->getProperty(new class () extends Data { public ?string $string; }); @@ -48,7 +48,7 @@ public function it_wont_add_a_required_rule_when_a_property_is_nullable() /** @test */ public function it_wont_add_a_required_rule_when_a_property_already_contains_a_required_rule() { - $dataProperty = $this->getProperty(new class() extends Data { + $dataProperty = $this->getProperty(new class () extends Data { public string $string; }); @@ -60,7 +60,7 @@ public function it_wont_add_a_required_rule_when_a_property_already_contains_a_r /** @test */ public function it_wont_add_a_required_rule_when_a_property_already_contains_a_required_object_rule() { - $dataProperty = $this->getProperty(new class() extends Data { + $dataProperty = $this->getProperty(new class () extends Data { public string $string; }); @@ -72,7 +72,7 @@ public function it_wont_add_a_required_rule_when_a_property_already_contains_a_r /** @test */ public function it_wont_add_a_required_rule_when_a_property_already_contains_a_boolean_rule() { - $dataProperty = $this->getProperty(new class() extends Data { + $dataProperty = $this->getProperty(new class () extends Data { public string $string; }); From daaab215fcbc3fc68bda2d5bf8e1e99980fbd3d1 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Tue, 2 Nov 2021 13:51:45 +0100 Subject: [PATCH 4/5] Update docs --- .../creating-a-data-object.md | 53 ++++++++++++++----- 1 file changed, 40 insertions(+), 13 deletions(-) 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 caf23161..5559a008 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 @@ -1,6 +1,5 @@ --- -title: Creating a data object -weight: 1 +title: Creating a data object weight: 1 --- Let's get started with the following simple data object: @@ -28,7 +27,8 @@ But with this package, you can initialize the data object also with an array: SongData::from(['title' => 'Never gonna give you up', 'artist' => 'Rick Astley']); ``` -You can use the `from` method to create a data object from nearly anything. For example, let's say you have an Eloquent model like this: +You can use the `from` method to create a data object from nearly anything. For example, let's say you have an Eloquent +model like this: ```php class Song extends Model{ @@ -46,9 +46,12 @@ The package will find the required properties within the model and use them to c ## Magical creation -It is possible to overwrite or extend the behaviour of the `from` method for specific types. So you can construct a data object in a specific manner for that type. This can be done by adding a static method starting with 'from' to the data object. +It is possible to overwrite or extend the behaviour of the `from` method for specific types. So you can construct a data +object in a specific manner for that type. This can be done by adding a static method starting with 'from' to the data +object. -For example, we want to change how we create a data object from a model. We can add a `fromModel` static method that takes the model we want to use as a parameter: +For example, we want to change how we create a data object from a model. We can add a `fromModel` static method that +takes the model we want to use as a parameter: ```php class SongData extends Data @@ -74,7 +77,8 @@ SongData::from(Song::firstOrFail($id)); Instead of the default method, the `fromModel` method will be called to create a data object from the found model. -You're truly free to add as many from methods as you want. For example, you could add one to create a data object from a string: +You're truly free to add as many from methods as you want. For example, you could add one to create a data object from a +string: ```php class SongData extends Data @@ -107,28 +111,34 @@ There are a few requirements to enable magical data object creation: - The method can only take **one typed parameter** for which you want to create an object - The method cannot be called **from** -When the package cannot find such a method for a type given to the data object's `from` method. Then the data object will try to create itself from the following types: +When the package cannot find such a method for a type given to the data object's `from` method. Then the data object +will try to create itself from the following types: - An *Eloquent model* by calling `toArray` on it - A *Laravel request* by calling `all` on it - An *Arrayable* by calling `toArray` on it - An *array* -When a data object cannot be created using magical methods or the default methods, a `CannotCreateDataFromValue` exception will be thrown. +When a data object cannot be created using magical methods or the default methods, a `CannotCreateDataFromValue` +exception will be thrown. ## Optional creation -It is impossible to return `null` from a data object's `from` method since we always expect a data object when calling `from`. To solve this, you can call the `optional` method: +It is impossible to return `null` from a data object's `from` method since we always expect a data object when +calling `from`. To solve this, you can call the `optional` method: ```php SongData::optional(null); // returns null ``` -Underneath the optional method will call the `from` method when a value is given, so you can still magically create data objects. When a null value is given, it will return null. +Underneath the optional method will call the `from` method when a value is given, so you can still magically create data +objects. When a null value is given, it will return null. ## Quickly getting data from Models, Requests, ... -By adding the `WithData` trait to a Model, Request or any class that can be magically be converted to a data object, you'll enable support for the `getData` method. This method will automatically generate a data object for the object it is called upon. +By adding the `WithData` trait to a Model, Request or any class that can be magically be converted to a data object, +you'll enable support for the `getData` method. This method will automatically generate a data object for the object it +is called upon. For example, let's retake a look at the `Song` model we saw earlier. We can add the `WithData` trait as follows: @@ -146,10 +156,11 @@ Now we can quickly get the data object for the model as such: Song::firstOrFail($id)->getData(); // A SongData object ``` -You can also use a method to define the data class: +We can do the same with a FormRequest, we don't use a property here to define the data class but use a method instead: ```php -class Song extends Model{ +class SongRequest extends Request +{ use WithData; protected function dataClass(): string @@ -158,3 +169,19 @@ class Song extends Model{ } } ``` + +Now within a controller where the request is injected, we can get the data object like this: + +```php +class SongController +{ + public function __invoke(SongRequest $request): SongData + { + $data = $request->getData(); + + $song = Song::create($data); + + return $data; + } +} +``` From 9e30c15b11873217ee7ab61ae54ef80d51affdbd Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Tue, 2 Nov 2021 13:53:07 +0100 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b83ff5ec..27e71cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-data-resource` will be documented in this file. +## 1.0.2 - 2021-11-02 + +- add a `WithData` trait for quicker getting data from objects + ## 1.0.1 - 2021-10-28 - fix required rules being added when not allowed