A Lightweight {JSON:API} Resource for Laravel.
composer require ark4ne/laravel-json-api
Path | Type | Description |
---|---|---|
describer.nullable |
bool |
For describer notation, defined if a value is nullable by default. |
describer.date |
string datetime format |
For describer notation, defined default date time format. |
describer.precision |
int \ null |
For describer notation, decimal precision for float value. null for disable rounding. |
describer.when-has |
bool \ string[] |
For describer notation, Apply automatically whenHas condition on attributes. |
relationship.when-included |
bool |
Allow to disabled by default the loading of relationship data. |
This package is a 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
This package allows the reading and dynamic inclusion of resources that will be requested in the requests via the "include" parameter.
@see {json:api} fetching-includes
Resource attributes will also be filtered according to the "fields" parameter.
@see {json:api} fetching-fields
You can also very simply validate your requests for a given resource via the rules Rules\Includes
and Rules\Fields
.
use \Ark4ne\JsonApi\Requests\Rules\Includes;
use \Illuminate\Foundation\Http\FormRequest;
class UserFetchRequest extends FormRequest
{
public function rules()
{
return [
'include' => [new Includes(UserResource::class)],
]
}
}
Rules\Includes
will validate the include
to exactly match the UserResource schema (determined by the relationships).
use \Ark4ne\JsonApi\Requests\Rules\Fields;
use \Illuminate\Foundation\Http\FormRequest;
class UserFetchRequest extends FormRequest
{
public function rules()
{
return [
'fields' => [new Fields(UserResource::class)],
]
}
}
Rules\Fields
will validate the fields
to exactly match the UserResource schema (determined by the attributes and relationships).
Trans key | default |
---|---|
validation.custom.jsonapi.fields.invalid |
The selected :attribute is invalid. |
validation.custom.jsonapi.fields.invalid_fields |
":resource" doesn ' t have fields ":fields". |
validation.custom.jsonapi.fields.invalid_resource |
":resource" doesn ' t exists. |
validation.custom.jsonapi.includes.invalid |
The selected :attribute is invalid. |
validation.custom.jsonapi.includes.invalid_includes |
":include" doesn ' t have relationship ":relation". |
Implementable methods :
protected function toType(Request $request): string;
protected function toIdentifier(Request $request): int|string;
protected function toAttributes(Request $request): iterable;
protected function toRelationships(Request $request): iterable;
protected function toResourceMeta(Request $request): ?iterable;
protected function toMeta(Request $request): ?iterable;
Example:
use Ark4ne\JsonApi\Resources\JsonApiResource;
use Illuminate\Http\Request;
class UserResource extends JsonApiResource
{
protected function toAttributes(Request $request): iterable
{
return [
'name' => $this->name,
'email' => $this->email,
];
}
protected function toResourceMeta(Request $request): ?iterable
{
return [
'created_at' => $this->created_at->format(DateTimeInterface::ATOM),
'updated_at' => $this->updated_at->format(DateTimeInterface::ATOM),
];
}
protected function toRelationships(Request $request): iterable
{
return [
'posts' => PostResource::relationship(fn() => $this->posts, fn() => [
'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
'related' => "https://api.example.com/user/{$this->id}/posts",
]),
'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments')),
];
}
}
Returns resource type.
protected function toType(Request $request): string
{
return 'user';
}
Default returns model class in kebab case : App\Models\MyPost
=> my-post
@see {json:api} resource-identifier
Returns resource identifier.
protected function toIdentifier(Request $request): int|string
{
return $this->id;
}
Default returns model id.
@see {json:api} resource-attributes
Returns resource attributes.
protected function toAttributes(Request $request): iterable
{
return [
'name' => $this->name,
'email' => $this->email,
];
}
@see laravel: eloquent-conditional-attributes
Support laravel conditional attributes.
protected function toAttributes(Request $request): array
{
return [
'name' => $this->name,
'email' => $this->email,
// with lazy evaluation
'hash64' => fn() => base64_encode("{$this->id}-{$this->email}"),
// Conditional attribute
'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
// Merging Conditional Attributes
// use applyWhen insteadof mergeWhen for keep fields
// useful for fields request rules validation
$this->applyWhen($request->user()->isAdmin(), [
'first-secret' => 123,
'second-secret' => 456.789,
]),
];
}
@see described notation
protected function toAttributes(Request $request): array
{
return [
'name' => $this->string(),
// pass key to describer
$this->string('email'),
// with lazy evaluation
'hash64' => $this->string(fn() => base64_encode("{$this->id}-{$this->email}")),
// Conditional attribute
$this->string('secret')->when($request->user()->isAdmin(), 'secret-value'),
// Merging Conditional Attributes
$this->applyWhen($request->user()->isAdmin(), [
'first-secret' => $this->integer(fn() => 123),
'second-secret' => $this->float(fn() => 456.789),
]),
];
}
@see {json:api} resources-relationships
Returns resource relationships.
All relationships must be created with ModelResource::relationship
.
This allows the generation of the schema representing the resource and thus the validation of request includes.
If your relation should have been a collection created via the ::collection(...)
method, you can simply use ->asCollection()
.
If you want the relation data to be loaded only when it is present in the request include, you can use the ->whenIncluded()
method.
protected function toRelationships(Request $request): array
{
return [
'avatar' => AvatarResource::relationship($this->avatar),
// with conditional relationship
'administrator' => $this->when($request->user()->isAdmin(), UserResource::relationship(fn() => $this->administrator),
// as collection, with conditional value
'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments'))->asCollection(),
// with relationship (allow to include links and meta on relation)
'posts' => PostResource::relationship(fn() => $this->posts)->withLinks(fn() => [
'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
'related' => "https://api.example.com/user/{$this->id}/posts",
])->asCollection(),
];
}
toRelationships
must returns an array, keyed by string, of JsonApiResource
or JsonApiCollection
.
@see laravel: eloquent-conditional-relationships
Support laravel conditional relationships.
protected function toRelationships(Request $request): array
{
return [
'avatar' => AvatarResource::relationship($this->avatar),
// as collection, with condition
'comments' => CommentResource::relationship(fn() => $this->whenLoaded('comments'))->asCollection(),
// with relationship (allow to include links and meta on relation)
'posts' => PostResource::relationship(fn() => $this->posts)
->asCollection(),
];
}
@see described notation
protected function toRelationships(Request $request): array
{
return [
'avatar' => $this->one(AvatarResource::class),
// custom relation name
'my-avatar' => $this->one(AvatarResource::class, 'avatar'),
// as collection, with condition
'comments' => $this->many(CommentResource::class)
->whenLoaded(),
// with relationship (allow to include links and meta on relation)
'posts' => $this->many(PostResource::class)
->links(fn() => [
'self' => "https://api.example.com/posts/{$this->resource->id}/relationships/posts",
'related' => "https://api.example.com/posts/{$this->resource->id}/posts",
])
->meta(fn() => [
'total' => $this->integer(fn() => $this->resource->posts()->count()),
]),
];
}
@see {json:api}: relation-linkage
@see {json:api}: relation-meta
Returns links and meta for a relation.
protected function toRelationships(Request $request): array
{
return [
'posts' => PostResource::relationship(fn() => $this->posts)->withLinks(fn() => [
// links
'self' => "https://api.example.com/user/{$this->id}/relationships/posts",
'related' => "https://api.example.com/user/{$this->id}/posts",
])->withMeta(fn() => [
// meta
'creator' => $this->name,
])
->asCollection(),
];
}
@see {json:api}: resource-linkage
Returns resource links.
protected function toLinks(Request $request): ?array
{
return [
'self' => route('api.user.show', ['id' => $this->id]),
];
}
@see {json:api}: resource-meta
@see {json:api}: document-meta
Returns resource meta.
protected function toResourceMeta(Request $request): ?iterable
{
return [
'created_at' => $this->created_at->format(DateTimeInterface::ATOM),
'updated_at' => $this->updated_at->format(DateTimeInterface::ATOM),
];
}
@see {json:api}: document-meta
Returns document meta.
protected function toMeta(Request $request): ?iterable
{
return [
"copyright": "Copyright 2022 My Awesome Api",
];
}
@see laravel: resource-collection
Collection are implemented in JsonApiCollection
.
Usage is the same as laravel collections.
UserResource::collection(User::all()); // => JsonApiCollection
Method | Description |
---|---|
bool |
Cast to boolean |
integer |
Cast to integer |
float |
Cast to float |
string |
Cast to string |
date |
Cast to date, allow to use custom format |
array |
Cast to array |
mixed |
Don't cast, return as is |
enum |
Get enum value. |
Method | Description |
---|---|
one |
For relationship with a single value: HasOne , BelongsTo , ... |
many |
For relationship with many value: HasMany , BelongsToMany , ... |
Method enum
allow to get enum value for backed enum or name for unit enum.
According to structure:
/// Role.php
enum Role {
case ADMIN;
case USER;
}
/// State.php
enum State:int {
case ACTIVE = 1;
case INACTIVE = 0;
}
/// User.php
class User extends Model
{
$casts = [
'role' => Role::class,
'state' => State::class,
];
}
The following attributes resource:
// UserResource.php
protected function toAttributes(Request $request): array
{
return [
'status' => $this->enum(),
'role' => $this->enum(),
];
}
Will return:
[
"status": 1,
"role": "ADMIN"
]