Using many-to-many relationships in Eloquent (with examples)

Updated: January 17, 2024 By: Guest Contributor Post a comment

Introduction

When working with databases in a Laravel application, the Eloquent ORM provides an expressive and convenient way to interact with database relationships. Many-to-many relationships are one of the complex types you’ll encounter. This tutorial will guide you through understanding and implementing many-to-many relationships in Eloquent, providing examples for better clarity. You’ll learn how to define the relationships, how to work with pivot tables, and how to perform common operations such as creating, reading, updating, and deleting related records.

Let’s start by defining what a many-to-many relationship is. Imagine two database entities, Users and Roles. A user can have multiple roles, and each role can be assigned to multiple users. This scenario can’t be represented by a simple one-to-one or one-to-many relationship; instead, it requires a many-to-many relationship.

Defining Many-to-Many Relationships

To define many-to-many relationships in Eloquent, we use the belongsToMany method. This method will define the relationship in both of the Eloquent models that represent the connected entities.

// In User model
public function roles()
{
    return $this->belongsToMany(Role::class);
}

// In Role model
public function users()
{
    return $this->belongsToMany(User::class);
}

By default, Eloquent will presume that the pivot table is named by concatenating the two related model names in alphabetical order. However, you can specify a custom table name as the second argument to the belongsToMany method.

Pivot Tables

The intermediary table in a many-to-many relationship is called a pivot table. Eloquent provides a fluent way to work with pivot tables. Let’s see how we would create our pivot table for Users and Roles.

Schema::create('role_user', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('user_id');
    $table->unsignedBigInteger('role_id');
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
    $table->timestamps();
});

Note that we follow the convention of naming the table alphabetically (‘role_user’ instead of ‘user_role’); however, this can be changed if you discern a different naming strategy.

Retrieving Related Models

Let’s discover how to fetch related models. After setting up the many-to-many relationship, you can retrieve related records easily.

$user = User::find(1);
foreach ($user->roles as $role) {
    echo $role->name;
}

The previous code would output a list of role names associated with the user whose ID is 1. The reverse is also possible:

$role = Role::find(1);
foreach ($role->users as $user) {
    echo $user->name;
}

This code would output a list of user names associated with the role whose ID is 1. With Eloquent, it is also simple to eager load relationships, improving performance by reducing the number of queries executed:

$users = User::with('roles')->get();

Adding Related Models

Attaching a role to a user can be accomplished using the attach() method:

$user = User::find(1);
$user->roles()->attach($roleId);

If you want to introduce additional attributes to the pivot table during the attach operation, you may pass an array as the second argument:

$user->roles()->attach($roleId, ['expires' => $expires]);

Updating Pivot Table Data

In case you need to update pivot table data, you can use the sync() method:

$user->roles()->sync([$roleId => ['expires' => $newExpires]]);

You can also use the syncWithoutDetaching() method to add relationships without removing any existing ones:

$user->roles()->syncWithoutDetaching([$roleId]);

Removing Related Models

To remove a relationship, use the detach() method:

$user->roles()->detach($roleId);

If you need to delete all related models, you can also call detach() without passing any arguments:

$user->roles()->detach();

Working with Pivot Data

Occasionally, you may want to work with additional data from the pivot table directly. Eloquent allows for easy interaction with pivot data:

foreach ($user->roles as $role) {
    echo $role->pivot->expires;
}

Advanced Examples

Now let’s dive into more advanced usages of many-to-many relationships in Eloquent.

Custom Pivot Models

Eloquent enables you to define custom pivot models if you need to add behavior to your pivot records. To do so, define a model extending Illuminate\Database\Eloquent\Relations\Pivot and assign it to your relationship:

class UserRolePivot extends Pivot {
    protected $casts = ['expires' => 'datetime'];
}

public function roles()
{
    return $this->belongsToMany(Role::class)->using(UserRolePivot::class);
}

Filtering Relationships

You can filter relationships directly by using the wherePivot method to define criteria:

$expiredRoles = $user->roles()->wherePivot('expires', '<', now())->get();

Ordering Pivot Data

If your pivot table contains sortable information, you can order your relationship results using the orderByPivot method:

$orderedRoles = $user->roles()->orderByPivot('created_at', 'desc')->get();

Conclusion

Throughout this tutorial, you’ve learned the intricacies of configuring and manipulating many-to-many relationships in Laravel’s Eloquent ORM. With this knowledge, you’re equipped to design complex data relationships with confidence in your Laravel applications.