Laravel hasManyThrough relationship

Laravel Has Many Through relationship

In this article let’s understand Laravel hasManyThrough relationship.

Laravel provides many relationship to work with multiple tables and one of them is Has Many Through relationship.

Check below graphics to understand it in simple scenario.

hasManyThrough Laravel

What you can see from graphics is one Country will have many States and one State will have many Cities. When we wants to access cities of a country we will have hasManyThrough relationship.

Let’s understand it through example where we have:

countries table:
id
name
----------------------
states table:
id
name
country_id
----------------------
cities table:
id
name
state_id
php artisan make:model Country -m
php artisan make:model State -m
php artisan make:model City -m

This will create three models and three migrations as below:

app/Models/
    Country.php
    State.php
    City.php

database/migrations/
    ***_create_countries_table.php
    ***_create_states_table.php
    ***_create_cities_table.php

Open ***_create_countries_table.php and modify as below:

public function up(): void
    {
       Schema::create('countries', function (Blueprint $table) {
          $table->id();
          $table->string("name");
         $table->timestamps();
       });
    }

Check ***_create_states_table.php and modify as below:

Set foreign key on country_id.

public function up(): void
    {
       Schema::create('states', function (Blueprint $table) {
           $table->id();
           $table->string("name");
           $table->bigInteger('country_id')->unsigned()->index();
           $table->foreign('country_id')->references('id')->on('countries')->onDelete('cascade');
           $table->timestamps();
       });
    }

Check ***_create_cities_table.php and modify as below:

Set foreign key on state_id.

public function up(): void
    {
       Schema::create('cities', function (Blueprint $table) {
           $table->id();
           $table->string("name");
           $table->bigInteger('state_id')->unsigned()->index();
           $table->foreign('state_id')->references('id')->on('states')->onDelete('cascade');
           $table->timestamps();
       });
}

Open app/Models/Country.php and modify as below:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
    use HasFactory;
    public function states()
    {
        return $this->hasMany('App\State');
    }
    public function cities()
    {
        return $this->hasManyThrough('App\City', 'App\State');
    }
}

You can see we have used hasManyThrough to connect cities directly to country through state.

If you will have different ids set than specific format you may need to define them as below:

// define if different naming used
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 countries table...
            'id' // Local key on state table...
        );

Check app/Models/State.php and modify as blow:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class State extends Model
{
    use HasFactory;
    public function cities()
    {
        return $this->hasMany('App\City');
    }
    public function countries()
    {
        return $this->belongsTo('App\Country');
    }    
}

Check app/Models/City.php and modify as blow:


namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class City extends Model
{
    use HasFactory;
    public function states()
    {
        return $this->belongsTo('App\State');
    }
}

Run Migrations:

php artisan migrate

Use below methods in your controller to check how hasManyThrough relationship works:

//Fetch cities of country

$country = Country::find(1);
foreach ($country->cities as $city) {
    echo $city->name . "\n";
}

//Fetch states of country

$country = Country::find(1);
foreach ($country->states as $state) {
    echo $state->name . "\n";
}

//Fetch cities of state

$states = State::find(1);
foreach ($states->city as $city ) {
    echo $city->name . "\n";
}

//Fetch state of city

$city = City::find(1);
echo $city->state;

So here we can directly retrieve cities of specific country of all the states.

That’s it on Laravel hasManyThrough relationship. Hope this finds you helpful.

See other articles on Laravel here.