Your Guide to Laravel Excellence

Many-to-Many relationship in Laravel - All you need to know

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

Recommeded Posts

Using spatie/browsershot in Laravel for Screenshot and PDF Generation

Using spatie/browsershot in Laravel for Screenshot and PDF Generation

Learn how to use the Spatie Browsershot package in Laravel to capture screenshots and generate PDFs easily. This guide walks you through the setup process and shows you how to create high-quality images and documents from your web pages.

3 months ago Read article →
Laravel Tailwind DataTable with Vanilla JS (No jQuery)

Laravel Tailwind DataTable with Vanilla JS (No jQuery)

Build fast, responsive Laravel DataTables with Tailwind CSS v4 and vanillajs-datatable. No jQuery, no frameworks. Just pure JS + Blade.

2 weeks ago Read article →
Learn Laravel Scopes and simplify Eloquent Queries

Learn Laravel Scopes and simplify Eloquent Queries

Learn Laravel Scopes and simplify Eloquent Queries

3 months ago Read article →
Get Started with Bootstrap 5 in Laravel 11: A Step-by-Step Guide

Get Started with Bootstrap 5 in Laravel 11: A Step-by-Step Guide

Get Started with Bootstrap 5 in Laravel 11: A Step-by-Step Guide

3 months ago Read article →