Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Ligonsker's avatar

Refactoring code to use Gates instead of role based check

Edit: Can not use Spatie's package right now

I know it's probably my third post relating this topic, but this time I've decided to quite redesign everything.

Right now, the way the app checks if a user can access a route is via a Role Middleware, like in the docs: https://laravel.com/docs/9.x/middleware#middleware-parameters

example usage:

Route::middleware(['role:manager,user'])->group(function () {
    Route::get('/route_x'
    Route::post('/route_x'
});

But, it makes things harder when new roles are added, because I will have to add it in many scattered places.

I decided to try to use Gates. What I want to do is to instead of authorizing based on roles, I will authorize based on the route using Gates and the can Middleware:

Route::middleware(['can:access_route_x'])->group(function () {
    Route::get('/route_x'
    Route::post('/route_x'
});

I will need to define the Gate:

Gate::define('access_route_x', function (User $user) {
    // if the user has a role that allows to view route x then return true
});

However I'm having trouble figuring how to make use of the current DB table that I already have: It's a table containing relationship between users and the role name (which is also the permission name):

user_id  | role_name
---------|------------
   11    |    manager
   65    |    user
   11    |    role_x
   65    |    role_y
   65    |    role_y
   43    |    role_z

Should I separate this table into two: relationship between role and permission, and another one for relationship between user_id and the role?

Then, if a new permission is added, I will add it to the permission table for all the roles that can use it.

Also, how do I actually write it in the gate definition? Is it something like that:

Gate::define('access_route_x', function (User $user) {
   return $user->roles()->permissions()->where('name', '=', 'access_route_x')->isNotEmpty();
});

Or I should do something else?

0 likes
3 replies
rodrigo.pedra's avatar

Should I separate this table into two: relationship between role and permission, and another one for relationship between user_id and the role?

Then, if a new permission is added, I will add it to the permission table for all the roles that can use it.

If you need both roles and permissions, this is the way to go. Consider caching a user's permission to make your system faster.

Mind that usually, in most system related jargon, a role is somewhat related to what "job title" a user has, such as a manager, analyst, admin, etc.

And permissions are usually related to an action a system can perform: create a user, delete an item, etc.

So in most RBAC (Role-Based Access Control) systems, there is usually this two pivots: a role_user, and a permission_role. Some such systems also allow a user_permission so you can grant any additional permission to a particular user without needing to create a whole new role just for them.

I used to use this RBAC approach on most of my projects. Nowadays I prefer to have a finite set of roles, that is not user modifiable, and make use of model policies to define fine-grained permissions. It is a lot less-flexible than allowing dynamic role creation and permission assignment.

At the end of the day I realized most clients usually settle down on a well-defined set of roles and permissions, so you can make small changes to your code until this set matures with your client, and after that both maintenance and support are much easier as you won't need to assess every particular role/permission combination to understand an issue your client might be having.

But of course, sometimes a flexible RBAC is a project's requirement. Choose at your project's own needs.

Good luck =)

1 like
Ligonsker's avatar

@rodrigo.pedra Thank you! In my case I do need more flexibility because accessing routes is a very flexible thing here. There are many managers, sub-managers, groups of users. For example division manager, then a few departments managers, then each department has few groups of employee types.

So I think I will separate it into role_user and permission_role as you said.

by caching permission, you mean that when a user logs in then I fetch all his roles (and subsequent permissions), then it will be in the cache so that I won't need to do more DB queries?

rodrigo.pedra's avatar

@Ligonsker

by caching permission, you mean that when a user logs in then I fetch all his roles (and subsequent permissions), then it will be in the cache so that I won't need to do more DB queries?

Yes. You can listen for the Role and Permission models to clear the cache only when those, or the relation between them changes.

And of course, when a user role/s changes you invalidate the cache too.

Please or to participate in this conversation.