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

How to Implement Custom Facebook OAuth Login in Laravel  Without Socialite

How to Implement Custom Facebook OAuth Login in Laravel Without Socialite

How to Implement Custom Facebook OAuth Login in Laravel Without Socialite

4 months ago Read article →
Real-Time Chat App with Laravel Websockets

Real-Time Chat App with Laravel Websockets

Learn how to build a real-time messaging system in this comprehensive tutorial. Explore sending messages via WebSockets, broadcasting events, receiving messages with Pusher, displaying user status (online/offline), and showcasing old message histories smoothly

4 months ago Read article →
Update Password in Laravel 11 using Bootstrap Modal and Ajax

Update Password in Laravel 11 using Bootstrap Modal and Ajax

Update Password in Laravel 11 using Bootstrap Modal and Ajax

4 months ago Read article →
How to Prevent Spam in Laravel Forms with spatie/laravel-honeypot

How to Prevent Spam in Laravel Forms with spatie/laravel-honeypot

How to Prevent Spam in Laravel Forms with spatie/laravel-honeypot

4 months ago Read article →