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
Create
permission
database tableCreate
roles
database tableCreate
permission_role
pivot database tableCreate
role_user
pivot database table
Define Relationships
Role has many permissions ( many to many relationship, define it with
belongsToMany
)Permission belongs to many roles ( many to many relationship, define it with
belongsToMany
)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.