メシのタネ

めしのたねになるIT情報配信サイト


Eloquentのリレーション徹底解説


  1. Laravel
  2. Eloquentのリレーション徹底解説

Laravel Eloquentの魅力の一つは、テーブル同士の関連をシンプルかつ明快に表現できる「リレーション」機能です。しかし、実際に使ってみると「どっちが hasMany?」「外部キーってどっち側に持たせるんだっけ?」といった疑問が絶えないのも事実。
本記事では、Eloquentのリレーションについてあえて丁寧に分解し、初心者がつまずきやすいポイントを一つずつ解説していきます。「もしかすると知ってるつもりで曖昧かも…」という方は、再確認にぜひ役立ててください。

hasMany/hasOne vs. belongsTo──「親子関係」と「外部キー」で考える

Eloquentでリレーションを定義するときは、「外部キーをどちらが持っているか?」が最重要の視点です。テーブル設計で「どのモデルのIDをどのテーブルに持たせるか」が決まっているはずなので、それに従ってリレーションを設定します。

親モデル(外部キーを持たれ“る”側): hasMany または hasOne

子モデル(外部キーを“持つ”側): belongsTo

例1: User と Post

  • Postテーブルに user_id カラムがある場合
    • users テーブル:親
    • posts テーブル:子(user_id が外部キー)
// 親モデル(User.php)
class User extends Model
{
    // 「複数の投稿を持つ」= hasMany
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

// 子モデル(Post.php)
class Post extends Model
{
    // 「投稿は1人のユーザーに属する」= belongsTo
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

ここで「どっちに hasMany を書くか?」で迷う場合は「どのテーブルが外部キーを持っているか」を確認すると分かりやすいです。外部キー(user_id)を持つのが posts テーブルなので、「Post は User に属する (belongsTo)」。したがって、逆側のUserモデルが「Postを複数持つ (hasMany)」と定義します。

例2: User と Profile

  • Profileテーブルに user_id カラムがある場合
    • users テーブル:親
    • profiles テーブル:子(user_id が外部キー)
// 親モデル(User.php)
class User extends Model
{
    // 「ユーザーは1つのプロフィールを持つ」= hasOne
    public function profile()
    {
        return $this->hasOne(Profile::class);
    }
}

// 子モデル(Profile.php)
class Profile extends Model
{
    // 「プロフィールは1人のユーザーに属する」= belongsTo
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

「1対1」の場合は hasOnebelongsTo の組み合わせになります。これもやはり、外部キーを持っているのはProfileテーブルなので、「Profile は User に属する」という書き方になります。

一方だけ書いても動くけど、両方書くほうがメリット大

Eloquentは片側だけのリレーション定義でも動作自体はします。しかし実務では、「親→子」「子→親」の両方のアクセスが必要になるシーンが多いものです。

  • 親モデル(User)から「このユーザーが持つPost一覧を取得したい」
  • 子モデル(Post)から「この投稿を書いたユーザーを取得したい」

こうした操作が発生するなら、両方にリレーションを定義しておくほうが便利です。片側のみだと、たとえば $post->user() といったアクセスができず、結局クエリを直接書く羽目になるかもしれません。

なぜ混乱しやすい?──「現実世界のイメージ」と「データベース実装」のギャップ

Eloquentリレーションが混乱を招く要因の一つは、下記のような「言葉のニュアンス」と「データベースの実装」が噛み合わない場合があるからです。

  • 「UserがPostを“持つ”とは言うけど、実際にuser_idはPost側にあるからイメージと逆に感じる」
  • belongsTo(属する)のほうが外部キーを持つのか…?」

ここで決め手になるのは、あくまで「どのテーブルに外部キーが存在するか」という事実です。Eloquentの定義はその事実に基づいているため、「belongsTo = 外部キーを持っている側」「hasMany/hasOne = 外部キーを持たれている側」という形になります。

コード例で確認する ── hasMany/hasOneとbelongsToの組み合わせ

多対一(1人のユーザーが複数の投稿を持つ)

// User(親):hasMany
public function posts()
{
    return $this->hasMany(Post::class);
}

// Post(子):belongsTo
public function user()
{
    return $this->belongsTo(User::class);
}

一対一(1人のユーザーに対して1つのプロフィール)

// User(親):hasOne
public function profile()
{
    return $this->hasOne(Profile::class);
}

// Profile(子):belongsTo
public function user()
{
    return $this->belongsTo(User::class);
}

どちらのパターンでも、“親”となるモデルが has___“子”となるモデルが belongsTo という組み合わせです。

Eloquentのリレーションは命名規則に従えば自動的にキーを推測してくれる

LaravelのEloquentはデフォルトで「モデルの主キー=id」「外部キー=モデル名(スネークケース)_id」という命名規則を前提にしているため、以下のようにシンプルに書くだけでキーを設定しなくても動作します。

public function posts()
{
    // デフォルトで「Postテーブルの user_id を使い、Userモデルの id を参照」
    return $this->hasMany(Post::class);
}

public function user()
{
    // デフォルトで「Userテーブルの id と、Postテーブルの user_id を関連付け」
    return $this->belongsTo(User::class);
}

このように「クラス名が User → テーブル名が users → 外部キーが user_id → 親のPKは id」という形で一致していれば、キーの指定を省略してOKです。

もし命名が異なる場合は手動指定が必要

デフォルトの命名規則と異なるスキーマを使っている、あるいは主キーを id 以外の名前にしている場合は、手動でキーを指定する必要があります。たとえば外部キーが author_id で、Userモデルの主キーが user_uuid というようなケースです。

public function posts()
{
    // 第2引数が「外部キー名」、第3引数が「親の主キー名」
    return $this->hasMany(Post::class, 'author_id', 'user_uuid');
}

自動推測が有効な条件

  1. テーブル名: 通常は「モデル名をスネークケース+複数形」
    • 例: Userusers, Postposts
  2. 外部キー: 「親モデル名(スネークケース)_id」
    • 例: Post テーブル側が user_id を持つ場合、belongsTo(User::class) は自動で user_id を探す
  3. 主キー: 通常は id
    • 例: 親の主キーも id であれば、追加指定なしでOK

上記のように、Eloquentのデフォルト命名規則に則ったテーブル設計をしている限り、リレーション定義時にキーを明示しなくても自動で解決してくれます。

まとめ

  • id」を主キーにし、「モデル名_id」を外部キーにしているなら、キー設定を省略してOK。
  • 命名規則と異なる場合は、「->hasMany(Post::class, '外部キー', '主キー')」や「->belongsTo(User::class, '外部キー', '親の主キー')」のように指定が必要。
  • プロジェクト規模が大きくなると「既存のテーブル名やキー名が揃わない」こともあり、そういったときはしっかりキーを指定してEloquentに教えてあげるのがベスト。

Laravelでは規約優先(Convention over Configuration)の設計が多いので、命名を合わせるだけで自動的に動くのはかなり大きなメリットです。もし混乱する場合は「外部キーを持つのはどっち?」「親の主キーは何?」を改めてテーブル定義と照らし合わせながら確認するとスッキリ理解できるでしょう。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.

若い頃、「仕事中にハマったこと」や「誰かに共有したい技術的な気づき」をアウトプットしたくてブログを始めましたが、勢い任せでよく分からない記事を大量生産し、あえなく飽きて終了。

改めて今、キャリア15年分の経験や知識が、これからITエンジニアを目指す方や、同じような課題で悩んでいる現役エンジニアの「メシのタネ」になるような記事を残したいと思っています。
※過去の記事は見ると精神が崩壊するため、そっとしておいてください。

🛠 経歴という名の珍道中:
文系Fラン → 広告営業 → Web営業 → 通信営業 → Web進行 → 出版 → Web媒体運用 → ソフトウェアハウス → SES → フリーランス

専門教育も受けず、転職歴も多数。履歴書はまるで時系列の事故記録のようですが、試行錯誤を重ね、なんとかエンジニアとして食べています。

このブログでは、そんな「履歴書クラッシャー型エンジニア」が送る、
名古屋一敷居の低い、実務に役立つ技術ブログを目指します。

Laravel
Laravel Collection入門: mapとeachの違い、ちゃんと説明できますか?New!!
Laravel
Eloquentのリレーション徹底解説New!!
Laravel
Eloquentは知ってる。でも“理解してる”って言えるのか?New!!
Laravel
FormRequestのポテンシャル、半分も出せてない説New!!
Laravel
Laravel Controllerの“万能感”、そこに幸せはあるのかい?New!!
API
RESTってつまり何?Webエンジニアが悩まないためのAPI設計入門