How to Use Has-Many-Through Relationships in Laravel

In this tutorial, we will explore how to use the Has-Many-Through relationship in Laravel Eloquent. Through a practical example involving the related entities: “Country,” “Team,” and “Athlete,” I’ll guide you through setting up these relationships and demonstrate how to access and work with them in your Laravel application.

The Has-Many-Through relationship in Laravel connects two models through an intermediate model. To implement it, define a function in the model named after the related table, returning $this->hasManyThrough(Related::class, Intermediate::class).

Take a look at the diagram below, which shows an example of entities related through a Has-Many-Through relationship. In this example, a country has multiple athletes through the intermediate teams entity.

Diagram Showing the Tables Involved in the Has-Many-Through Relationship: countries, teams and athletes

In the following step-by-step guide, we will walk through how to implement the Has-Many-Through relationship in Laravel and create related data that can be accessed through this relationship. Let’s start and learn how to establish and work with a Has-Many-Through relationship in Laravel.

Step 1: Create and Run the Migrations

To begin, let’s create the necessary migrations for the “countries,” “teams,” and “athletes” tables. Open your terminal and run the following Artisan commands:

php artisan make:migration create_countries_table --create=countries
php artisan make:migration create_teams_table --create=teams
php artisan make:migration create_athletes_table --create=athletes

This will generate three migration files: one for the “countries” table, one for the “teams” table, and one for the “athletes” table. Open each migration file and modify the up() and down() methods as shown below.

For the countries table, use:

database/migrations/2023_06_28_162735_create_countries_table.php
<?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');
    }
};

For the teams table, use:

database/migrations/2023_06_28_162736_create_teams_table.php
<?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('teams', function (Blueprint $table) {
            $table->id();
            $table->foreignIdFor(\App\Models\Country::class)->constrained();
            $table->string('name');
            $table->string('sport');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('teams');
    }
};

For the athletes table, use:

database/migrations/2023_06_28_162737_create_athletes_table.php
<?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('athletes', function (Blueprint $table) {
            $table->id();
            $table->foreignIdFor(\App\Models\Team::class)->constrained();
            $table->string('first_name');
            $table->string('last_name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('athletes');
    }
};

After saving the modified migration files, run the following command to create the tables:

php artisan migrate

Step 2: Create the Eloquent Models

Next, let’s create the Eloquent models for the “Country,” “Team,” and “Athlete” entities. Run the following commands in your terminal:

php artisan make:model Country
php artisan make:model Team
php artisan make:model Athlete

This will generate three model files in the “app/Models” directory. Open each model file and add the necessary relationships as shown below.

For the Country Model, use:

app/Models/Country.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    public function teams()
    {
        return $this->hasMany(Team::class);
    }

    public function athletes()
    {
        return $this->hasManyThrough(Athlete::class, Team::class);
    }
}

For the Team Model, use:

app/Models/Team.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Team extends Model
{
    public function country()
    {
        return $this->belongsTo(Country::class);
    }

    public function athletes()
    {
        return $this->hasMany(Athlete::class);
    }
}

For the Athlete Model, use:

app/Models/Athlete.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Athlete extends Model
{
    public function team()
    {
        return $this->belongsTo(Team::class);
    }
}

Step 3: Performing Has-Many-Through Queries

Now that we have set up the migrations and models, we can perform queries using the Has-Many-Through relationship. Here are 2 examples of how you can use the relationship:

routes/web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/testhasmanythrough', function () {
    // Example 1: Retrieve all athletes for a country
    $country = \App\Models\Country::find(1);
    $athletes = $country->athletes;
    foreach ($athletes as $athlete) {
        dump($athlete->attributesToArray());
    }

    // Example 2: Retrieve all athletes for a country
    // Retrieve the country for an athlete
    $athlete = \App\Models\Athlete::find(13);
    $country = $athlete->team->country;
    dump($country->attributesToArray());
});

Further Has-Many-Through Examples

In addition to the “Country”, “Team”, and “Athlete” examples we have used in this tutorial, there are various other scenarios where the Has-Many-Through relationship can be applied. Here are a few more examples:

Parent ModelIntermediate ModelChild Model
CountryStateCity
AuthorBookChapter
UserAccountTransaction
CompanyDepartmentEmployee
SchoolGradeStudent
CategorySubcategoryProduct
CountryUserPost
Table Showing Example Models Which Can Have a Has-Many-Through Relationship

By understanding the concepts and implementation of Has-Many-Through relationships in Laravel, you can effectively manage complex relationships and retrieve data from related models with ease.

Conclusion

In this tutorial, we explored how to use the Has-Many-Through relationship in Laravel to establish complex relationships between entities. By implementing the relationship in the “Country,” “Team,” and “Athlete” models, we were able to create a connection between these entities through an intermediate model.

In this tutorial, we covered essential steps like creating migrations, defining model relationships, and performing queries with the Has-Many-Through relationship. Understanding and implementing this relationship enables you to build robust and interconnected data structures in your Laravel applications.

Finally, as an inspiration, we’ve included a number of example entities to which you might apply a Has-Many-Through Relationship. Happy coding!

References:

Johan van den Broek

Johan is the creator of laracoding.com. As a child, he began tinkering with various programming languages, many of which have been long forgotten today. Currently, he works exclusively with PHP and Laravel, and his passion for programming remains to this day.

2 thoughts on “How to Use Has-Many-Through Relationships in Laravel

  1. Hi Johan,

    Thanks for the tutorial.
    It looks like you’ve missed country_id in the teams migration and the team_id in the Athletes migration.

    1. Hey Danny, thank you for pointing out the missing foreign id columns in the sample code. It appears I didn’t paste the most recent version since my current code has them. I’ve updated the example by adding them to the migrations. I apologize for the oversight. Your feedback is much appreciated!

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts