Eloquent:リレーション 6.x Laravel#Has Many Through
LaravelではEloquent(データベース中のテーブルをマッピングしたモデルクラス)のリレーションを表現する機能が備わっています。HasManyThroughはその中の一つで中間テーブルをはさんだ多対多の関係を表現できます。問題のHasManyThroughは次の様に記述できます。
### DB
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
class Country extends Model
{
public function posts()
{
// ()ないは主キーと
return $this->hasManyThrough(
'App\Post',// つなげる先のテーブルクラス
'App\User',// 中間テーブルクラス
'country_id', // 中間テーブルの外部キー
'user_id', // つなげる先のテーブルの外部キー
'id', // 元テーブルのローカルキー
'id' // つなげる先のテーブルのローカルキー
);
}
}
## 別ファイル
$country = Country::first();
// プロパティとしていきなり呼べるようになる
$posts = $ocuntry->posts; // Collection<Post>
便利なのですが危険なくらい混乱します。というのも引数が6個もある上、どこかの主キーの言い換えが4個占めていて大変紛らわしいためです。よく使う状況に合わせて何かしらメソッドを作った方がいいです。例えば自分がよく使うのは次です。
class BaseEloquent extends Model
{
/**
* @param string $tgtClass 関係を結ぶテーブルクラス
* @param string $throughClass 通る中間テーブルクラス
* @param string $throughHasThisPrimaryKeyColumn 中間テーブルの持つ$thisの主キーを指すカラム
* @param string $throughHasTgtPrimaryKeyColumn 中間テーブルの持つ関係先の主キーを指すカラム
* @return HasManyThrough
*/
public function hasManyThroughWrapper(
string $tgtClass,
string $throughClass,
string $throughHasThisPrimaryKeyColumn,
string $throughHasTgtPrimaryKeyColumn
): HasManyThrough {
return $this->hasManyThrough(
$tgtClass,
$throughClass,
$throughHasThisPrimaryKeyColumn,
(new $tgtClass())->getKeyName(),
$this->getKeyName(),
$throughHasTgtPrimaryKeyColumn,
);
}
}
これは次の様な二つの1対多でつながった中間テーブルを対象にしたメソッドです。

上記PUML図のテーブル定義を対象にすると次の様に定義できます。
class User extends BaseEloquent
{
/**
* コメントした投稿を取得
* @return HasManyThrough
*/
public function commentedPosts(): HasManyThrough
{
return $this->hasManyThroughWrapper(
Post::class,
Comment::class,
'user_id', // 引数のカラム名は中間テーブル(commentsテーブル)の持つカラム名だけで済む
'post_id'
);
}
}
Eloquent には主キー名を取得するメソッドである getKeyName があります。これを使って引数を減らせるのでうまい具合に言い換えメソッドを作れます。