Eloquentのリレーション活用方法【関連の定義】

Eloquentのリレーションを定義する方法について解説します。
「1対1」「1対多」「多対多」など関係に応じた定義方法をER図とともに解説します。

なお、Eloquentのリレーションについて以下3回に渡って解説しています。他の記事も参考にしてください。

動作確認に利用したリポジトリです。
https://github.com/raku-raku/laravel_eloquent_practice

1対1
( hasOne - belongsTo )

laravel-relation-o-o.png

「ユーザー」と「ユーザーが所有する電話」の関係が 1対1 のとき以下のように定義できます。

User.php

public function phone()
{
    return $this->hasOne(Phone::class);
}
>>> User::first()->phone()->toSql()
=> "select * 
    from `phones` 
    where `phones`.`user_id` = ? 
    and `phones`.`user_id` is not null"

Phone.php

public function user()
{
    return $this->belongsTo(User::class);
}
>>> Phone::first()->user()->toSql()
=> "select * 
    from `users` 
    where `users`.`id` = ?"

1対多
( hasMany - belongsTo )

laravel-relation-o-m.png

ユーザーが複数の投稿を持つ関係を以下のように定義できます。

User.php

public function posts()
{
    return $this->hasMany(Post::class);
}
>>> User::first()->posts()->toSql()
=> "select * 
    from `posts` 
    where `posts`.`user_id` = ? 
    and `posts`.`user_id` is not null"

Post.php

public function user()
{
    return $this->belongsTo(User::class);
}
>>> Post::first()->user()->toSql()
=> "select * 
    from `users` 
    where `users`.`id` = ?"

〜経由の1対多
( hasManyThrough )

laravel-relation-o-m.png

「国」と「ユーザー」の関係が 1対多 であり、
「ユーザー」と「投稿」の関係が 1対多 のときに、
「国」と「投稿」の関係を、「ユーザー」を経由した形で定義できます。

Country.php

public function posts()
{
    return $this->hasManyThrough(Post::class, User::class);
}
>>> Country::first()->posts()->toSql()
=> "select * 
    from `posts` 
    inner join `users` 
    on `users`.`id` = `posts`.`user_id` 
    where `users`.`country_id` = ?"

多対多
( belongsToMany - belongsToMany )

laravel-relation-m-m.png

ユーザーとロールの関係が 多対多 です。 role_userテーブル中間テーブル になります。

関連モデルを取得するとき、中間テーブルの値も取得したい場合、 withPivotメソッド withTimestampsメソッド を利用します。

User.php

public function roles()
{
    return $this->belongsToMany(Role::class)
        ->withPivot('column1', 'column2')
        ->withTimestamps();
}
>>> User::first()->roles()->toSql()
=> "select *
    from `roles`
    inner join `role_user`
    on `roles`.`id` = `role_user`.`role_id`
    where `role_user`.`user_id` = ?"

Role.php

public function users()
{
    return $this->belongsToMany(User::class)
        ->withPivot('column1', 'column2')
        ->withTimestamps();
}
>>> Role::first()->users()->toSql()
=> "select * 
    from `users` 
    inner join `role_user` 
    on `users`.`id` = `role_user`.`user_id` 
    where `role_user`.`role_id` = ?"

中間テーブルのカラムの値によって条件をつけたい場合、 wherePivotメソッド wherePivotInメソッド を利用します。

public function xxxUsers()
{
    return $this->users()
        ->wherePivot('column1', 'xxx')
        ->wherePivotIn('column2', ['xxx', 'yyy']);
}
>>> Role::first()->xxxUsers()->toSql()
=> "select * 
    from `users` 
    inner join `role_user` 
    on `users`.`id` = `role_user`.`user_id` 
    where `role_user`.`role_id` = ? 
    and `role_user`.`column1` = ? 
    and `role_user`.`column2` in (?, ?)"

1対多のポリモーフィック
( morphTo - morphMany )

laravel-relation-p-o-m.png

commentsテーブルcommentable_idpostsテーブルvideosテーブル に紐づきます。

ER図上は線で結ばれていませんが、これは同一カラムで複数テーブルに紐づく外部キーを設定することができないためです。

Comment.php

public function commentable()
{
    return $this->morphTo();
}
>>> Comment::find(1)->commentable_type
=> "App\Models\Post"
>>>
>>> Comment::find(1)->commentable()->toSql()
=> "select * from `posts` where `posts`.`id` = ?"
>>> Comment::find(6)->commentable_type
=> "App\Models\Video"
>>>
>>> Comment::find(6)->commentable()->toSql()
=> "select * from `videos` where `videos`.`id` = ?"

Post.php & Video.php

public function comments()
{
    return $this->morphMany(Comment::class, 'commentable');
}
>>> Post::first()->comments()->toSql()
=> "select * 
    from `comments` 
    where `comments`.`commentable_id` = ? 
    and `comments`.`commentable_id` is not null 
    and `comments`.`commentable_type` = ?"
>>> 
>>> Video::first()->comments()->toSql()
=> "select * 
    from `comments` 
    where `comments`.`commentable_id` = ? 
    and `comments`.`commentable_id` is not null 
    and `comments`.`commentable_type` = ?"

多対多のポリモーフィック
( morphToMany - morphedByMany )

laravel-relation-p-m-m.png

tagsテーブルpostsテーブルvidoesテーブル多対多 の関係で紐づきます。

Tag.php

public function posts()
{
    return $this->morphedByMany(Post::class, 'taggable');
}

public function videos()
{
    return $this->morphedByMany(Video::class, 'taggable');
}
>>> Tag::first()->posts()->toSql()
=> "select * 
    from `posts` 
    inner join `taggables` 
    on `posts`.`id` = `taggables`.`taggable_id` 
    where `taggables`.`tag_id` = ? 
    and `taggables`.`taggable_type` = ?"
>>> 
>>> Tag::first()->videos()->toSql()
=> "select * 
    from `videos` 
    inner join `taggables` 
    on `videos`.`id` = `taggables`.`taggable_id` 
    where `taggables`.`tag_id` = ? 
    and `taggables`.`taggable_type` = ?"

Post.php & Video.php

public function tags()
{
    return $this->morphToMany(Tag::class, 'taggable');
}
>>> Post::first()->tags()->toSql()
=> "select * 
    from `tags` 
    inner join `taggables` 
    on `tags`.`id` = `taggables`.`tag_id` 
    where `taggables`.`taggable_id` = ? 
    and `taggables`.`taggable_type` = ?"
>>> 
>>> Video::first()->tags()->toSql()
=> "select * 
    from `tags` 
    inner join `taggables` 
    on `tags`.`id` = `taggables`.`tag_id` 
    where `taggables`.`taggable_id` = ? 
    and `taggables`.`taggable_type` = ?"
わくわくBank.
フリーランスのエンジニアとして活動してます。ここでは、ソフトウェア開発で必要とされる技術、用語、概念を整理しています。