Eloquentのリレーションを定義する方法について解説します。
「1対1」「1対多」「多対多」など関係に応じた定義方法をER図とともに解説します。
なお、Eloquentのリレーションについて以下3回に渡って解説しています。他の記事も参考にしてください。
- Eloquentのリレーション活用方法【関連の定義】 ← 今回
- Eloquentのリレーション活用方法【関連の取得】
- Eloquentのリレーション活用方法【紐付けの設定と解除】
動作確認に利用したリポジトリです。
https://github.com/raku-raku/laravel_eloquent_practice
目次
1対1
( hasOne – belongsTo )
「ユーザー」と「ユーザーが所有する電話」の関係が 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 )
ユーザーが複数の投稿を持つ関係を以下のように定義できます。
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 )
「国」と「ユーザー」の関係が 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 )
ユーザーとロールの関係が 多対多
です。 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 )
commentsテーブル
の commentable_id
が postsテーブル
と 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 )
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` = ?"