Laravel - Refactoring User Permission "Gate::Define" Code Into Easier to Read Code Laravel - Refactoring User Permission "Gate::Define" Code Into Easier to Read Code laravel laravel

Laravel - Refactoring User Permission "Gate::Define" Code Into Easier to Read Code


First of all, the author of that article did not use policies at all, he created a permissions table and then bound the permissions he created to laravel gates by the code snippet

 Permission::get()->map(function($permission){    Gate::define($permission->slug, function($user) use ($permission){       return $user->hasPermissionTo($permission);    }); });

Let's break it line by line

Permission::get() // Query all permissions defined in permissions database table->map(function($permission){ // Foreach permission do the following   Gate::define($permission->slug, // Create new gate with the permission slug   function($user) use ($permission){      return $user->hasPermissionTo($permission); // the user table has many to many relation with permissions table, here we only check if $user is associated with $permission   });});

To make your code more dynamic, I suggest you to do the following:

Database structure

  1. Create permission database table

  2. Create roles database table

  3. Create permission_role pivot database table

  4. Create role_user pivot database table

Define Relationships

  1. Role has many permissions ( many to many relationship, define it with belongsToMany )

  2. Permission belongs to many roles ( many to many relationship, define it with belongsToMany )

  3. User has many roles ( many to many relationship, define it with belongsToMany )

Reduce the number of global permissions

By utilising Gate::before you can allow specific user who has global or root permission to authorise all defined abilities:

Gate::before(function ($user, $ability) {    if ($user->hasPermission('root-access')) {        return true;    }});

If you implement the database permissions you no longer need to create policies for every model, and the gates will be defined using the above code dynamically.


Personally, your existing code is fine. It works. It is readable. While it might become more verbose as your app grows, it also might not. So why improve it?

That said, here are some ideas. Most of your code is a mapping between permission and policy implementation. For example 'part.view' maps to 'App\Policies\Parts\PartsPolicy@view. The "weight" of this mapping can't be removed: it can only be moved.

You might consider moving it to a simpler configuration file, something that looks like this:

// config/permission-map.php<?php return [    'post.view' => 'App\Policies\Blog\PostsPolicy@view',    'post.create' => 'App\Policies\Blog\PostsPolicy@create',    'post.update' => 'App\Policies\Blog\PostsPolicy@update',    'post.delete' => 'App\Policies\Blog\PostsPolicy@delete',    // etc...];

Then in your boot you read that config and iterate:

// boot permissions$permission_map = require_once('config/permission_map.php');foreach ($permission_map as $permission => $policy_implementation) {    Gate::define($permission, $policy_implementation);}

Advantage: adding a new policy mapping only changes the most salient information, and you don't have to think about how to make that mapping happen - today that is Gate::define but tomorrow maybe it's Sentry::policy. Additionally, by separating data from the code, you can test the code more freely.

Another approach could be annotations: in the DocBlock of your policy implementation, you write your own annotation syntax, which you then parse and compile into the configuration file. Along the lines of

namespace App\Policies\Blog;class PostsPolicy {    /**     * @permission post.view     */    public function view() { /* ... */ }}

I, personally, am not a fan of this: it adds a layer of inner framework whose value I find it hard to measure.