
Many-to-Many relationship in Laravel - All you need to know
In this article, you will learn about Laravel's many-to-many relationship, starting from the basics and gradually progressing to more advanced concepts. We'll begin with a simple example involving users and projects, where we will establish a relationship between users and projects using a pivot table. After setting up the relationship, we will demonstrate how to store and fetch records from the database efficiently. By the end, you will have a deep understanding of how to implement many-to-many relationships in Laravel with practical use cases.
Setting Up the Database
Our tables for projects, users and a pivot table name project_user.
// users table
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
// projects table
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
// our pivot 'project_user' table
Schema::create('project_user', function (Blueprint $table) {
$table->foreignId('project_id')->constrained();
$table->foreignId('user_id')->constrained();
});
Relationship Setup
here we will define many-to-many relationship using the belongsToMany() method.
class Project extends Model
{
use HasFactory;
protected $fillable = ['name'];
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}
Creating Sample Data
We will generate sample data by using factories and seeder.
// ProjectFactory
public function definition(): array
{
return [
'name' => $this->faker->name(),
];
}
// ProjectSeeder
$projects = \App\Models\Project::factory(10)->create();
foreach ($projects as $project) {
$users = User::inRandomOrder()->take(rand(1, 3))->pluck('id');
foreach ($users as $user) {
$project->users()->attach($user);
}
}
Now, migrate and seed the database:
php artisan migrate:fresh --seed
Utilizing the Relationship
Now we will use our relationship to fetch the projects with users. users is our relationship which is present in Project model.
public function index()
{
$projects = Project::with('users')->get();
$data = ['projects' => $projects];
return view('welcome', $data);
}
Now lets goto welcome.blade.php and paste this code.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel Many-to-Many Relationship</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body>
<div class="container mx-auto">
<div class="container d-flex align-items-center justify-content-center">
<table class="table table-striped w-75">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Projects</th>
<th scope="col">Users</th>
</tr>
</thead>
<tbody>
@foreach ($projects as $project)
<tr>
<th>{{ $loop->index + 1 }}</th>
<th>{{ $project->name }}</th>
<td>
<ul>
@foreach ($project->users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js"
integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous">
</script>
</body>
</html>
1 - Adding timestamps column in Pivot Table
Now let's add time timestamps column in pivot table.
Schema::create('project_user', function (Blueprint $table) {
$table->foreignId('project_id')->constrained();
$table->foreignId('user_id')->constrained();
$table->timestamps();
});
Then in the model we need to attach withTimestamps() in the model function.
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'project_user', 'project_id', 'user_id')
->withTimestamps();
}
And in the welcome.blade.php , you need to show created_at column.
@foreach ($projects as $project)
<tr>
<th>{{ $loop->index + 1 }}</th>
<th>{{ $project->name }}</th>
<td>
<ul>
@foreach ($project->users as $user)
<li>{{ $user->name }}({{ $user->pivot->created_at }})</li>
@endforeach
</ul>
</td>
</tr>
@endforeach
If you want to change pivot name from the loop, then you need to attach 1 more function in Project model.
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'projects_users', 'projects_id', 'users_id')
->withTimestamps()
->as('project_user');
}
Now your welcome.blade.php will look like this
@foreach ($projects as $project)
<tr>
<th>{{ $loop->index + 1 }}</th>
<th>{{ $project->name }}</th>
<td>
<ul>
@foreach ($project->users as $user)
<li>{{ $user->name }} ({{ $user->project_user->is_manager }})</li>
@endforeach
</ul>
</td>
</tr>
@endforeach
The output will be the same.
2 - Adding is_manager column in Pivot Table
Let's modify project_user table . Goto Migrations, add 1 more column in the project_user table.
Schema::create('project_user', function (Blueprint $table) {
$table->foreignId('project_id')->constrained();
$table->foreignId('user_id')->constrained();
$table->boolean('is_manager')->default(false);
$table->timestamps();
});
Goto Seeder attaches manager to user in random order.
$projects = \App\Models\Project::factory(10)->create();
foreach ($projects as $project) {
$users = User::inRandomOrder()->take(rand(1, 3))->pluck('id');
foreach ($users as $user) {
$project->users()->attach($user,['is_manager'=>rand(0,1)]);
}
}
Goto your Project model, create a new relationship function named managers.
public function managers(): BelongsToMany
{
return $this->belongsToMany(User::class, 'projects_user', 'project_id', 'user_id')
->withTimestamps()
->withPivot(['is_manager'])
->as('project_user');
}
Now go to your controller and call managers function.
public function index()
{
$projects = Project::with('managers')->get();
$data = ['projects' => $projects];
return view('welcome', $data);
}
Now go to welcome.blade.php
@foreach ($projects as $project)
<tr>
<th>{{ $loop->index + 1 }}</th>
<th>{{ $project->name }}</th>
<td>
<ul>
@foreach ($project->managers as $manager)
<li>{{ $manager->name }} ({{ $manager->project_user->is_manager }})</li>
@endforeach
</ul>
</td>
</tr>
@endforeach
3 - Adding Relationship(manager_id) with Pivot Table
php artisan make:model ProjectUser
class ProjectUser extends Pivot
{
use HasFactory;
public function manager(): BelongsTo
{
return $this->belongsTo(User::class, 'manager_id');
}
}
In this model , we have extended this class from model to pivot and added a new relationship with managers.
Now goto to Database/Migration/project_user.php
Schema::create('project_user', function (Blueprint $table) {
$table->foreignId('project_id')->constrained();
$table->foreignId('user_id')->constrained();
$table->boolean('is_manager')->default(false);
$table->timestamps();
});
After that go to your Project.php Model and attach a new function in users relationship.
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'project_users', 'project_id', 'user_id')
->withTimestamps()
->withPivot('manager_id')
->using(ProjectUser::class);
}
After that, goto to your ProjectSeeder and attach manager_id in random order.
$projects = \App\Models\Project::factory(10)->create();
foreach ($projects as $project) {
$users = User::inRandomOrder()->take(rand(1, 3))->pluck('id');
foreach ($users as $user) {
$project->users()->attach($user, ['manager_id' => User::inRandomOrder()->first()->id]);
}
}
Now goto your HomeController
public function index()
{
$projects = Project::with('users')->get();
$data = ['projects' => $projects];
return view('welcome', $data);
}
and in the welcome.blade.php
@foreach ($projects as $project)
<tr>
<th>{{ $loop->index + 1 }}</th>
<th>{{ $project->name }}</th>
<td>
<ul>
@foreach ($project->users as $user)
<li>{{ $user->name }}({{ $user->pivot->manager->name }})</li>
@endforeach
</ul>
</td>
</tr>
@endforeach