Package Data | |
---|---|
Maintainer Username: | timacdonald |
Maintainer Contact: | hello@timacdonald.me (Tim MacDonald) |
Package Create Date: | 2021-08-09 |
Package Last Update: | 2024-10-07 |
Home Page: | |
Language: | PHP |
License: | MIT |
Last Refreshed: | 2024-11-15 15:13:07 |
Package Statistics | |
---|---|
Total Downloads: | 362,068 |
Monthly Downloads: | 18,120 |
Daily Downloads: | 858 |
Total Stars: | 585 |
Total Watchers: | 11 |
Total Forks: | 39 |
Total Open Issues: | 8 |
A lightweight JSON Resource for Laravel that helps you adhere to the JSON:API standard and also implements features such as sparse fieldsets and compound documents.
These docs are not designed to introduce you to the JSON:API spec and the associated concepts, instead you should head over and read the spec if you are not familiar with it. The documentation that follows only contains information on how to implement the specification via the package.
You can install using composer from Packagist.
composer require timacdonald/json-api
This package is an specialisation of Laravel's JsonResource
class. All the underlying API's are still there, thus in your controller you can still interact with JsonApiResource
classes as you would with the base JsonResource
class, e.g.
<?php
class UserController
{
public function index()
{
return UserResource::collection(User::paginate());
}
public function show(User $user)
{
return UserResource::make($user);
}
}
The internal developer facing API however has changed in that you no longer interact with the toArray($request)
method, instead this package exposes some new methods to interact with. More on those shortly.
We have defined a sensible default for you so you can hit the ground running without having to fiddle with the small stuff.
The "id"
and "type"
of a resource is automatically resolved for you under-the-hood if you are using resources solely with Eloquent models.
"id"
is resolved by calling the $model->getKey()
method and the "type"
is resolved by using a camel case of the model's table name, e.g. blog_posts
becomes blogPosts
.
You can customise how this works to support other types of objects and behaviours, but that will follow in the advanced usage section.
Nice. Well that was easy, so let's move onto...
To provide a set of attributes for a resource, you can implement the toAttributes(Request $request)
method...
<?php
class UserResource extends JsonApiResource
{
protected function toAttributes(Request $request): array
{
return [
'name' => $this->name,
'email' => $this->email,
];
}
}
The advanced usage section covers sparse fieldsets and handling expensive attribute calculation and minimal attribute payloads, but you can ignore those advanced features for now and continue on with...
Just like we saw with attributes above, we can specify relationships that should be available on the resource by using the toRelationships(Request $request)
method, however with relationships you should always wrap the values in a Closure
.
<?php
class UserResource extends JsonApiResource
{
protected function toRelationships(Request $request): array
{
return [
'posts' => fn () => PostResource::collection($this->posts),
'subscription' => fn () => SubscriptionResource::make($this->subscription),
'profileImage' => fn () => optional($this->profileImage, fn (ProfileImage $profileImage) => ProfileImageResource::make($profileImage)),
// if the relationship has been loaded and is null, can we not just return the resource still and have a nice default? That way you never have to handle any of this
// optional noise?
// also is there a usecase for returning a resource linkage right from here and not a full resource?
];
}
}
Note: "links" and "meta" are not yet supported for relationships, but they are WIP. Resource linkage "meta" is not yet implemented. Let me know if you have a use-case you'd like to use it for!
Each Closure
is only resolved when the relationship has been included by the client...
JSON:API docs: Inclusion of Related Resources
As previously mentioned, relationships are not included in the response unless the calling client requests them. To do this, the calling client needs to "include" them by utilising the include
query parameter.
# Include the posts...
/api/users/8?include=posts
# Include the subscription...
/api/users/8?include=subscription
# Include both...
/api/users/8?include=posts,subscription
To provide links for a resource, you can implement the toLinks(Request $request)
method...
<?php
use TiMacDonald\JsonApi\Link;
class UserResource extends JsonApiResource
{
protected function toLinks(Request $request): array
{
return [
Link::self(route('users.show', $this->resource)),
'related' => 'https://example.com/related'
];
}
}
To provide meta information for a resource, you can implement the toMeta(Request $request)
method...
<?php
class UserResource extends JsonApiResource
{
protected function toMeta(Request $request): array
{
return [
'resourceDeprecated' => true,
];
}
}
If you have an existing API that utilises Laravel's JsonApiResource
or other values that you would like to migrate over to the JSON:API standard via this package, it might be a big job. For this reason, we've enabled you to migrate piece by piece so you can slowly refactor your API.
From a relationship Closure
you can return anything. If what you return is not a JsonApiResource
or JsonApiResourceCollection
, then the value will be "inlined" in the relationships object.
<?php
class UserResource extends JsonApiResource
{
protected function toRelationships(Request $request): array
{
return [
'nonJsonApiResource' => fn (): JsonResource => LicenseResource::make($this->license),
];
}
}
Here is what that response might look like. Notice that the resource is "inlined" and is not moved out to the "included" section of the payload.
{
"data": {
"id": "1",
"type": "users",
"attributes": {},
"relationships": {
"nonJsonApiResource": {
"id": "5",
"key": "4h29kaKlWja)99ja72kafj&&jalkfh",
"created_at": "2020-01-04 12:44:12"
}
},
"meta": {},
"links": {}
},
"included": []
}
// TODO
"id"
You can customise the resolution of the id
by specifying an id resolver in your service provider.
<?php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
JsonApiResource::resolveIdUsing(function (mixed $resource, Request $request): string {
// your custom resolution logic...
});
}
}
Although it is not recommended, you can also override the toId(Request $request): string
method on a resource by resource basis.
"type"
You can customise the resolution of the type
by specifying a type resolver in your service provider.
<?php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
JsonApiResource::resolveTypeUsing(function (mixed $resource, Request $request): string {
// your custom resolution logic...
});
}
}
Although it is not recommended, you can also override the toType(Request $request): string
method on a resource by resource basis.
JSON:API docs: Sparse fieldsets
Without any work, your response supports sparse fieldsets. If you are utilising sparse fieldsets and have some attributes that are expensive to create, it is a good idea to wrap them in a Closure
. Under the hood, we only resolve the Closure
if the attribute is to be included in the response.
<?php
class UserResource extends JsonResource
{
protected function toAttributes(Request $request): array
{
return [
'name' => $this->name,
'email' => $this->email,
'profile_image' => fn () => base64_encode(
// don't really download a file like this. It's just an example of a slow operation...
file_get_contents('https://www.gravatar.com/avatar/'.md5($this->email)),
),
];
}
}
The Closure
is only resolved when the attribute is going to be included in the response, which can improve performance of requests that don't require the returned value.
# The Closure is not resolved...
/api/users/8?fields[users]=name,email
# The Closure is resolved...
/api/users/8?fields[users]=name,profile_image
Out of the box the resource provides a maximal attribute payload when sparse fieldsets are not used i.e. all declared attributes in the resource are returned. If you prefer to instead make it that spare fieldsets are required in order to retrieve any attributes, you can specify the use of minimal attributes in your applications service provider.
<?php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
JsonApiResource::minimalAttributes();
// ...
}
}
JSON:API docs: Inclusion of Related Resources
Relationships can be resolved deeply and also multiple relationship paths can be included. Of course you should be careful about n+1 issues, which is why we recommend using this package in conjunction with Spatie's Query Builder.
# Including deeply nested relationships
/api/posts/8?include=author.comments
# Including multiple relationship paths
/api/posts/8?include=comments,author.comments
And a special (vegi) thanks to Caneco for the logo ✨
->when()
stuff for attributes and relationshipsnull
as the Laravel resource does not support this. Is possible to support locally, but it might be unexpected. Perhaps a PR to Laravel is best?400
when requesting relationships that are not present.