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

elb98rm's avatar

Best practice on permissions

Hi,

As with "expert subjects", often what you need is for someone to simply tell you the word/approach to look up and that'll help you.. as google is too blunt a tool to find the answer. I have one of those cases!

I was wondering what the best practice for using permissions with roles is in terms of lookups.

Background:

  • L11
  • Spatie permissions

Question overview/example

For an admin user trying to find all users (User), the following lookup is fine:

$users = User::all()

Let's say that there organisations (Organisation) that have many users, but not all... So an organisation admin might only be allowed to see Users from a certain Organisation. You might do:

$users = user()->auth()->organisations->users;
// or some similar direct lookup
```php

Together this is:
```php
if($user->hasRole('admin')) {
    $users = User::all()
} else {
    $users = user()->auth()->organisations->users;
}
// ... etc, obviously this moves to a switch() or similar with more options

... this quickly expands into a lot of code in a controller method that needs to be duplicated. The obvious answer is to put this in a method or class.

public function loadUsers()
if($user->hasRole('admin)) {
    $users = User::all()
} else {
    $users = user()->auth()->organisations->users;
}
return $users
}
// ... etc, obviously this moves to a switch() or similar with more options

So, this could go in a bunch of places with varying degrees of merit:

  • in the UsersController
  • in the User model
  • in some helper file...

So what's the best practice for this? A helper file seems logical, but what is the name/type of such a file so I can google it for best practice advice? Is there a better way of doing this etc.

Thanks in advance.

0 likes
3 replies
LaryAI's avatar
LaryAI
AI
Best Answer
Level 58

The best practice for handling permissions and roles in a Laravel application, especially when using the Spatie permissions package, is to encapsulate the logic for retrieving users based on roles within a dedicated service class. This approach adheres to the Single Responsibility Principle, keeping your controllers lean and your models focused on representing the data.

Here's how you can structure your solution:

  1. Create a UserService class that contains the logic for loading users based on roles.
  2. Inject this service into your controller.
  3. Use the service method to retrieve the users.

Here's an example of what the UserService class might look like:

namespace App\Services;

use App\Models\User;
use Spatie\Permission\Models\Role;

class UserService
{
    public function loadUsers(User $user)
    {
        if ($user->hasRole('admin')) {
            return User::all();
        } else {
            // Assuming there's a relationship set up in the User model for organizations
            return $user->organizations()->with('users')->get()->pluck('users')->flatten();
        }
    }
}

And then in your UsersController, you would inject this service and use it:

namespace App\Http\Controllers;

use App\Services\UserService;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    protected $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function index(Request $request)
    {
        $users = $this->userService->loadUsers($request->user());
        return view('users.index', compact('users'));
    }
}

This way, you keep your controller actions clean and focused on handling the request/response cycle, while the UserService handles the business logic of determining which users to load.

Regarding the naming and type of such a file, "service" is a common term used in Laravel and other frameworks to describe a class that encapsulates business logic. You can look up "service class in Laravel" or "Laravel service layer" for best practices and more detailed examples.

Remember to register your service class in a service provider if you need to resolve it out of the Laravel container, which allows for easier testing and swapping implementations if needed.

elb98rm's avatar

Thanks Lary - That's the thing I was looking for! :)

Robstar's avatar

You could also looks at Eloquent scope, which will automatically apply restrictions to all queries, without a user check. There's no need to worry where the database originates from and so is secure i.e. no none admin users see records they shouldn't see.

Here's an example from a single project. I have a list of tasks. Tasks are associated with clients. A client can contain or many users.

The below basically states:

  • if the user has the admin role, display all tasks
  • otherwise, determine the client to which the user is associated with and automatically scope tasks to that client
<?php

declare(strict_types=1);

namespace App\Scopes;

use Illuminate\Database\Eloquent\{Builder, Model, Scope};
use Illuminate\Support\Facades\Auth;

class ClientScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        /** @var \App\User $user */
        $user = Auth::user();

        if (null === $user || $user->isAdmin()) {
            return;
        }

        $builder->whereIn(column: 'client_id', values: $user->client_ids);
    }
}

Then in the Eloquent model I wish to scope:

public static function boot(): void
    {
        parent::boot();
        static::addGlobalScope(scope: new ClientScope());
    }

Scope are a seriously under appreciated feature of Eloquent.

Please or to participate in this conversation.