Checking actions - agilepixels/laravel-resource-abilities GitHub Wiki
Now that your gates and policies are defined, you would normally have all the information available in the frontend. On the other hand, your application quickly checks far too many gates and policies without them being necessary for the specific route. As a result, it can happen that unnecessary queries are executed, making your application less efficient and less fast. That is why this package offers the possibility to specify exactly which actions must be checked. By default, all available actions will be checked.
We'll now be speaking of "adding abilities" which is different than defining them. The definitions of the abilities are defined in the API resources (see Resource Setup). However, adding abilities means specifying that a certain action should be checked.
This package provides two primary ways to specify which actions should be checked. You can add the abilities on the model itself or in the query builder.
If your route is working with just one model, you can add an ability using the checkAbility()
method provided in the
HasAbilities
trait. Typically, your controller should look something like this:
public function show(Post $post)
{
$post->checkAbility('update');
return PostResource::make($post);
}
The package will now check the update
action and include the result in the response. This might look like the
following example:
{
"data": {
"id": 10,
"title": "Corporis eum adipisci et cum nostrum.",
"slug": "corporis-eum-adipisci-et-cum-nostrum",
"published_at": "2020-06-06T10:49:01.000000Z",
"abilities": {
"update": false
}
}
}
Of course, you may want to check multiple actions. This can be done in several ways, use whatever you like:
// comma separated
$post->checkAbility('view', 'update', 'delete');
// using an array
$post->checkAbility(['view', 'update', 'delete']);
// multiple checkAbility() calls
$post->checkAbility('view')
->checkAbility('update')
->checkAbility('delete');
But what if you're getting a whole collection of models. Performance wise it is inefficient to run across the entire collection and add the right abilities everywhere using a map function. That is why it is also possible to add the abilities at the builder level, so that the added abilities are immediately hydrated during the loading of the models. In your controller, this looks like this:
public function index()
{
return PostResource::collection(
Post::query()
->checkAbility('update')
->get()
);
}
The package will now automatically add the abilities to all posts. This can also be used to load certain relationship abilities from the current resource. Eloquent can "eager load" relationships at the time you query the parent model. Using eager loading it's also possible to indicate via a closure exactly how the relationship should be loaded. That is why it is also the perfect place to add the abilities for the relationship.
public function index()
{
return PostResource::collection(
Post::query()
->with(['author' => fn (BelongsTo $belongsTo) => $belongsTo->checkAbility('view')])
->checkAbility('update')
->get()
);
}
The json response from your controller will now check the view
action for the author
relationship.
{
"data": [
{
"id": 1,
"title": "Perspiciatis veritatis rerum voluptatem reprehenderit earum rerum quod.",
"slug": "perspiciatis-veritatis-rerum-voluptatem-reprehenderit-earum-rerum-quod",
"published_at": "2018-04-12T23:31:59.000000Z",
"author": {
"id": 1053,
"name": "Gavin Waters",
"abilities": {
"view": true
}
},
"abilities": {
"update": true
}
},
// More posts with authors
]
}
When authorizing actions, you may pass a parameters array as the second argument to the abilities()
method. These parameters will be added when checking the ability on response.
class PostPolicy
{
use HandlesAuthorization;
public function restore(User $user, Post $post, bool $parameter): bool
{
// Use $parameter to authorize the restore action
}
}
public function toArray($request): array
{
return [
'abilities' => $this->abilities(PostPolicy::class, [
true
]),
];
}
public function toArray($request): array
{
return [
'abilities' => $this->abilities('restore', [
true
]),
];
}
If you want to change how the abilities are structured, you can use serializers to output the abilities in different
formats or write your own serializers. By default, the AbilitySerializer
is used which outputs the abilities in the
format: ability => granted
.
"abilities": {
"view": true,
"update": true,
"delete": false
}
The serializer that will be used can be changed in two ways. Either by changing the resource-abilities.serializer
config or by passing the serializer class as third (serializer:
when using named parameters) parameter to the
abilities()
method. This package also provides the ExtendedAbilitySerializer
which outputs in the following format:
"abilities": {
"view": {
"ability": "view",
"granted": true,
},
"update": {
"ability": "update",
"granted": true,
},
"delete": {
"ability": "delete",
"granted": false,
},
}
You're free to provide and use your own serializer. You can do so by implementing the
AgilePixels\ResourceAbilities\Serializers\Serializer
interface and add it to the config file or the abilities()
method.