CodlessCode
PHP

[ Laravel ] HasOneThrough の公式に無い定義方法について

HasOneThrough の引数指定について、公式に記載されてない方法をご紹介します。

Laravelは、一対一・一対多の関係のほか、
中間モデルを介して別のモデルを結びつけるHasManyThroughHasOneThroughという関係があります。
これを定義すると、
モデルAからモデルBを介してモデルCを取得する
といったことができます。

しかし、このリレーションは引数が複雑なので、使う時に毎回調べている方もいるんじゃないでしょうか(←私です)
共同で作業している人がいる時なんかも、相棒が誤解しないようにコメントを書くなどして、配慮が必要かもしれませんね。

今回は、そのHasOneThroughについて、公式に記載されてない定義方法があったので残しておこうと思います。
使う場面ないかもしれませんが…😅


Laravel 8.x – HasOneThrough

こちらのページを参考にさせていただきました!

 
 

HasOneThrough 公式に無かった定義方法

HasOneThroughイメージ

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

 
 

サンプルモデルの関係図

HasOneThrough関係図

「漫画(Comic)」は複数の「話(Chapter)」で構成され、「話」は複数の「ページ(Page)」を持っていたとします。
「漫画」と「話」は一対多で、「話」と「ページ」も一対多です。

以下の2パターンを定義するとしたらどのようになるでしょうか。

  1. 漫画から全ページを取得
    漫画(Comic) → 話(Chapter) → ページ(Page)
  2. 任意のページが含まれる漫画を取得
    ページ(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'           // 中間モデルのローカルキー
    );
}