Skip to content
Lex de Willigen edited this page Feb 11, 2021 · 4 revisions

If you build a web application with a separate frontend and backend, all kinds of information has to be transferred between these two parts. Part of this are your routes, but how do you share the authorized actions in the most convenient way? One quick note: from now on, we'll call these authorized actions "abilities". To share abilities, we use API resources.
That way we can see the abilities the current user has for that resource.

Now we hear you think, can't I just add a Gate::check() to my API resource and then I have accomplished sharing abilities? That is certainly true, but you will run into some problems. We've tried this approach in a big project. This is what one of our resources looked like (stripped version):

class BoardResource extends Resource
{
    /**
     * Transform the resource into an array.
     *
     * @param Request $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            // Some attributes
            
            'meta' => [
                'abilities' => [
                    BoardPolicy::UPDATE => Gate::check(BoardPolicy::UPDATE, $this->resource),
                    BoardPolicy::DELETE => Gate::check(BoardPolicy::DELETE, $this->resource),
                ],
            ],
        ];
    }
}         

This approach worked perfectly fine. However, the biggest issue we ran into was the fact that the update and delete actions were always checked for every resource that was loaded. Our BoardPolicy performed some database queries to check whether the current user is authorized to perform the action, which resulted in A LOT of queries. So, we reduced the amount of queries by making use of eager loading. That improved our issue quite significantly, however, it still felt like we were loading an unnecessary amount of data since there were quite some pages that didn't require the update or delete abilities at all. We've therefore engineered this package which gives you precise control over which actions should be checked on a page load.

Let's take a look at a new API resource using this package:

class PostResource extends JsonResource
{
    use ProcessesAbilities;

    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'slug' => $this->slug,
            'published_at' => $this->published_at,

            'abilities' => $this->abilities(PostPolicy::class)
        ];
    }
}

Our package will scan the PostPolicy for available actions and check whether the currently authenticated user is authorized to perform a certain action against the given resource. As a result, the json output will look like this:

{
    "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": {
            "view": true,
            "update": false,
            "delete": true
        }
    }
}