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

Install tailwindcss in Laravel 11

Install tailwindcss in Laravel 11

Install tailwindcss in Laravel 11

2 months ago Read article →
Laravel 11: How to Download Files from External URLs and Servers

Laravel 11: How to Download Files from External URLs and Servers

Learn how to download files from external URLs and servers in Laravel 11. This easy guide walks you through the steps to set up file downloads in your Laravel application, making it simple to fetch files from anywhere.

2 months ago Read article →
How to Easily Add ‘Add to Calendar’ Functionality in Laravel with Spatie Calendar-Links

How to Easily Add ‘Add to Calendar’ Functionality in Laravel with Spatie Calendar-Links

Learn how to add an Add to Calendar feature in your Laravel app using Spatie's Calendar-Links package. This guide covers step-by-step installation, generating Google Calendar, iCal, and Outlook links, and including event details like organizer, attendees, and reminders for a complete user experience.

2 months ago Read article →
How to Use Lateral Joins in Laravel for Fetching Limited Records

How to Use Lateral Joins in Laravel for Fetching Limited Records

How to Use Lateral Joins in Laravel for Fetching Limited Records

2 months ago Read article →