Laravel, with its elegant syntax and robust features, is a go-to choice for web application development. Laravel has earned its reputation as a versatile and developer-friendly PHP framework. However, as your Laravel application evolves and grows, efficiently managing Many-to-Many Relationships becomes paramount to maintaining optimal performance. This step-by-step guide will explore how to scale Laravel applications while optimizing many-to-many relationships. You can achieve this using a function in Laravel that is called belongsToMany. The belongsToMany relation takes the argument as a second model.
Before diving deep into the Many-to-Many relationships in Laravel 10, let’s build a solid understanding of what many-to-many relationships are. Also, why they are essential in web application development.
The Power of Many-to-Many Relationships
Many-to-many relationships are a fundamental concept in relational databases. It allows you to connect multiple records in one table to multiple records in another table. In Laravel, Eloquent makes working with Many-to-Many Relationships a breeze. Whether you’re building a User and Role-based application or an e-commerce platform with products and categories, Many-to-Many Relationships are likely to play a crucial role.
However, as your application scales, the sheer volume of data and the complexity of queries can become overwhelming. It’s essential to optimize your Many-to-Many Relationships to maintain excellent application performance.
Recommended: Advanced Eloquent Relation with hasManyThrough in Laravel 10
Key Characteristics of Many-to-Many Relationships
- Multiple-to-Multiple: As the name suggests this relationship involves multiple instances on both sides of the relationship.
- Pivot Table: To represent a many-to-many relationship in a relational database, a pivot table is used. This table serves as an intermediary between the two related entities. It stores foreign keys from both tables.
- Flexibility: This relationship offers flexibility in data modeling. Also, it allows you to connect and associate various entities without creating complex, redundant structures.
So, now, let’s see, what we are going to build in this example of the post.
The above result contains the users list with their respective assigned roles. Now, let’s come to the post for implementing it.
Recommended: Efficient Data Retrieval With hasOneThrough Relation in Laravel
Prerequisites
If you want to proceed with the implementation of Many-to-Many relationships in Laravel 10 then you would require the below configurations.
- PHP >=8.1
- Composer
- Apache/Nginx Server
- VS Code Editor (Optional)
- MySQL (version > 5)
However, it is not recommended that you use this in Laravel 10 only. You can implement this relationship in earlier versions of Laravel as well.
Step 1 – Project Setup For Implementing Many-to-Many Relationships
For implementing the Many to Many relations in Laravel, I will be creating an example of Users and Roles. So, in this example, multiple users can have multiple roles to access any module of functionality in the application.
Hence, for creating the project in Laravel 10, you will have to hit the below command in the terminal.
composer create-project --prefer-dist laravel/laravel laravel-relation
After installing Laravel 10, you will be required to set up a database for it. Hence, navigate to the project folder and look at the .env file.
After that, under the DB section, add the DB credentials as shown below.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_relation
DB_USERNAME=root
DB_PASSWORD=root
But, you will have to replace the credentials as per your.
Recommended: Simplify Relation By Converting hasMany to hasOne in Laravel 10
Step 2 – Create Models and Migrations in Laravel 10
You can create models and migrations for the User role-based applications as shown below.
php artisan make:model User -mc
In the above command, I have passed m (migration) and c (controller) as a flag. So, it will generate three files. Which will include, a Model, a Migration, and a Controller for the User.
However, you already know that Laravel comes up with the default Model and a Migration for the User.
Next, you will have to create the same for the Role as shown below.
php artisan make:model Role -m
Now, you will have two Models and Migrations. But, we are not done yet.
You will require one additional migration to create a pivot table for managing user roles. Hence, let’s create one more migration quickly. You required only a migration.
php artisan make:migration create_role_user_table
After having these three migrations, let’s put the schemas inside all.
Recommended: Hidden Power of oldestOfMany Relationship in Laravel 10
Step 3 – Create Migration Schema and Model Fillable Data
As per the migrations we have, let’s add the schemas as shown below. Firstly, start with the users table migration.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
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();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
}
};
After the users table migration, let’s move to the roles table migration. Hence, next, add the schema into the roles table.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('roles');
}
};
Now, at last, we have the pivot table for table role_user. Hence, let’s add that one as well.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('role_user', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('role_id');
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('role_user');
}
};
We are done with the migrations. Hence, let’s migrate them using the below command.
php artisan migrate
After that, you will have to add fillable properties in the respective models.
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
Thereafter, you will have to add the same for the Role model as per the schema.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description'
];
}
Now, you are done with the model and migrations setup. Hence, in the next step, you will have to define the relationships in the models.
Recommended: Simplify Data Retrieval Using latestOfMany Relation in Laravel 10
Step 4 – Create Many-to-Many Relationships in Laravel
We will be fetching Users and their roles. Hence, you will require the belongsToMany relation in the User model itself.
public function roles()
{
return $this->belongsToMany(Role::class);
}
The belongsToMany relationship in the above snippet accepted the Role model as a first argument.
Similarly, you can define the same belongsToMany relationship in the Role model as well. So, this will help us to get users belonging to the role.
public function users()
{
return $this->belongsToMany(User::class);
}
After adding the belongsToMany relation in the models, you will have to fetch the data. Therefore let’s add the functionality in the controller.
But, before that, you will require some data based on the relations. So, I already created some data in these tree tables as shown below.
Here, I have created five users in the users table.
After that, I created five records for roles as shown below.
Lastly, I have assigned a few roles to a few users. This is just for the demo purpose.
However, you can create more records as per your need.
Thereafter, in the next step, you require a controller. But, you have already created the UserController in step 2 of this post. Hence, let’s move to the functionality part quickly.
Recommended: How to Create and Use belongsTo Relationship in Laravel 10
Step 5 – Fetch Data Using BelongsToMany Relations in Laravel
For fetching the belongsToMany relation data, you will have to add the below snippet in the controller.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index() {
$users = User::with('roles')->get();
return view('users', compact('users'));
}
}
In the above snippet, I have fetched all the users along with the roles. After that, I returned the users data to the view to display.
Hence, let’s create a view in order to render the data.
Step 6 – Create a View to Render Many-to-Many Relation Data
Simply create a view with the name users.blade.php. Thereafter, you will have to add the below snippet inside it.
<!doctype html>
<html lang="en">
<head>
<title>Laravel Many to Many Relations</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
</head>
<body>
<main class="pt-4">
<div class="container-fluid px-5 pt-3">
<h4 class="text-center fw-bold border-bottom pb-3"> Many-to-Many Relationships in Laravel 10 </h4>
<div class="table-responsive pt-1">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="5%">Id</th>
<th width="30%">Name</th>
<th>Roles</th>
</tr>
</thead>
<tbody>
@forelse ($users as $user)
<tr>
<td>{{ $user->id }} </td>
<td>{{ $user->name }} </td>
<td>
<ol>
@foreach ($user->roles as $role)
<li>
<dt>{{ $role->name }} </dt>
<dl>- {{$role->description}} </dl>
</li>
@endforeach
</ol>
</td>
</tr>
@empty
<tr>
<td colspan="4">
<p class="text-danger">No data found </p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</main>
<!-- Bootstrap JavaScript Libraries -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"
integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.min.js"
integrity="sha384-7VPbUDkoPSGFnVtYi0QogXtr74QeVeeIs99Qfg5YCF+TidwNdjvaKZX19NZ/e6oz" crossorigin="anonymous">
</script>
</body>
</html>
Last, but not least, you will have to create a route for executing the controller function. So, let’s create it now.
Step 7 – Add Route in Laravel
In order to add the web route, you will have to navigate to the routes/web.php file.
<?php
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
Route::get('users', [UserController::class, 'index']);
Now, your application is ready to be executed. So, run the application and see the result.
Conclusion
Many-to-many relationships in Laravel are a robust tool for building complex and scalable applications. However, to ensure your application maintains its performance as it grows, you must apply efficient techniques like indexing, eager loading, caching, and potentially database sharding. By doing so, you’ll be well-prepared to scale your Laravel applications gracefully and handle even the most intricate many-to-many relationships.
Jay says
Great article on scaling Laravel apps! For those looking to deepen their knowledge.