Hey there! If you're diving into Laravel 12 and want to master the "has many through" relationship, you’ve come to the right place. In this beginner-friendly guide, I’ll walk you through setting up a has many through relationship using Laravel’s Eloquent ORM.
We’ll use a clear example: a Country
that has many Posts
through Users
. By the end, you’ll know how to define models, migrations, and relationships to access related data across tables.
A has many through relationship allows you to access distant related records through an intermediate model. For example:
Country
has many Users
.User
has many Posts
.Users
, a Country
has many Posts
.This is useful when you want to retrieve all posts from a country without directly linking the countries
and posts
tables.
Before we begin, ensure you have:
If you don’t have a project, create one with:
laravel new example-app
We’ll create three tables:
countries
: Stores country data.users
: Stores user data with a reference to a country.posts
: Stores post data with a reference to a user.Run this command to create a migration for the countries
table:
php artisan make:migration create_countries_table
Open the generated migration file in database/migrations
and update it:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('countries');
}
};
Laravel includes a default users
migration. Open the migration in database/migrations
and modify it to include a country_id
foreign key:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->unsignedBigInteger('country_id');
$table->timestamps();
$table->foreign('country_id')->references('id')->on('countries')->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
Run this command to create a migration for the posts
table:
php artisan make:migration create_posts_table
Open the generated migration file and update it:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('title');
$table->text('content');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Explanation:
country_id
in users
links to countries
.user_id
in posts
links to users
.onDelete('cascade')
ensures related records are deleted if the parent record is removed.Run the migrations to create the tables:
php artisan migrate
Define the Country
, User
, and Post
models and set up their relationships.
Run:
php artisan make:model Country
Open app/Models/Country.php
and add the hasMany
and hasManyThrough
relationships:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Country extends Model
{
protected $fillable = ['name'];
public function users(): HasMany
{
return $this->hasMany(User::class);
}
public function posts(): HasManyThrough
{
return $this->hasManyThrough(Post::class, User::class);
}
}
Explanation:
hasMany
defines that a Country
has many Users
.hasManyThrough
defines that a Country
has many Posts
through Users
.Open app/Models/User.php
and add the belongsTo
and hasMany
relationships:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Authenticatable
{
protected $fillable = ['name', 'email', 'password', 'country_id'];
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
Explanation:
belongsTo
indicates a User
belongs to a Country
.hasMany
indicates a User
has many Posts
.Run:
php artisan make:model Post
Open app/Models/Post.php
and add the belongsTo
relationship:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Post extends Model
{
protected $fillable = ['user_id', 'title', 'content'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Explanation: The belongsTo
method indicates a Post
belongs to a User
.
Create a controller to test the has many through relationship and a route to access it.
Run:
php artisan make:controller CountryController
Open app/Http/Controllers/CountryController.php
and add:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Country;
use App\Models\User;
use App\Models\Post;
class CountryController extends Controller
{
public function index()
{
// Create a country
$country = Country::create(['name' => 'Canada']);
// Create a user for the country
$user = User::create([
'name' => 'Alice Smith',
'email' => '[email protected]',
'password' => bcrypt('password'),
'country_id' => $country->id,
]);
// Create posts for the user
$user->posts()->createMany([
[
'title' => 'First Post',
'content' => 'Welcome to my blog!',
],
[
'title' => 'Second Post',
'content' => 'Exploring Laravel!',
],
]);
// Retrieve the country with its posts
$countryWithPosts = Country::with('posts')->first();
return response()->json([
'country' => $countryWithPosts->name,
'posts' => $countryWithPosts->posts->map(function ($post) {
return [
'title' => $post->title,
'content' => $post->content,
'author' => $post->user->name,
];
}),
]);
}
}
Explanation:
with('posts')
to eager-load the posts through users.Open routes/web.php
and add:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CountryController;
Route::get('/test-relationship', [CountryController::class, 'index'])->name('test.relationship');
Start your Laravel server:
php artisan serve
Visit http://localhost:8000/test-relationship
in your browser. You should see a JSON response like:
{
"country": "Canada",
"posts": [
{
"title": "First Post",
"content": "Welcome to my blog!",
"author": "Alice Smith"
},
{
"title": "Second Post",
"content": "Exploring Laravel!",
"author": "Alice Smith"
}
]
}
This confirms the has many through relationship is working!
You can access related data in various ways:
$country = Country::find(1);
$posts = $country->posts; // Returns a collection of Posts
foreach ($posts as $post) {
echo $post->title; // Outputs: First Post, Second Post
}
$post = Post::find(1);
$user = $post->user; // Returns the associated User
$country = $user->country; // Returns the associated Country
echo $country->name; // Outputs: Canada
Create a Blade file resources/views/country.blade.php
:
<!DOCTYPE html>
<html>
<head>
<title>Laravel 12 Has Many Through Relationship</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h3>Posts from {{ $country->name }}</h3>
<h4>Posts:</h4>
<ul>
@foreach ($country->posts as $post)
<li>
<strong>{{ $post->title }}</strong>: {{ $post->content }} (by {{ $post->user->name }})
</li>
@endforeach
</ul>
</div>
</body>
</html>
Update the CountryController
to return the view:
public function index()
{
$country = Country::with('posts')->first();
return view('country', compact('country'));
}
Implementing a has many through relationship in Laravel 12 is a powerful way to access distant related data with Eloquent ORM. This tutorial showed you how to create migrations, define models, and set up a has many through relationship using a Country
, Users
, and Posts
example.
Whether you’re building a global blog or another complex system, this approach simplifies data retrieval. I hope this guide was clear and helps you add has many through relationships to your Laravel projects.
Q1: When should I use a has many through relationship?
A: Use it when you need to access records in a distant table through an intermediate model, like getting all posts in a country through users.
Q2: What’s the difference between hasMany
and hasManyThrough
?
A: hasMany
defines a direct one-to-many relationship (e.g., User
has many Posts
). hasManyThrough
accesses records through an intermediate model (e.g., Country
has many Posts
through Users
).
Q3: Why use onDelete('cascade')
?
A: It ensures data integrity by deleting related records (e.g., posts or users) when the parent record (e.g., country or user) is deleted.
Q4: How do I handle cases where a country has no posts?
A: The posts
relationship returns an empty collection if no posts exist. Check with @if($country->posts->isEmpty())
in Blade or $country->posts->isEmpty()
in PHP.
Q5: Can I add extra conditions to the has many through relationship?
A: Yes! Modify the relationship in the model, e.g.:
public function posts(): HasManyThrough
{
return $this->hasManyThrough(Post::class, User::class)->where('posts.created_at', '>=', now()->subMonth());
}
This filters posts from the last month.
You might also like: