Laravel Many-to-many Pivot Table: Add Extra Column with Relation

Sometimes you need to add more fields to the many-to-many pivot table, and those fields may also have their own relationships.

Example: you have models User, Project, and Role.

Users may be assigned to different Projects, with a different Role in each project.

The pivot table structure would look like this:

project_user

  • project_id (foreign key to projects)
  • user_id (foreign key to users)
  • role_id (foreign key to roles)

If that third field is a regular field, without a relationship, you define this in the Eloquent model:

app/Models/Project.php:

class Project extends Model {
 
public function users() {
return $this->belongsToMany(User::class)->withPivot('role_id');
}
 
}

But since we have a relationship here, we can define a special Pivot model and add a relationship inside of it.

php artisan make:model ProjectUser --pivot

Then, in that Pivot model, you define the relationship to the Role.

app/Models/ProjectUser.php:

use Illuminate\Database\Eloquent\Relations\Pivot;
 
class ProjectUser extends Pivot
{
public function role()
{
return $this->belongsTo(Role::class, 'role_id');
}
}

Then, you provide that Pivot model as a part of your many-to-many relationship, with using():

app/Models/Project.php:

use App\Models\ProjectUser;
 
class Project extends Model {
 
public function users() {
return $this->belongsToMany(User::class)
->withPivot('role_id')
->using(ProjectUser::class);
}
 
}

And then, finally, you can run this query:

$projects = App\Models\Project::with('users')->get();
foreach ($projects as $project) {
echo $project->name. ': ';
foreach ($project->users as $user) {
echo $user->name . ' (' . $user->pivot->role->name . ') ';
}
}

As you can see, we can access the role of each user by calling $user->pivot->role->name.

Notice: keep in mind that this code will unfortunately produce the N+1 query problem. During my testing, I haven't found a way to eager load the users.pivot.role with this or any other syntax. If you have achieved this, please write in the comment, and I will update the article with your solution.

avatar

Hi Povilas, I would rename the many-to-many table as engagements.

Then, I create a Model Engagement. This model should have 3 relationsships belongsToMany for the Project, User, and Role. I would specify the engagements as the Pivot table.

Then, I query the Engagement model, join on Products, Users, and Roles, and select the columns I want.

class Engagement extends Model
{
    public function projects()
    {
        return $this->belongsToMany(Project::class, 'engagements');
    }
    public function users()
    {
        return $this->belongsToMany(User::class, 'engagements');
    }
    public function roles()
    {
        return $this->belongsToMany(Role::class, 'engagements');
    }
}

$engagements = Engagement::join('projects','engagements.project_id','projects.id')
    ->join('users','engagements.user_id','users.id')
    ->join('roles','engagements.role_id','roles.id')
    ->select('engagements.id','projects.title', 'users.name', 'roles.name')
    ->get();

That's what i would use for 3 tables having a many-to-many relationship. I am not 100% sure that's what you are looking for, but thought of sharing it.

👍 1
avatar

Wow, thanks for sharing! Not sure I would do it exactly this way, but you have a good logic behind it.

avatar

Have a look at medium article and this repo to solve N+1

👍 1

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1056 lessons, total 42 h 44 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials