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

Differences Between Cron Jobs and Laravel Scheduler

Differences Between Cron Jobs and Laravel Scheduler

Differences Between Cron Jobs and Laravel Scheduler

1 month ago Read article →
CRUD with Alpine.js, Tailwind CSS, and jQuery DataTable Modals

CRUD with Alpine.js, Tailwind CSS, and jQuery DataTable Modals

Learn how to build a full CRUD system using Alpine.js, Tailwind CSS, and jQuery DataTable, complete with modals for better user experience.

1 month ago Read article →
How to Create XML from an Array Using Spatie(array-to-xml) in Laravel

How to Create XML from an Array Using Spatie(array-to-xml) in Laravel

Learn how to convert an array into XML format using the Spatie array-to-xml package in Laravel. This easy guide walks you through the steps to set up the package and create XML files from your data quickly.

1 month ago Read article →
How to Accept Credit Card Payments in Laravel 12 Using Square

How to Accept Credit Card Payments in Laravel 12 Using Square

Learn how to easily add Square credit card payments to your Laravel 12 website. Step-by-step guide for secure and smooth payment integration.

2 weeks ago Read article →