bchalier / laravel-json-api
A Lightweight JSON:API Resource for Laravel
Requires
- php: ^8.1
- illuminate/collections: ^11.0
- illuminate/http: ^11.0
- illuminate/support: ^11.0
Requires (Dev)
- orchestra/testbench: ^9.0
- phpstan/phpstan: ^1.9.18
- phpunit/phpunit: ^9.5|^10.0
This package is auto-updated.
Last update: 2024-11-21 12:11:07 UTC
README
A Lightweight {JSON:API} Resource for Laravel.
Installation
composer require ark4ne/laravel-json-api
Config
Usage
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
Request
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
.
Include validation
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).
Fields validation
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).
Customize validation message
Resource
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')), ]; } }
toType
Returns resource type.
protected function toType(Request $request): string { return 'user'; }
Default returns model class in kebab case : App\Models\MyPost
=> my-post
toIdentifier
@see {json:api} resource-identifier
Returns resource identifier.
protected function toIdentifier(Request $request): int|string { return $this->id; }
Default returns model id.
toAttributes
@see {json:api} resource-attributes
Returns resource attributes.
protected function toAttributes(Request $request): iterable { return [ 'name' => $this->name, 'email' => $this->email, ]; }
Laravel conditional attributes
@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, ]), ]; }
Described attributes
@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), ]), ]; }
toRelationships
@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
.
Laravel conditional relationships
@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(), ]; }
Described attributes
@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()), ]), ]; }
Relation links and meta
@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(), ]; }
toLinks
@see {json:api}: resource-linkage
Returns resource links.
protected function toLinks(Request $request): ?array { return [ 'self' => route('api.user.show', ['id' => $this->id]), ]; }
toResourceMeta
@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), ]; }
toMeta
@see {json:api}: document-meta
Returns document meta.
protected function toMeta(Request $request): ?iterable { return [ "copyright": "Copyright 2022 My Awesome Api", ]; }
Collection
@see laravel: resource-collection
Collection are implemented in JsonApiCollection
.
Usage is the same as laravel collections.
UserResource::collection(User::all()); // => JsonApiCollection
Described notation
Value methods
Relation methods
Enum
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" ]