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

the_lar's avatar

Best approach for many to many

Hi all,

In my app I have Clients and Users with a many to many relationship between them. I am using spatie for roles and permissions and I have an Administrator and Client Administrator roles created.

I have a route for /users where Administrator can view all Users but where Client Administrator should only be able to view Users related to the same Client that they are.

In theory a User with Client Administrator role 'could' be related to many Clients - however this can't really happen in practice because of the way Client Adminstrators are created in my app.

What would be the best way of me filtering the Users that are shown on the /users route for Client Administrators - is it OK for me to get the first related Client and then get all the Users related to that Client? Or is this bad practice given that the relationship is many to many? What should my approach be?

Many thanks Kevin

0 likes
16 replies
OussamaMater's avatar

Are the Administrator, Client Administrator and Users all the same table (users)?

If so, you can define a method in the User model to return the related clients of the current object, so you can do something like auth()->user()->getRelatedClients() , in the method check if the current user is an admin then return all the collection, otherwise return only the related, and you can make use of the query builder to translate a simply SQL query there if no relationships are defined (you can use recursive relationships in your design as the are all the same table).

If not, you can use the relationships you defined directly, that will give you access to only the related clients anyway.

1 like
the_lar's avatar

Hi @oussamamater - yes they all in the users table.

So currently on my UserController the index function is:

public function index()
    {
        $users = User::all();
        return view('users.index', compact('users'));
    }

So I'm not doing any auth() or filtering at all currently - could you give me an idea of the code I'd need to do what you're suggesting?

OussamaMater's avatar
Level 37

@the_lar Sure

Instead of that you want to do something like

public function index() {
      // now you know you will be returning all related clients based on the role of the authenticated user
       return view('users.index', ['users' => auth()->user()->getRelatedClients()]);
}

Now in your User model

public function getRelatedClients(){
    // this is an example you def perform the check based on the role and the package you are using
      if($this->is_admin) {
               return User::all();
      }
      // now you know he is the Client Administrator, so make use of the query builder, as I have no idea how the relations are defined, so get all the clients where their Client Administrator is the current one

    return ...
}
1 like
the_lar's avatar

@OussamaMater thank you! When I tried that I'm getting an error: Method Illuminate\Auth\RequestGuard::getRelatedClients does not exist. But when I change my index function to:

public function index()
    {
        return view('users.index', ['users' => auth()->user()->getRelatedClients()]);
    }

it seems to work - although I'm not really sure what auth() is doing here - sorry for basic q's!!

the_lar's avatar

@OussamaMater is auth()->user() basically getting the authenticated user in the same way that Auth::user() would?

OussamaMater's avatar

@the_lar Yes exactly, my bad I was so sleepy yesterday auth()->user() is exactly Auth::user() I just prefer using the helper so my code is a bit cleaner without more imports.

It's fine there are no basic questions, and am glad it's working for you, if that helped you maybe mark the thread as solved by setting a "best answer", for the future comers.

1 like
the_lar's avatar

@OussamaMater will do and thank you for your help, it always befuddles me a bit when there are a few ways of doing the same thing!

In your original answer you refer to the Query builder and recursive relationships - I'm stumped as to how I practically implement that in code within my getRelatedClients function... here is what I have so far...

public function getRelatedClients()
    {
        if($this->hasRole('Super Administrator') || $this->hasPermissionTo('manage users')){
            return User::all();
        }else{
            $clients = $this->clients()->get();

            /* stumped */
			/* in vanilla PHP I'd just loop over $clients and go $users[] = $client->users()-get() and just return the $users array - or something like that, but there's probably a more laravel way to do that here? */

            return 'Collection of Users who belong to same Clients that Authenticated User does';
        }
    }

So I'm getting clients because of the many to many relationship:

public function clients()
    {
        return $this->belongsToMany(Client::class);
    }

How do I use recursion as you describe to return a collection of Users linked to these Client(s)

Much appreciated. Kevin

OussamaMater's avatar

@the_lar No need for the recursive relationship, I thought that the clients shared the same table as the users as well.

Can you share more details about your tables and defined relationships? because till now I still don't know how its structed to fully assist you.

For now what I had in mind is you are only using the users table, apparently you are using a users table a clients table, where a user may have multiple clients right? need more context and details.

The Models involved with the currently defined relationships will help.

As for a small refactor for now you can get rid of the else statement so

public function getRelatedClients()
    {
        if( $this->hasRole('Super Administrator') || 
            $this->hasPermissionTo('manage users')){
            return User::all();
        }
        
        // code goes here
        }
    }

Note: in some scenarios we do use that vanilla php as you said, it does not have to be Laravel Way if what you are doing is a bit custom to you.

1 like
the_lar's avatar

@OussamaMater sure, it's really super simple, here's my User model:

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;
    use HasRoles;

    /**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'profile_photo_url',
    ];

    public function clients()
    {
        return $this->belongsToMany(Client::class);
    }

    public function getRelatedClients()
    {
        if($this->hasRole('Super Administrator') || $this->hasPermissionTo('manage users')){
            return User::all();
        }else{
            $clients = $this->clients()->get();

            /* stumped */

            return 'Collection of Users who belong to same Clients that Authenticated User does';
        }
    }
}

And my Client model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Client extends Model
{
    use HasFactory;
    protected $fillable = ['name'];

    public function users()
    {
        return $this->belongsToMany(User::class);
    }
}

Then I have a pivot table client_user defined using:

Schema::create('client_user', function (Blueprint $table) {
            $table->id();
            $table->foreignId('client_id')->constrained()->cascadeOnDelete();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
        });

Think thats it.

OussamaMater's avatar

@the_lar Well then what you wrote is totally correct the method should be

public function getRelatedClients()
    {
        if( $this->hasRole('Super Administrator') || 
            $this->hasPermissionTo('manage users')){
            return User::all();
        }
        // At this stage the current auth user will return all his related users, behind the scenes Laravel will perform a join, so it will a grab all the clients that have a user id of this auth one (in the pivot table ofc)
        return $this->clients;
        }
    }

And that's it I guess, please give this a go and keep me updated, I haven't tested the code, and as yesterday I am a bit sleepy, but that should do it.

1 like
the_lar's avatar

@OussamaMater ah no, that is going to return the Client (technically Clients even though there's only one) - what I need is the Users related to the Client(s) returned as a collection of Users, that's where I'm struggling to see the wood for the trees!

OussamaMater's avatar

@the_lar

I am sorry, I still don't quite understand what you're trying to do, bare with me

// this will a return a collection of only one client
$this->clients; 
// since it's only one client I appended the first method, and now we have one instance of that client
$client = $this->clients->first(); 
// now you want to return the users that are related to this client? well I am assuming you are doing some special insert in a way a user will have one single client, and that client will have multiple users (yes which makes sense actually I remembered some similar scenarios, it's like your user have a client and that client itself has some different users right?), you can simply do
$client->users;
// so the final code is
$related_clients = $this->clients->first()->users;

I hope this is what you are trying to do? because I promise I am doing my best to understand the goal haha

If it is what you are looking for the method will look like

public function getRelatedClients()
    {
        if( $this->hasRole('Super Administrator') || 
            $this->hasPermissionTo('manage users')){
            return User::all();
        }
           // the ? is a nullsafe operator in php 8, in case the auth user doesn't currently have any clients
           return $this->clients?->first()->users; 
        }
    }
1 like
the_lar's avatar

@OussamaMater its going to be something like that... the goal at the moment is just to list all the Users that the authenticated User with the Role of Client Administrator CAN manage. So basically that's all the Users that are related to all the Clients that the authenticated user is... probably sounds more complicated than it really is.

So $this->clients->first()->users; probably would work, but as a User 'could' be related to many Clients - lets say User Mark is a Client Administrator and is related to Client Facebook and Client Google - I would want Mark to be able to see a list of Users who are also related to BOTH Google and Facebook - and ultimately be able to manage them.

OussamaMater's avatar

@the_lar No it's just confusing to me as English is not my primary language.

Ah okay then, because you said it's "technically Clients even though there's only one", in that case you will have to loop through them so

// Inside the method
$all_related_users = [];
foreach($this->clients as $client){
      all_related_users[] = $client->users;
} 
// And later on you can either leave the array to be used for the view directly or maybe transform it into a collection of collections, both will do the job really

Note: just to be aware, this will lead to an n+1 issue, as in that loop each time we iterate we will make another sql query to fetch the users, so maybe consider using a package to eager load this belongsToMany if you do have multiple records, because for real, you will see a huge amount of queries and loading time will be a lot more.

Docs for eager loading, I am sure it can be done:

But for now, that code works perfectly fine, unless you have a huge database, consider refactoring to eager loading

1 like
the_lar's avatar

@OussamaMater yeah OK, so I've ended up going with

$related_users = [];
            foreach($this->clients as $client){
                $users = $client->users;
                foreach($users as $user){
                    $related_users[] = $user;
                }
            }
            return $related_users;

which works fine returning an array of Users to the view. I just didn't know if there was a more 'laravel' way of doing it really, as you can probably tell I'm pretty new to it!

Anyway... this will work, thanks again for your help!

OussamaMater's avatar

@the_lar well both code are similar, I just appended the whole collection without going a one level deeper.

And nope sometimes you just have to do it manually.

Just consider:

  • The n+1 issue as I stated above.
  • The possibility of null collections, so make the checks always.

And I am happy to help! and maybe close the thread by setting a "best answer" for future comers.

1 like

Please or to participate in this conversation.