HasOneThrough の引数指定について、公式に記載されてない方法をご紹介します。
Laravelは、一対一・一対多の関係のほか、
中間モデルを介して別のモデルを結びつけるHasManyThrough/HasOneThroughという関係があります。
これを定義すると、
モデルAからモデルBを介してモデルCを取得する
といったことができます。
しかし、このリレーションは引数が複雑なので、使う時に毎回調べている方もいるんじゃないでしょうか(←私です)
共同で作業している人がいる時なんかも、相棒が誤解しないようにコメントを書くなどして、配慮が必要かもしれませんね。
今回は、そのHasOneThroughについて、公式に記載されてない定義方法があったので残しておこうと思います。
使う場面ないかもしれませんが…😅
こちらのページを参考にさせていただきました!
- LaravelのEloquentORMでモデルベースのDBリレーション~基本からEagerロードまで~
- 【Laravel】hasManyThroughリレーションを使う方法まとめ
- Laravelのリレーション hasManyThrough の使い方を毎回間違うのでメモ
HasOneThrough 公式に無かった定義方法

よくあるのはHasManyThroughで定義するモデルA→モデルB→モデルCというパターンですが、
逆からたどるようなHasOneThroughで定義するモデルC→モデルB→モデルAというパターンはややこしいです。
サンプルモデルの関係図

「漫画(Comic)」は複数の「話(Chapter)」で構成され、「話」は複数の「ページ(Page)」を持っていたとします。
「漫画」と「話」は一対多で、「話」と「ページ」も一対多です。
以下の2パターンを定義するとしたらどのようになるでしょうか。
- 漫画から全ページを取得:
漫画(Comic) → 話(Chapter) → ページ(Page) - 任意のページが含まれる漫画を取得:
ページ(Page) → 話(Chapter) → 漫画(Comic)
HasManyThrough :漫画から全ページを取得
Comicモデルから全Pageモデルを取得する場合は以下のように定義します。
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Comic extends Model
{
/**
* 漫画の全ページを取得する
*/
public function pages(): HasManyThrough {
return $this->hasManyThrough(
Page::class,
Chapter::class,
'comic_id', // Chapterの外部キー
'chapter_id', // Pageの外部キー
'id', // Comicのローカルキー
'id' // Chapterのローカルキー
);
}
}
HasOneThrough :任意のページが含まれる漫画を取得
Pageモデルから、結びついているComicモデルを取得する場合は以下のように定義します。
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
class Page extends Model
{
/**
* 任意のページが含まれる漫画を取得
*/
public function comic(): HasOneThrough
{
return $this->hasOneThrough(
Comic::class,
Chapter::class,
'id', // Comicのローカルキー
'id', // Chapterのローカルキー
'chapter_id', // Pageの外部キー
'comic_id' // Chapterの外部キー
);
}
}
まとめ
この記事のパターン

public function comic(): HasOneThrough
{
return $this->hasOneThrough(
Comic::class, // 最終モデル
Chapter::class, // 中間モデル
'id', // 中間モデルのローカルキー
'id', // 最終モデルのローカルキー
'chapter_id', // このモデルの外部キー
'comic_id' // 中間モデルの外部キー
);
}
公式ドキュメントのパターン

public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(
Owner::class, // 最終モデル
Car::class, // 中間モデル
'mechanic_id', // 中間モデルの外部キー
'car_id', // 最終モデルの外部キー
'id', // このモデルのローカルキー
'id' // 中間モデルのローカルキー
);
}