Laravel has consistently proven itself as a powerful PHP framework for web development. One of its standout features is Eloquent. Laravel’s ORM (Object-Relational Mapping) system, which simplifies database interactions. The eloquent provides various relationship types to establish connections between database tables. Among these one of the more advanced relationships is hasManyThrough relation in Laravel. In this blog post, we will understand more advanced aspects of Eloquent. That is the hasManyThrough relation in Laravel 10. Also, we will explore how it can be used to build sophisticated database relationships in Laravel 10.
Understanding the hasManyThrough Relations in Laravel
The hasManyThrough relationship in Laravel is particularly useful when dealing with complex database structures. It involves intermediate tables. This relationship allows you to retrieve data from a distantly related table through a chain of other tables.
Here’s a common scenario where you can use hasManyThrough relation in your Laravel project. Let’s consider a database schema with three tables- countries, states, and cities. You want to retrieve a list of all cities associated with a specific country. Without hasManyThrough, this could involve multiple queries and complex SQL joins. However, with hasManyThrough, you can achieve this in a cleaner and more efficient manner.
Let’s start by understanding the components of the hasManyThrough relationship:
- Parent Model: This is the model that directly “has many” of the related model through the intermediate table. In our example, the Country model is the parent, as it has many City models indirectly through the State model.
- Intermediate Model: This is the model that connects the parent and related models. In our case, it’s the State model, as it acts as an intermediary between Country and City.
- Related Model: This is the model that you ultimately want to access. In our example, it’s the City model.
We are going to achieve the below result. Here, I have fetched all the countries along with the states. Also, via the states, all the cities are displayed.
Now, let’s move to the project setup to implement this example in Laravel 10.
Recommended: Efficient Data Retrieval With hasOneThrough Relation in Laravel
Prerequisites
If you want to proceed with the Laravel 10 for the hasManyThrough relation then you would require the below configurations.
- PHP >=8.1
- Composer
- Apache/Nginx Server
- VS Code Editor (Optional)
- MySQL (version > 5)
However, you can implement the hasManyThrough relation in the older version of Laravel as well.
Once you are done, let’s proceed to the example.
I am assuming you are ready with the project and DB setup. Hence, I’m skipping these two steps.
Step 1 – Create Models and Migrations For hasManyThrough Relation in Laravel
For implementing the hasManyThrough relation in Laravel, you will require at least three models and migrations. Hence, create them one by one using the below command.
php artisan make:model Country -m
php artisan make:model State -m
php artisan make:model City -m
After having the models and migrations, let’s put the schema in every migration first.
Recommended: Simplify Relation By Converting hasMany to hasOne in Laravel 10
Step 2 – Add DB Schema and Fillable Data Properties
You have to add the schema in the created migrations. Hence, let’s add it one by one.
<?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('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('countries');
}
};
Next, you will have to add it to the states and cities 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('states', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedBigInteger('country_id');
$table->foreign('country_id')->references('id')->on('countries')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('states');
}
};
Lastly, we have cities 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('cities', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedBigInteger('state_id');
$table->foreign('state_id')->references('id')->on('states')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cities');
}
};
After adding the schemas in the migrations, you will have to migrate them all.
php artisan migrate
The tables will be migrated to the database.
Thereafter, you will have to add fillable properties in these three models. Hence, let’s add them as well.
Initially, start with the Country model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
use HasFactory;
protected $fillable = [
'name'
];
}
Secondly, you have the State model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class State extends Model
{
use HasFactory;
protected $fillable = [
'name',
'country_id'
];
}
At last, you will have to add to the City model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class City extends Model
{
use HasFactory;
protected $fillable = [
'name',
'state_id'
];
}
Now, next you can define the hasManyThrough relation.
Recommended: Hidden Power of oldestOfMany Relationship in Laravel 10
Step 3 – Define hasManyThrough Relation in Laravel 10
We will be fetching cities from the Country model via the state (intermediary) model. Hence, you will have to define the hasManyThrough relation in the Country model itself.
Let’s add the below snippet in the Country model.
public function cities()
{
return $this->hasManyThrough(
City::class,
State::class,
'country_id', // Foreign key on state table...
'state_id', // Foreign key on city table...
'id', // Local key on country table
'id' // Local key on state table
);
}
Also, we will define one hasMany relation in the Country model for the States. So, the Country model will look like this.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
use HasFactory;
protected $fillable = [
'name'
];
/**
* Function : hasMany States
* @relationType : hasMany
*/
public function states() {
return $this->hasMany(State::class);
}
/**
* Function : hasManyThrough Relation
* @relationType : hasManyThrough
*/
public function cities()
{
return $this->hasManyThrough(
City::class,
State::class,
'country_id', // Foreign key on state table...
'state_id', // Foreign key on city table...
'id', // Local key on country table
'id' // Local key on state table
);
}
}
Similarly, you can define relations in the State and City model as well.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class State extends Model
{
use HasFactory;
protected $fillable = [
'name',
'country_id'
];
public function country() {
return $this->belongsTo(Country::class);
}
/**
* Function : hasMany City
* @relationType : hasMany
*/
public function cities() {
return $this->hasMany(City::class);
}
}
Here, in the above model (State Model), I have defined the relation for hasMany cities and belongsTo country.
Lastly, you have the City model. Hence, let’s add a relation for that as well. This is optional for this post because we will be fetching the country-wise cities via states.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class City extends Model
{
use HasFactory;
protected $fillable = [
'name',
'state_id'
];
/**
* Function : belongsTo State
* @relationType : belongsTo
*/
public function state() {
return $this->belongsTo(State::class);
}
}
That’s the relation. Now, let’s see the power of this hasManyThrough relation by fetching data.
I have already created some data for testing purposes using seeder. I assume you have this.
Hence, I am moving to fetch the hasManyThrough relation in Laravel 10.
Recommended: Simplify Data Retrieval Using latestOfMany Relation in Laravel 10
Step 4 – Fetch HasManyThrough Relation Data in Laravel 10
Firstly, you will have to create a controller in order to write a function. Therefore, let’s create it by using the below command.
php artisan make:controller CountryController
After having the controller, simply put the below function inside it.
<?php
namespace App\Http\Controllers;
use App\Models\Country;
use Illuminate\Http\Request;
class CountryController extends Controller
{
public function index() {
$countries = Country::with('states', 'cities')->get();
return view('countries', compact('countries'));
}
}
Here, I have fetched all the countries along with states and cities. However, we know cities are not related to the country directly. It is related via the intermediary model that is the State.
Recommended: How to Create and Use belongsTo Relationship in Laravel 10
Step 5 – Create a View to Render the HasManyThrough Relation
You have to create a view by specifying the name as countries.blade.php. After that, you have to add the below snippet for rendering the HasManyThrough relation data.
<!doctype html>
<html lang="en">
<head>
<title>Laravel HasManyThrough Relation</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS v5.2.1 -->
<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>
<div class="container-fluid px-5 pt-3">
<h4 class="text-center fw-bold border-bottom pb-3"> Has Many Through Relationship 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="20%">Name</th>
<th>States</th>
<th>Cities</th>
</tr>
</thead>
<tbody>
@forelse ($countries as $country)
<tr>
<td>{{ $country->id }} </td>
<td>{{ $country->name }} </td>
<td>
<ol>
@foreach ($country->states as $state)
<li>
<dt>{{ $state->name }} </dt>
</li>
@endforeach
</ol>
</td>
<td>
<ol>
@foreach ($country->cities as $city)
<li>
<dd> <span>- {{ $city->name }} </span> <strong>({{ $city->state->name }})</strong></dd>
</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>
At last, you have to create a route for rendering the controller function.
Step 6 – Add Route
To add the route, you have to navigate to the web.php file. After that, you need to add the below route.
<?php
use App\Http\Controllers\CountryController;
use Illuminate\Support\Facades\Route;
Route::get('countries', [CountryController::class, 'index']);
That’s it for the functionality. Now, you are ready to serve the application in order to see the result.
Conclusion
In Laravel 10, the hasManyThrough relationship remains a powerful tool for developers dealing with complex database structures. By understanding how to set up and use this relationship effectively, you can simplify your code, improve performance, and create more maintainable applications.
In this blog post, we’ve covered the fundamentals of hasManyThrough from its components to its implementation and benefits. Armed with this knowledge, you’ll be well-equipped to tackle projects that involve distant relationships between database tables. Laravel’s Eloquent ORM, combined with relationships like hasManyThrough empowers you to build advanced web applications with ease. Whether you’re building an e-commerce platform, a content management system, or any other web application, the hasManyThrough can be a valuable tool in your Laravel toolkit.
Rishi V says
It helped me a lot to understand hasmanythrough relations. Thanks for sharing.