Skip to content

Commit be6fede

Browse files
wip
1 parent 6196054 commit be6fede

37 files changed

+1118
-262
lines changed

.github/FUNDING.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
github: :vendor_name
1+
github: spatie

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
All notable changes to `:package_name` will be documented in this file.
3+
All notable changes to `laravel-data-resource` will be documented in this file.
44

55
## 1.0.0 - 202X-XX-XX
66

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) :vendor_name <[email protected]>
3+
Copyright (c) spatie <[email protected]>
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 293 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
# :package_description
1+
# This is my package LaravelDataResource
22

3-
[![Latest Version on Packagist](https://img.shields.io/packagist/v/vendor_slug/package_slug.svg?style=flat-square)](https://packagist.org/packages/vendor_slug/package_slug)
4-
[![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/vendor_slug/package_slug/run-tests?label=tests)](https://github.com/vendor_slug/package_slug/actions?query=workflow%3Arun-tests+branch%3Amain)
5-
[![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/vendor_slug/package_slug/Check%20&%20fix%20styling?label=code%20style)](https://github.com/vendor_slug/package_slug/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain)
6-
[![Total Downloads](https://img.shields.io/packagist/dt/vendor_slug/package_slug.svg?style=flat-square)](https://packagist.org/packages/vendor_slug/package_slug)
3+
[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-data-resource.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-data-resource)
4+
[![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/spatie/laravel-data-resource/run-tests?label=tests)](https://github.com/spatie/laravel-data-resource/actions?query=workflow%3Arun-tests+branch%3Amain)
5+
[![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/spatie/laravel-data-resource/Check%20&%20fix%20styling?label=code%20style)](https://github.com/spatie/laravel-data-resource/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain)
6+
[![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-data-resource.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-data-resource)
77

88
---
99
This repo can be used as to scaffold a Laravel package. Follow these steps to get started:
1010

11-
1. Press the "Use template" button at the top of this repo to create a new repo with the contents of this skeleton
12-
2. Run "./configure-skeleton.sh" to run a script that will replace all placeholders throughout all the files
11+
1. Press the "Use template" button at the top of this repo to create a new repo with the contents of this laravel-data-resource
12+
2. Run "./configure-laravel-data-resource.sh" to run a script that will replace all placeholders throughout all the files
1313
3. Remove this block of text.
1414
4. Have fun creating your package.
1515
5. If you need help creating a package, consider picking up our <a href="https://laravelpackage.training">Laravel Package Training</a> video course.
@@ -19,7 +19,7 @@ This is where your description should go. Limit it to a paragraph or two. Consid
1919

2020
## Support us
2121

22-
[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/:package_name.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/:package_name)
22+
[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-data-resource.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/laravel-data-resource)
2323

2424
We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
2525

@@ -30,19 +30,19 @@ We highly appreciate you sending us a postcard from your hometown, mentioning wh
3030
You can install the package via composer:
3131

3232
```bash
33-
composer require vendor_slug/package_slug
33+
composer require spatie/laravel-data-resource
3434
```
3535

3636
You can publish and run the migrations with:
3737

3838
```bash
39-
php artisan vendor:publish --provider="VendorName\Skeleton\SkeletonServiceProvider" --tag="package_slug-migrations"
39+
php artisan vendor:publish --provider="Spatie\LaravelDataResource\LaravelDataResourceServiceProvider" --tag="laravel-data-resource-migrations"
4040
php artisan migrate
4141
```
4242

4343
You can publish the config file with:
4444
```bash
45-
php artisan vendor:publish --provider="VendorName\Skeleton\SkeletonServiceProvider" --tag="package_slug-config"
45+
php artisan vendor:publish --provider="Spatie\LaravelDataResource\LaravelDataResourceServiceProvider" --tag="laravel-data-resource-config"
4646
```
4747

4848
This is the contents of the published config file:
@@ -54,9 +54,288 @@ return [
5454

5555
## Usage
5656

57+
Data objects are structured entities within Sail that fullfill the role of a data transfer object and a resource.
58+
59+
They have a few advantages:
60+
61+
- One structure for both the resource and data
62+
- Can be lazy (=only send required data needed)
63+
- They are type safe
64+
- TypeScript transformer knows exactly what to do with them
65+
66+
There are situations where the unified data objects aren't a good fit. For example when the structure of the DTO and resource differ too much or in the case where a DTO is required but a resource isn't. In such case you'd better create a seperate resource and/or dto.
67+
68+
## Creating Data objects
69+
70+
A data object extends from `Data` and looks like this:
71+
72+
```php
73+
class ContentThemeData extends Data
74+
{
75+
public function __construct(
76+
public string $name
77+
) {
78+
}
79+
80+
public static function create(ContentTheme $theme): self
81+
{
82+
return new self(
83+
$theme->name
84+
);
85+
}
86+
}
87+
88+
```
89+
90+
In the constructor we define the properties associated with this data object. Each data object also should have a static `create` method that will create the object based upon a model. This is required for automatically creating collections of resources and logging activity.
91+
92+
## Using Data objects as dto
93+
94+
Since the data objects are just simple PHP objects with some extra methods added to them, you can use them like regular PHP dto's:
95+
96+
```php
97+
$data = new ContentThemeResource('Hello world');
98+
```
99+
100+
You probably going to create a dto when receiving data from a form in the frontend. There are going to be two points where this happens: when you create something and when you edit something. That's why we'll create the data object within the request:
101+
102+
```php
103+
class ContentThemeRequest extends Request
104+
{
105+
public function rules(): array
106+
{
107+
return [
108+
'name' => ['required', 'string'],
109+
];
110+
}
111+
112+
public function getData(): ContentThemeData
113+
{
114+
$validated = $this->validated();
115+
116+
return new ContentThemeData(
117+
$validated['name']
118+
);
119+
}
120+
}
121+
```
122+
123+
This has two advantages:
124+
125+
- your validation rules and data objects will be created in the same class
126+
- you can create the same data object in different requests with slightly different properties
127+
128+
Since PHP supports the spread operator, for simple data objects you could do the following:
129+
130+
```php
131+
public function getData(): ContentThemeData
132+
{
133+
return new ContentThemeData(...$this->validated());
134+
}
135+
```
136+
137+
## Using Data objects as resource
138+
139+
When creating a resource you'll probably have a model, so you can call the `create` method:
140+
141+
```php
142+
ContentThemeData::create($this->contentTheme);
143+
```
144+
145+
At the moment you're creating a new model, in this case the model is `null`, you can use the `empty` method:
146+
147+
```php
148+
ContentThemeData::empty();
149+
```
150+
151+
This will return an array that follows the structure of the data object, it is possible to change the default values within this array by providing them in the constructor of the data object:
152+
153+
```php
154+
class ContentThemeData extends Data
155+
{
156+
public function __construct(
157+
public string $name = 'Hello world'
158+
) {
159+
}
160+
161+
public static function create(ContentTheme $theme): self
162+
{
163+
return new self(
164+
$theme->name
165+
);
166+
}
167+
}
168+
```
169+
170+
Or by passing defaults within the `empty` call:
171+
172+
173+
```php
174+
ContentThemeData::empty([
175+
'name' => 'Hello world',
176+
]);
177+
```
178+
179+
In your view models the `values method will now look like this:
180+
181+
```php
182+
public function values(): ContentThemeData | array
183+
{
184+
return $this->contentTheme
185+
? ContentThemeData::create($this->contentTheme)
186+
: ContentThemeData::empty();
187+
}
188+
```
189+
190+
### Indexes
191+
192+
We already took a look at using data objects within create and edit pages, but index pages also need resources albeit a collection of resources. You can easily create a collection of resources as such:
193+
194+
```php
195+
ContentThemeIndexData::collection($contentThemes);
196+
```
197+
198+
Within the `index` method of your controller you now can do the following:
199+
200+
```php
201+
public function index(ContentThemesIndexQuery $indexQuery)
202+
{
203+
return ContentThemeIndexData::collection($indexQuery->paginate());
204+
}
205+
```
206+
207+
As you can see we provide the `collection` method a paginated collection, the data object is smart enough to create a paginated response from this with links to the next, previous, last, ... pages.
208+
209+
When you yust provide a collection or array of resources to the `collection` method of a data object, then it will just return a `DataCollection` which is just a simple collection of resources:
210+
211+
```php
212+
ContentThemeIndexData::collection($contentThemes); // No pagination here
213+
```
214+
215+
### endpoints
216+
217+
Each data object can also have endpoints, you define these within a seperate `endpoints` method:
218+
219+
```php
220+
class ContentThemeIndexData extends Data
221+
{
222+
public function __construct(
223+
public ContentThemeUuid $uuid,
224+
public string $name
225+
) {
226+
}
227+
228+
public static function create(ContentTheme $theme): self
229+
{
230+
return new self(
231+
$theme->getUuid(),
232+
$theme->name,
233+
);
234+
}
235+
236+
public function endpoints(): array
237+
{
238+
return [
239+
'edit' => action([ContentThemesController::class, 'edit'], $this->uuid),
240+
'destroy' => action([ContentThemesController::class, 'destroy'], $this->uuid),
241+
];
242+
}
243+
}
244+
```
245+
246+
When this object is transformed to a resource, an extra key will be present with the endpoints. Also TypeScript transformer is smart enough to understand which endpoints this data object has.
247+
248+
When creating this data object as an dto, no endpoints will be added. And when an `endpoints` method wasn't added to the data object, then the resource representation of the data object won't include an endpoints key.
249+
250+
### Using collections of data objects within data objects
251+
252+
When you have a data object which has a collection of data objects as a property, then always type this as a `DataCollection` with an annotation for the resources within the collection:
253+
254+
```php
255+
class ApplicationData extends Data
256+
{
257+
public function __construct(
258+
/** @var \App\Data\ContentThemeData[] */
259+
public DataCollection $themes
260+
) {
261+
}
262+
263+
public static function create(Event $event): static
264+
{
265+
return new self(
266+
ContentThemeData::collection($app->themes)
267+
);
268+
}
269+
}
270+
```
271+
272+
This will make sure data objects can be lazy loaded and that activity logging works as expected.
273+
274+
### Resolving resources from Data objects
275+
276+
You can convert a data object to a resource(array) as such:
277+
278+
```php
279+
ContentThemeData::create($theme)->toArray();
280+
```
281+
282+
When you want an array representation of the data object without transforming the properties like converting the underlying resources into arrays, then you can use:
283+
284+
285+
```php
286+
ContentThemeData::create($theme)->toArray();
287+
```
288+
289+
A data object is also `Responsable` so it can be returned in a controller:
290+
291+
```php
292+
public function (ContentTheme $theme): ContentThemeData
293+
{
294+
return ContentThemeData::create($theme;)
295+
}
296+
```
297+
298+
Collections of data objects are also `Responsable` and `toArray()` can be called on them.
299+
300+
## Lazy properties
301+
302+
Data objects support lazy properties by default, they allow you to only output certain properties when converting a data object to a resource. You create a lazy property as such:
303+
304+
```php
305+
class ContentThemeData extends Data
306+
{
307+
public function __construct(
308+
public string $name,
309+
public int | Lazy $usages,
310+
) {
311+
}
312+
313+
public static function create(ContentTheme $theme): self
314+
{
315+
return new self(
316+
$theme->name,
317+
Lazy::create(fn() => $theme->calculateUsages()),
318+
);
319+
}
320+
}
321+
```
322+
323+
Now when you output this data object as a resource, the `usages` property will be missing since it is quite expensive to calculate:
324+
325+
```php
326+
ContentThemeData::create($theme)->toArray(); // missing usages
327+
```
328+
329+
We can include `usages` in the resource as such:
330+
331+
```php
332+
ContentThemeData::create($theme)->include('usages')->toArray(); // with usages
333+
```
334+
335+
This will also work when we use a data object within a collection, let's take a look at the `ApplicationData` resource from earlier. This one has a `DataCollection` property with `ContentThemeResources` within it. We now can include the `usages` property for all these resources by using the dot notation:
336+
57337
```php
58-
$skeleton = new VendorName\Skeleton();
59-
echo $skeleton->echoPhrase('Hello, Spatie!');
338+
ContentThemeData::create($theme)->include('themes.usages')->toArray(); // with usages
60339
```
61340

62341
## Testing
@@ -79,7 +358,7 @@ Please review [our security policy](../../security/policy) on how to report secu
79358

80359
## Credits
81360

82-
- [:author_name](https://github.com/:author_username)
361+
- [Ruben Van Assche](https://github.com/rubenvanassche)
83362
- [All Contributors](../../contributors)
84363

85364
## License

0 commit comments

Comments
 (0)