Day 4・EloquentとリレーションとN+1問題
リレーション
テーブル同士のつながり(1対多・多対多など)をモデルのメソッドとして表現する。
リレーションとは
現実のデータは互いに関係しています。「1人のユーザーが複数の投稿を持つ」「1つの投稿は1人のユーザーに属する」といったつながりが です。Eloquentでは、このつながりをモデルのメソッドとして定義します。定義しておくと、外部キーを意識せずに「$user->posts」のようにたどれるようになります。
- hasMany(1対多): 1人のユーザーが複数の投稿を持つ(User → posts)。
- belongsTo(多対1): 1つの投稿は1人のユーザーに属する(Post → user)。
- belongsToMany(多対多): 投稿は複数のタグを持ち、タグも複数の投稿に付く(中間テーブルを使う)。
- hasOne(1対1): 1人のユーザーが1つのプロフィールを持つ、など。
class User extends Model
{
// 1人のユーザーは複数の投稿を持つ
public function posts()
{
return $this->hasMany(Post::class);
}
}
class Post extends Model
{
// 1つの投稿は1人のユーザーに属する(hasMany の逆向き)
public function user()
{
return $this->belongsTo(User::class);
}
}なぜ外部キーを直接触らずリレーションを定義するのか
「user_id が一致する行を探す」ようなSQLを都度書くと、結合条件があちこちに散らばり、修正時に漏れます。リレーションとして1か所に定義しておけば、以降は「$user->posts」「$post->user」と意味の分かる形でたどれ、結合の詳細を意識せずに済みます。関係性の定義を一箇所に集約する、という保守性の設計です。
$user = User::find(1);
// メソッドをプロパティのように呼ぶと、関連データを取得できる
foreach ($user->posts as $post) {
echo $post->title;
}
// 逆向きもたどれる
$post = Post::find(10);
echo $post->user->name;class Post extends Model
{
// 投稿は複数のタグを持ち、タグも複数の投稿に付く
public function tags()
{
// post_tag という中間テーブルを介して結びつく
return $this->belongsToMany(Tag::class);
}
}落とし穴(次のレッスンへの布石)
「$user->posts」のように後からリレーションにアクセスすると、その瞬間に追加のSQLが1本発行されます(遅延ロード)。これ自体は正しい挙動ですが、ループの中で毎回アクセスすると、SQLが件数分だけ増えてしまう——これが次に学ぶ 問題です。
面接ではこう言う
「リレーションは、テーブル同士のつながりをモデルのメソッドとして定義する仕組みです。hasMany・belongsTo・belongsToManyで1対多・多対1・多対多を表し、以降は $user->posts のように意味の分かる形でたどれます。結合条件を1か所に集約できるので保守性が上がります」と説明できます。
読んでも腑に落ちない所は、AIに噛み砕いてもらいましょう。
理解度チェック
答えられれば、面接で説明できる状態に一歩近づきます。
「1人のユーザーが複数の投稿を持ち、1つの投稿は1人のユーザーに属する」関係を表す組み合わせはどれか。
外部キーで都度JOINを書くのではなく、Eloquentのリレーションとして定義しておく利点を説明せよ。
記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。