For this example; I’m going to introduce Blake, our teacher. Blake works across two schools but his responsibilities differ between them. As you would expect, we need to create permissions for Blake but we need to group them together to prevent permission leaks between the schools.

Now; I’m not going to spoon feed all this, as you should be familiar with model relationships. We need to create the models and relationships for:

Like so.

class User extends Model
{
    // id, name etc...
    public function schools()
    {
        return $this->belongsToMany(School::class);
    }
}

class School extends Model
{
    // id, title
}

class Group extends Model
{
    // id, title
    public function permissions()
    {
        return $this->belongsToMany(Permission::class);
    }
}

class Permission extends Model
{
    // id, key, note
}

Great! This is what we need to get started.

Custom Pivot

Now we need to add a relationship to Groups; but where do we add it?

We could create a relationship between Users and Groups, but then we don’t know which School the permissions then belongs to. So how about we add the Groups to the Schools model? But again, now we don’t know what User the permissions belong to either.

The solution would be to create a custom Pivot between Users and Schools. Then! from the Pivot, we can define a further relationship specific to both User and School, for the Group (singular).  Essentially, we are creating a three-way relationships from a single row on our pivot table.

Start by adding a column to the school_user pivot table for group_id (int). Next, create a new class for the pivot, something like:

namespace App\Pivots;

use Illuminate\Database\Eloquent\Relations\Pivot;

class SchoolUser extends Pivot
{
    public function group()
    {
        return $this->belongsTo(\App\Group::class);
    }
}

Update the schools() method on the User model to use our new Pivot model, instead of the default.

class User extends Model
{
    public function schools()
    {
        return $this->belongsToMany(School::class)->using(\App\Pivots\SchoolUser::class)->withPivot("group_id");
    }
}

Note: we need to add the withPivot method to expose the group_id to our Pivot model.

Awesome! This now gives us access to the Group for the relationship between user and school. So; User: Blake, belongsTo School: Norwich City College, with Group: Teaching Assistant, that hasMany Permissions.

You can access this Pivot relationship using something like:

$user = User:find(1);
dd( $user->schools->first()->pivot->group );

Hard part done!

Lastly, we need to check that a user, belonging to a school, has permission to perform an action. Where you check for this is up to you; you have Policies, Controllers, Middleware, FormRequest, Model events (at a stretch). But I’ll show you have to quickly and cleaning verify a permission.

Let’s go back to the Pivot model SchoolUser, and add this below the group() method.

public function hasPermission($key)
{
    if ($this->group) {
        foreach($this->group->permissions as $perm) {
            if ($perm->key == $key) {
                return true;
            }
        }
    }
    return false;
}

This will look through the permissions for the group, find a match for a given permission key and return Boolean.

Now within your controller, for example, you can use:

public function index(Request $request, School $school) {
    // Check the relationship integrity
    $school = $request->user()->schools()->where("id",$school->id)->firstOrFail();

    if ($school->pivot->hasPermission("update_register")) {
        return true;
    }
}

Voila! There you have it

A few refinements

Change the model pivot property

If you feel using $school->pivot is too generic. You can change the property to something for memorable.  The as() method on the belongsToMany class lets you define a custom pivot name which is used to access the relationship.

public function schools()
{
    return $this->belongsToMany(School::class)->using(\App\Pivots\SchoolUser::class)->as("schoolUser")->withPivot("group_id");
}

Now becomes:

$user = User:find(1);
dd( $user->schools->first()->schoolUser->group );