Advertisement

Google Ad Slot: content-top

Has One Through


What is Has One Through?

  • A Has One Through relationship is used when a model is related to another model through an intermediate model.
  • Example:
  • A Manager has one Profile.
  • But the Profile is not directly linked to the Manager.
  • Instead, the link is through the Employee table.
  • So the connection looks like:
Manager → Worker → Salary

Flow diagram


Migration:

Use this command to generate a migration for creating a tables:

php artisan make:migration create_has_one_through_tables


Migration File

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

    // Workers table
    Schema::create('workers', function (Blueprint $table) {
        $table->id();
        $table->foreignId('manager_id')->constrained()->onDelete('cascade');
        $table->string('name');
        $table->timestamps();
    });

    // Salaries table
    Schema::create('salaries', function (Blueprint $table) {
        $table->id();
        $table->foreignId('worker_id')->constrained()->onDelete('cascade');
        $table->decimal('amount', 10, 2);
        $table->timestamps();
    });
}

public function down(): void
{
    Schema::dropIfExists('salaries');
    Schema::dropIfExists('workers');
    Schema::dropIfExists('managers');
}


Run Migration

php artisan migrate

Tables

managers table
id name created_at updated_at
workers table
id name manager_id created_at updated_at
salaries table
id amount worker_id created_at updated_at
  • workers.manager_id is a foreign key referencing managers.id.
  • salaries.worker_id is a foreign key referencing workers.id.

Model:

Generate a model with Artisan:

php artisan make:model Manager
php artisan make:model Employee
php artisan make:model Profile

This generates 3 files Manager.php , Employee.php and Profile.php


Manager.php

namespace App\Models;

use App\Models\Salary;
use App\Models\Worker;
use Illuminate\Database\Eloquent\Model;

class Manager extends Model
{
    protected $fillable = ['name'];

    // HasOneThrough: Manager → Worker → Salary
    public function salary()
    {
        return $this->hasOneThrough(
            Salary::class,   // Final model
            Worker::class,   // Intermediate model
            'manager_id',    // Foreign key on workers table
            'worker_id',     // Foreign key on salaries table
            'id',            // Local key on managers
            'id'             // Local key on workers
        );
    }
}


Worker.php

namespace App\Models;

use App\Models\Salary;
use App\Models\Manager;
use Illuminate\Database\Eloquent\Model;

class Worker extends Model
{
    protected $fillable = ['name', 'manager_id'];

    public function manager()
    {
        return $this->belongsTo(Manager::class);
    }

    public function salary()
    {
        return $this->hasOne(Salary::class);
    }
}


Salary.php

namespace App\Models;

use App\Models\Worker;
use Illuminate\Database\Eloquent\Model;

class Salary extends Model
{
    protected $fillable = ['amount', 'worker_id'];

    public function worker()
    {
        return $this->belongsTo(Worker::class);
    }
}

CRUD Examples

Create

use App\Models\Worker;
use App\Models\Salary;
use App\Models\Manager;

// Create a Manager
$manager = Manager::create(['name' => 'John']);

// Create Worker under Manager
$worker = Worker::create([
    'manager_id' => $manager->id,
    'name' => 'Contract Worker'
]);

// Assign Salary to Employee
$salary = Salary::create([
    'worker_id' => $worker->id,
    'amount' => 50000
]);

Tables

managers table
id name created_at updated_at
1 John 2025-08-25 05:39:34 2025-08-25 05:39:34
workers table
id name manager_id created_at updated_at
1 Contract Worker 1 2025-08-25 05:39:34 2025-08-25 05:39:34
salaries table
id amount worker_id created_at updated_at
1 50000 1 2025-08-25 05:39:34 2025-08-25 05:39:34

Read (Get Related Data)

use App\Models\Worker;
use App\Models\Manager;

// Get Manager's Salary through Worker
$managerSalary = Manager::find(1)->salary;
echo $managerSalary->amount . "<br>"; // Output: 50000

// Get Worker with Salary
$worker = Worker::first();
echo $worker->salary->amount . "<br>"; // Output: 50000

Update (Modify Data)

use App\Models\Worker;
use App\Models\Salary;
use App\Models\Manager;

// Update salary
$salary = Salary::where('worker_id', 1)->first();
$salary->update(['amount' => 60000]);

// Or directly
$worker = Worker::find(1);
$worker->salary->amount = 65000;
$worker->salary->save();

// Or via Manager using HasOneThrough
$manager = Manager::find(1);
$manager->salary->amount = 70000;
$manager->salary->save();

4. Delete (Remove Data)

use App\Models\Worker;
use App\Models\Salary;
use App\Models\Manager;

// Delete Salary
$salary = Salary::first();
$salary->delete();

// Delete Salary via Worker using hasOne
$worker = Worker::first();
$worker->salary->delete();

// Delete Salary via Manager using hasOne
$manager = Manager::first();
$manager->salary->delete();

// Delete Employee (will cascade Salary also if set cascade)
$worker = Worker::first();
$worker->delete();

// Delete Manager (cascade removes workers & their salaries)
$manager = Manager::first();
$manager->delete();

Eager loading (optimizes queries)

use App\Models\Worker;
use App\Models\Manager;

// Get Manager's Salary through Worker
$managerSalary = Manager::with('salary')->find(1);
echo $managerSalary->amount . "<br>"; // Output: 50000

// Get Worker with Salary
$worker = Worker::with('salary')->first();
echo $worker->salary->amount . "<br>"; // Output: 50000

Always use with() when fetching related data in bulk.