# Controllers
# Introduction
Laravel JSON:API's resource routing assigns routes for a specific resource type to a single controller. As described in the Routing section on controllers, the controller name is either inferred from the resource type, or can be explicitly specified when registering resource routes.
# Generating Controllers
JSON:API controllers should be generated using the jsonapi:controller
command, instead of using Laravel's make:controller
command. This
is so that we can generate a controller that has all the actions
required for JSON:API routing. For example:
php artisan jsonapi:controller Api/V1/PostController
This will generate a controller at
app/Http/Controllers/Api/V1/PostController.php
. This means the controller's
fully-qualified class name will be App\Http\Controllers\Api\V1\PostController
.
The generated controller will look like this:
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use LaravelJsonApi\Laravel\Http\Controllers\Actions;
class PostController extends Controller
{
use Actions\FetchMany;
use Actions\FetchOne;
use Actions\Store;
use Actions\Update;
use Actions\Destroy;
use Actions\FetchRelated;
use Actions\FetchRelationship;
use Actions\UpdateRelationship;
use Actions\AttachRelationship;
use Actions\DetachRelationship;
}
As you can see, the methods needed for all the JSON:API route actions are added via traits. Our action traits handle requests and generate responses that conform with the JSON:API specification. They also allow you to specify controller hooks that are called during the action methods.
These actions and hooks are described later in this chapter. However, it is important to explain at this point that we use traits so that it is easier for you to implement custom actions if you need to deviate from our default implementation.
For example, say we only needed a custom implementation for the show
route.
We would remove the Actions\FetchOne
trait, but leave all the other traits.
Then we would just need to write our own show
method on the controller.
See the next chapter for a complete guide to writing your own controller actions.
# JSON:API Controller
If you have a resource type that does not need to use any controller hooks,
and does not need to override any of our default action implementations,
you can use our JsonApiController
. This removes the need for you to
generate a controller for a resource type. This can be registered as follows:
use LaravelJsonApi\Laravel\Http\Controllers\JsonApiController;
JsonApiRoute::server('v1')
->prefix('v1')
->resources(function ($server) {
$server->resource('posts', JsonApiController::class);
});
If you are using controller namespacing, you will need to fully-qualify the generic controller when registering it. For example:
use LaravelJsonApi\Laravel\Http\Controllers\JsonApiController;
JsonApiRoute::server('v1')
->prefix('v1')
->namespace('Api\V1')
->resources(function ($server) {
$server->resource('posts', '\\' . JsonApiController::class);
});
# Controller Hooks
Each of our default controller actions will invoke methods on the controller
if they are implemented. For example, the store
action will invoke the
saving
, creating
, created
and saved
hooks, if they are implemented
on the controller.
These hooks allow you to easily implement application specific actions, such as firing events or dispatching jobs. They also allow you to return customised responses. If you return a response from any hook, the controller action will stop executing and immediately return the response.
# Resource Hooks
# Fetch-Many aka Index
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\FetchMany
trait
implements the index
controller action. This fetches a list of resources.
This action has two hooks: searching
and searched
.
The searching
hook will be called with the validated query parameters
for the resource collection. This will either be an instance of
LaravelJsonApi\Laravel\Http\Requests\AnonymousCollectionQuery
, or
the specific class if you have generated one for your resource,
for example:
use App\JsonApi\V1\Posts\PostCollectionQuery;
public function searching(PostCollectionQuery $query): void
{
// do something...
}
TIP
Both the AnonymousCollectionQuery
and PostCollectionQuery
extend
Laravel's FormRequest
class. More details are available in the
Requests chapters.
The searched
hook will be called once models have been retrieved from
the database. The first argument will be the result of the query,
and the second will be the query request class. For example:
use App\JsonApi\V1\Posts\PostCollectionQuery;
public function searched($data, PostCollectionQuery $query): void
{
// e.g. dispatch a job.
}
The searched
hook can return a response to override the default response
created by the FetchMany
trait.
WARNING
The $data
argument provided to the searched
hook could be a mixture
of types. For example, it could be an Eloquent collection or a JSON:API
page object - or even a model or null if a singular filter was used.
It is therefore likely you will need to check the type before taking
action within your searched
hook.
# Fetch-One aka Show
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\FetchOne
trait
implements the show
controller action. This fetches a specified
resource.
This action has two hooks: reading
and read
.
The reading
hook will be called with the validated query parameters
for the resource. This will either be an instance of
LaravelJsonApi\Laravel\Http\Requests\AnonymousQuery
, or
the specific class if you have generated one for your resource,
for example:
use App\JsonApi\V1\Posts\PostQuery;
public function reading(PostQuery $query): void
{
// do something...
}
TIP
Both the AnonymousQuery
and PostQuery
classes extend
Laravel's FormRequest
class. More details are available in the
Requests chapters.
The read
hook will be called once model has been retrieved from
the database. The first argument will be the model,
and the second will be the query request class. For example:
use App\JsonApi\V1\Posts\PostQuery;
use App\Models\Post;
public function read(?Post $post, PostCollectionQuery $query): void
{
// e.g. dispatch a job.
}
The read
hook can return a response to override the default response
created by the FetchOne
trait.
WARNING
The $model
argument provided to the read
hook is nullable
for a
reason. If the client provides filter query parameters, the model
may not match the filters and therefore the result of retrieving
it from the database will be null
.
Whatever you do in the read
hook,
you should ensure that it handles a null
value safely.
# Store
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\Store
trait
implements the store
controller action. This creates a new resource.
This action has four hooks: saving
, creating
, created
and saved
.
Note that the saving
and saved
hooks are also called by the
Update action.
The saving
hook will be called with three arguments:
null
to indicate that this is a create, not an update.- The JSON:API request class for the resource, which will have validated
the JSON document in the request, e.g.
PostRequest
. - The validated query parameters - either an instance of
LaravelJsonApi\Laravel\Http\Requests\AnonymousQuery
, or the specific class if you have generated one for your resource (e.g.PostQuery
).
For example:
use App\JsonApi\V1\Posts\PostQuery;
use App\JsonApi\V1\Posts\PostRequest;
use App\Models\Post;
public function saving(?Post $post, PostRequest $request, PostQuery $query): void
{
// do something on creating and updating...
}
The creating
hook will only receive the request classes, for example:
use App\JsonApi\V1\Posts\PostQuery;
use App\JsonApi\V1\Posts\PostRequest;
public function creating(PostRequest $request, PostQuery $query): void
{
// do something only on creating...
}
TIP
All the PostRequest
, AnonymousQuery
and PostQuery
classes extend
Laravel's FormRequest
class. More details are available in the
Requests chapters.
The saved
and created
hooks will be called once model has been created
and stored in the database. They both receive three arguments:
- The model that was created.
- The JSON:API request class for the resource.
- The validated query parameters.
For example:
use App\JsonApi\V1\Posts\PostQuery;
use App\JsonApi\V1\Posts\PostRequest;
use App\Models\Post;
public function saved(Post $post, PostRequest $request, PostQuery $query): void
{
// do something on created and updated...
}
public function created(Post $post, PostRequest $request, PostQuery $query): void
{
// do something only on created...
}
Both the saved
and created
hooks can return a response to override
the default response created by the Store
trait. Note that if the
saved
hook returns a response, the created
hook will not be
invoked.
# Update
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\Update
trait
implements the update
controller action. This updates an existing resource.
This action has four hooks: saving
, updating
, updated
and saved
.
Note that the saving
and saved
hooks are also called by the
Store action.
The saving
and updating
hooks will be called with three arguments:
- The model that is being updated.
- The JSON:API request class for the resource, which will have validated
the JSON document in the request, e.g.
PostRequest
. - The validated query parameters - either an instance of
LaravelJsonApi\Laravel\Http\Requests\AnonymousQuery
, or the specific class if you have generated one for your resource (e.g.PostQuery
).
For example:
use App\JsonApi\V1\Posts\PostQuery;
use App\JsonApi\V1\Posts\PostRequest;
use App\Models\Post;
public function saving(?Post $post, PostRequest $request, PostQuery $query): void
{
// do something on creating and updating...
}
public function updating(Post $post, PostRequest $request, PostQuery $query): void
{
// do something only on updating...
}
TIP
All the PostRequest
, AnonymousQuery
and PostQuery
classes extend
Laravel's FormRequest
class. More details are available in the
Requests chapters.
The saved
and updated
hooks will be called once model has been updated
and stored in the database. They both receive the same three arguments.
For example:
use App\JsonApi\V1\Posts\PostQuery;
use App\JsonApi\V1\Posts\PostRequest;
use App\Models\Post;
public function saved(Post $post, PostRequest $request, PostQuery $query): void
{
// do something on created and updated...
}
public function updated(Post $post, PostRequest $request, PostQuery $query): void
{
// do something only on created...
}
Both the saved
and updated
hooks can return a response to override
the default response created by the Update
trait. Note that if the
saved
hook returns a response, the updated
hook will not be
invoked.
# Destroy
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\Destroy
trait
implements the destroy
controller action. This deletes an existing resource.
This action has two hooks: deleting
and deleted
. These hooks
will be called with two arguments:
- The model that is being deleted.
- The JSON:API request class for the resource, which will validated
the delete request, e.g.
PostRequest
.
For example:
use App\JsonApi\V1\Posts\PostRequest;
use App\Models\Post;
public function deleting(Post $post, PostRequest $request): void
{
// do something...
}
public function deleted(Post $post, PostRequest $request): void
{
// do something...
}
TIP
The PostRequest
class extends from Laravel's FormRequest
class.
More details are available in the Requests chapters.
The default response returned by the Destroy
trait is a
204 No Content
response. The deleted
hook can return a response to
override this default response.
# Relationship Hooks
# Concept
An important concept to highlight with relationship actions is that the JSON:API query parameters relate to the resource type returned by the relationship, not the resource type on which the relationship exists.
The type of query parameters class is also determined by whether the relation is a to-one or to-many relationship.
For to-one, the query class will be for a zero-to-one response.
For example, if we have an author
relationship on a posts
resource,
that returns zero-to-one users
resources. The query class will
either be the UserQuery
class if it exists, or our
LaravelJsonApi\Laravel\Http\Requests\AnonymousQuery
class.
For to-many, the query class will be for a zero-to-many response.
For example, if we have a tags
relationship on a posts
resource,
that returns zero-to-many tags
resource. The query class will
either be the TagCollectionQuery
class if it exists, or our
LaravelJsonApi\Laravel\Http\Requests\AnonymousCollectionQuery
class.
# Fetch-Related aka Show-Related
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\FetchRelated
trait
implements the showRelated
controller action. This fetches the
related resource(s) for a relationship.
This action has two hooks: readingRelated{Field}
and readRelated{Field}
.
The field is the field-name for the relationship.
The readingRelated{Field}
hook receives two arguments:
- The model on which the relationship exists.
- The query class for the resource type returned by the relationship.
For example if a posts
resource has a to-one author
relationship,
and a to-many tags
relationship, the following readingRelated
hooks
can exist:
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\JsonApi\V1\Users\UserQuery;
use App\Models\Post;
public function readingRelatedAuthor(Post $post, UserQuery $query): void
{
// do something...
}
public function readingRelatedTags(Post $post, TagCollectionQuery $query): void
{
// do something...
}
TIP
Both the AnonymousQuery
, AnonymousCollectionQuery
and specific classes
(e.g. UserQuery
and TagCollectionQuery
) extend Laravel's FormRequest
class. More details are available in the Requests chapters.
The readRelated{Field}
hook recieves three arguments:
- The model on which the relationship exists.
- The data returned by the relationship. For a to-one relation, this will
be the related model or
null
. For a to-many, this will be of mixed type depending on the request filter and page parameters. - The query class for the resource type returned by the relationship.
Using our example of an author
and a tags
relation on a posts
resource:
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\JsonApi\V1\Users\UserQuery;
use App\Models\Post;
use App\Models\User;
public function readRelatedAuthor(Post $post, ?User $user, UserQuery $query): void
{
// do something...
}
public function readRelatedTags(Post $post, $data, TagCollectionQuery $query): void
{
// do something...
}
The readRelated{Field}
hook can return a response to override the default
response created by the FetchRelationship
trait.
WARNING
The $user
argument provided to the readRelatedAuthor
hook is
nullable
for a reason. If the client provides filter query parameters,
the model may not match the filters and therefore the result of retrieving
the relation it from the database will be null
.
For to-many relations, the $data
argument provided to the readRelatedTags
hook will be of mixed type. It could be an Eloquent collection or a JSON:API
page object - or even a model or null if a singular filter was used.
You will need to check the type before taking action with it.
# Fetch-Relationship aka Show-Relationship
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\FetchRelationship
trait
implements the showRelationship
controller action. This fetches the
related resource(s) for a relationship and returns their resource identifiers.
This action has two hooks: reading{Field}
and
read{Field}
. The field is the field-name for the relationship.
The reading{Field}
hook receives two arguments:
- The model on which the relationship exists.
- The query class for the resource type returned by the relationship.
For example if a posts
resource has a to-one author
relationship,
and a to-many tags
relationship, the following hooks can exist:
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\JsonApi\V1\Users\UserQuery;
use App\Models\Post;
public function readingAuthor(Post $post, UserQuery $query): void
{
// do something...
}
public function readingTags(Post $post, TagCollectionQuery $query): void
{
// do something...
}
TIP
Both the AnonymousQuery
, AnonymousCollectionQuery
and specific classes
(e.g. UserQuery
and TagCollectionQuery
) extend Laravel's FormRequest
class. More details are available in the Requests chapters.
The read{Field}
hook receives three arguments:
- The model on which the relationship exists.
- The data returned by the relationship. For a to-one relation, this will
be the related model or
null
. For a to-many, this will be of mixed type depending on the request filter and page parameters. - The query class for the resource type returned by the relationship.
Using our example of an author
and a tags
relation on a posts
resource:
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\JsonApi\V1\Users\UserQuery;
use App\Models\Post;
use App\Models\User;
public function readAuthorRelationship(Post $post, ?User $user, UserQuery $query): void
{
// do something...
}
public function readTagsRelationship(Post $post, $data, TagCollectionQuery $query): void
{
// do something...
}
The read{Field}
hook can return a response to override the default
response created by the FetchRelationship
trait.
WARNING
The $user
argument provided to the readAuthor
hook is nullable
for
a reason. If the client provides filter query parameters,
the model may not match the filters and therefore the result of retrieving
the relation it from the database will be null
.
For to-many relations, the $data
argument provided to the
readTags
hook will be of mixed type. It could be an Eloquent
collection or a JSON:API page object - or even a model or null if a singular
filter was used. You will need to check the type before taking action with it.
# Update-Relationship
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\UpdateRelationship
trait
implements the updateRelationship
controller action. This completely
replaces the contents of a relationship, and then returns the
resource identifiers of the new relationship value.
This action has two hooks: updating{Field}
and
updated{Field}
. The field is the field-name for the relationship.
The updating{Field}
hook receives three arguments:
- The model on which the relationship exists.
- The JSON:API request class for the resource that is having its relationship
updated, e.g.
PostRequest
when updating aposts
author
relationship. - The query class for the resource type returned by the relationship.
For example if a posts
resource has a to-one author
relationship,
and a to-many tags
relationship, the following hooks can exist:
use App\JsonApi\V1\Posts\PostRequest;
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\JsonApi\V1\Users\UserQuery;
use App\Models\Post;
public function updatingAuthor(
Post $post,
PostRequest $request,
UserQuery $query
): void
{
// do something...
}
public function updatingTags(
Post $post,
PostRequest $request,
TagCollectionQuery $query
): void
{
// do something...
}
TIP
Both the AnonymousQuery
, AnonymousCollectionQuery
and specific classes
(e.g. UserQuery
and TagCollectionQuery
) extend Laravel's FormRequest
class. More details are available in the Requests chapters.
The updated{Field}
hook receives four arguments:
- The model on which the relationship exists.
- The data that replaced the relationship. For a to-one relation, this will
be the related model or
null
. For a to-many, this will typically be an Eloquent collection. - The JSON:API request class for the resource that had its relationship updated.
- The query class for the resource type returned by the relationship.
Using our example of an author
and a tags
relation on a posts
resource:
use App\JsonApi\V1\Posts\PostRequest;
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\JsonApi\V1\Users\UserQuery;
use App\Models\Post;
use App\Models\User;
public function updatedAuthor(
Post $post,
?User $user,
PostRequest $request,
UserQuery $query
): void
{
// do something...
}
public function updatedTags(
Post $post,
$tags,
PostRequest $request,
TagCollectionQuery $query
): void
{
// do something...
}
The updated{Field}
hook can return a response to override the default
response created by the UpdateRelationship
trait.
# Attach-Relationship
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\AttachRelationship
trait
implements the attachRelationship
controller action. This action
attaches models to a to-many relationship.
This action has two hooks: attaching{Field}
and attached{Field}
.
The field is the field-name for the relationship.
The attaching{Field}
hook receives three arguments:
- The model on which the relationship exists.
- The JSON:API request class for the resource that is having its relationship
updated, e.g.
PostRequest
when updating aposts
tags
relationship. - The query class for the resource type returned by the relationship.
For example if a posts
resource has to-many tags
relationship,
the attachingTags
hook can exist:
use App\JsonApi\V1\Posts\PostRequest;
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\Models\Post;
public function attachingTags(
Post $post,
PostRequest $request,
TagCollectionQuery $query
): void
{
// do something...
}
TIP
Both the AnonymousCollectionQuery
and specific classes
(e.g. TagCollectionQuery
) extend Laravel's FormRequest
class. More details are available in the Requests chapters.
The attached{Field}
hook receives four arguments:
- The model on which the relationship exists.
- The data that was attached to the relationship, as an Eloquent collection.
- The JSON:API request class for the resource that had its relationship updated.
- The query class for the resource type returned by the relationship.
Using our example of a tags
relation on a posts
resource:
use App\JsonApi\V1\Posts\PostRequest;
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\Models\Post;
public function attachedTags(
Post $post,
$tags,
PostRequest $request,
TagCollectionQuery $query
): void
{
// do something...
}
The attached{Field}
hook can return a response to override the default
204 No Content
created by the AttachRelationship
trait.
# Detach-Relationship
Our LaravelJsonApi\Laravel\Http\Controllers\Actions\DetachRelationship
trait
implements the detachRelationship
controller action. This action
detaches models from a to-many relationship.
This action has two hooks: detaching{Field}
and detached{Field}
.
The field is the field-name for the relationship.
The detaching{Field}
hook receives three arguments:
- The model on which the relationship exists.
- The JSON:API request class for the resource that is having its relationship
updated, e.g.
PostRequest
when updating aposts
tags
relationship. - The query class for the resource type returned by the relationship.
For example if a posts
resource has to-many tags
relationship,
the detachingTags
hook can exist:
use App\JsonApi\V1\Posts\PostRequest;
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\Models\Post;
public function detachingTags(
Post $post,
PostRequest $request,
TagCollectionQuery $query
): void
{
// do something...
}
TIP
Both the AnonymousCollectionQuery
and specific classes
(e.g. TagCollectionQuery
) extend Laravel's FormRequest
class. More details are available in the Requests chapters.
The detached{Field}
hook receives four arguments:
- The model on which the relationship exists.
- The data that was detached from the relationship, as an Eloquent collection.
- The JSON:API request class for the resource that had its relationship updated.
- The query class for the resource type returned by the relationship.
Using our example of a tags
relation on a posts
resource:
use App\JsonApi\V1\Posts\PostRequest;
use App\JsonApi\V1\Tags\TagCollectionQuery;
use App\Models\Post;
public function detachedTags(
Post $post,
$tags,
PostRequest $request,
TagCollectionQuery $query
): void
{
// do something...
}
The detached{Field}
hook can return a response to override the default
204 No Content
created by the DetachRelationship
trait.