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

Jawsh's avatar

Has-many-through-many?

What is the simplest way to achieve this relationship query in Eloquent?

I have many boards, which each may have many roles, which each may have many assignee staff members.

I could normally compile the array this way.

$staff = [];
    
foreach ($board->roles as $role)
{
    foreach ($role->users as $user)
    {
        $staff[] = $user;
    }
}
    
return $staff;

I can't just do a "has many through" because there's actually 4 tables here. Between Role and User there is a UserRole intermediary table that handles a many-to-many relationship between a User and his Roles.

0 likes
15 replies
pmall's avatar

Actually you are doing this the right way. There is no has many through relationship with many to many.

The only workaround is to have a board_user table.

kfirba's avatar

@Jawsh You can actually do that using Eloquent only and it will work for any deep nested relation:

$board->load(['roles.users' => function($query) use (&$staff) {
    $staff = $query->get()->unique();
}]);

dd($staff);

The reason we are using &$staff here (by reference) is so we can use this variable outside the anonymous function.

You can use this with the with() method as-well and do that for any deep nested relationship.

Disclaimer: I did not "invent" that trick. I read it few months ago somewhere. I can't recall where, unfortunately, if someone knows where I read that, please provide the link :)

2 likes
kfirba's avatar

@pmall Well there is another option but having a board_user table, take a look at my answer above. Also, if he wants to use his solution, he has to make sure he's eager loading the relations and removing duplicates. As his code stands now, he's gonna query the DB TONS of times and he will have duplicates.

He can maybe do something like:

$staff = [];
    
$board->load('roles.users');

foreach ($board->roles as $role)
{
    foreach ($role->users as $user)
    {
        $staff[] = $user;
    }
}

$staff = array_unique($staff, SORT_REGULAR);    

return $staff;

I would still suggest my answer above.

kfirba's avatar

@JarekTkaczyk Thanks for the link :) Now I have the resource.

How does the list solution work? Are you listing a relationship? What is the result of listing a relationship? Never knew it was possible. I thought it was only possible for a field.

JarekTkaczyk's avatar

@kfirba It works like with any other property/attribute (just mind that the relation must be eager loaded):

// given role has name attribute, you can access these just the same
$role->name // single value
$role->users // collection

// then
$board->roles->lists('name')
// collection {
//   'name_1',
//   'name_2',
//   ...
// }

$board->roles->lists('users')
// collection {
//   collection_1 { ... }
//   collection_2 { ... }
//   ...
// }

So:

$board->roles->lists('users') // collection of collections
    ->collapse() // flattened collection of users
    ->unique()
1 like
kfirba's avatar

@JarekTkaczyk there is one thing I want to ask, why won't we simply do something like:

$board->roles->users

Instead of

$board->roles->lists('users')

Won't they both return a collection with the users?

JarekTkaczyk's avatar

@kfirba Obviously there's no users available on the roles collection, but rather on each of its elements.

SebastianSchöps's avatar

I extended the Eloquent collection and added this method:

class CustomCollection extends Collection
{
    public function __get($relationOrProperty) {
        if(method_exists($this->first(), $relationOrProperty))
        {
            $this->load($relationOrProperty);
            return $this->pluck($relationOrProperty)->collapse()->unique();
        }
        elseif($this->first()->$relationOrProperty)
        {
            return $this->pluck($relationOrProperty)->unique();
        }

        throw new Exception('Method or property "' . $relationOrProperty . '" does not exist.');
    }
}

Now I can use $board->roles->users

Does this make sense? Any dangers because of using the magic method? Maybe something similar is already built in in the meantime?

If there is a problem with it, we could still use a separate method like $board->roles->collect('users')

sandervanhooft's avatar

Hi @SebastianSchöps ,

Any new insights on the polymorphic hasManyThrough?

I am implementing this relationship right now. But whatever method I try, it just doesn't feel right.

staudenmeir's avatar

I created a HasManyThrough relationship with support for BelongsToMany: Repository on GitHub

After the installation, you can use it like this:

class Board extends Model {
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function staff() {
        return $this->hasManyDeep(User::class, [Role::class, 'role_user']);
    }
}
1 like
NicksonYap's avatar

Thanks @ staudenmeir

It works!!!!

It is a Laravel 5.5 composer package that can perform multi-level relationships (deep)

Package: https://github.com/staudenmeir/eloquent-has-many-deep

Example:

User --> belongs to many --> Role --> belongs to many --> Permission


    class User extends Model
    {
        use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
    
        public function permissions()
        {
            return $this->hasManyDeep(
                'App\Permission',
                ['role_user', 'App\Role', 'permission_role'], // Pivot tables/models starting from the Parent, which is the User
            );
        }
    }

Example if foreign keys need to be defined:

https://github.com/staudenmeir/eloquent-has-many-deep/issues/7#issuecomment-431477943

1 like

Please or to participate in this conversation.