Gates
A client often needs to know whether to show an edit or a delete button on a row. Ask for gates in a search, and each row answers the abilities of your policies for the current user.
Asking for gates
{
"search": {
"gates": ["view", "update", "create"]
}
}
An excerpt of the page, when the policy denies the update with Response::deny('Closed tickets are read-only.'):
{
"current_page": 1,
"data": [
{"id": 2, "title": "Password reset", "gates": {"authorized_to_view": {"allowed": true, "message": null}, "authorized_to_update": {"allowed": false, "message": "Closed tickets are read-only."}}}
],
"meta": {"gates": {"authorized_to_create": {"allowed": true, "message": null}}}
}
- Each answer holds
allowedandmessage: the message of the policy'sResponse::deny(), ornull. gatestakesview,create,update,delete,restoreandforceDelete, and the gates that the resource declares, each once.restoreandforceDeleteare gates only when the model of the resource usesSoftDeletes.viewAny, another name, orrestoreandforceDeleteon another model, is a422:The selected search.gates.0 is invalid.An ability listed twice givesThe search.gates.0 field has a duplicate value.on each copy.createdoes not depend on a row: its answer goes to themetaof the page. It is accepted at the root of a search only.- An include takes its own
gates, answered on each included row. See Includes and aggregates. - A row whose
gateshold no answer returns{}. A search withoutgatesreturns nogateskey on its rows.
Gates and the perimeter
update, delete, restore and forceDelete answer for the rows that their endpoint reaches. A row that the endpoint would refuse with a 422 answers {"allowed": false, "message": null} without calling the policy:
- a trashed row, for
updateanddelete, unlessperimeterQuery()takes trashed rows in; - a row of a search engine page that
perimeterQuery()leaves out. See Search engines.
Declaring your own gates
A resource declares more gates in gates(): the names of policy methods that receive the user and the model.
public function gates(): array
{
return ['download'];
}
// PostPolicy
public function download(User $user, Post $post): bool
{
return $post->published;
}
A search of that resource then accepts download in gates, at the root and in each include of that resource, and each row answers it under authorized_to_download.
- A name is the camelCase name of a policy method, listed once, other than
viewAnyand the six abilities above. Another value throwsLomkit\Rest\Exceptions\InvalidResourceExceptionwhen the gates are first read. - A declared gate is answered on each row, never in
meta. - A policy without the method denies it, with a
nullmessage, as Laravel does. - The details list every gate that a search accepts, with
?with=gates.
Naming the answers
config/rest.php names the key of the answers and each answer:
'gates' => [
'key' => 'gates',
'names' => [
'authorized_to_view' => 'authorized_to_view',
'authorized_to_create' => 'authorized_to_create',
'authorized_to_update' => 'authorized_to_update',
'authorized_to_delete' => 'authorized_to_delete',
'authorized_to_restore' => 'authorized_to_restore',
'authorized_to_force_delete' => 'authorized_to_force_delete',
],
],
gates.keyrenames thegateskey of the rows and ofmeta. A field named like it throwsInvalidResourceException.gates.namesrenames each answer. It also renames the gates that a resource declares, by their default name:'authorized_to_download' => 'can_download'.
Turning the gates off
Override isGatingEnabled() on a resource, or on the base resource for every resource, and return false:
public function isGatingEnabled(): bool
{
return false;
}
At the root of a search, gates stays accepted and returns no answer. In an include of such a resource, gates is a 422: The search.includes.0.gates field is prohibited: the included resource turns its gates off.
Cost
Each answer goes through the resource's authorizedTo(), so the policy runs once per row and gate in a request. To keep the answers between requests, declare the abilities in authorizationCache(), see Decision cache.