はじめに
ログインしている user に、 group とのリレーションが存在するか確認したい。
$user->has('groups')->exists()
でうまく動くかと思ったが期待する挙動ではなかった。
has()の挙動がよくわからない。どのような場合に自分が期待する挙動になるのか調べてみた。
ERD
試したこと
1. Modelからhasメソッドを呼び出す
\Auth::user()->has('groups')->exists()
前述した通り、期待する挙動をしない。
sqlを確認すると、Auth::user()のidを考慮していない。
そのため、いずれかのuserレコードにgroupsとのリレーションが存在すればtrueを返してしまう。
select exists(
select * from `users` where exists (
select * from `groups` inner join `user_group` on `groups`.`id` = `user_group`.`group_table_id`
where `users`.`id` = `user_group`.`user_table_id`
)
) as `exists`
2. 中間テーブルを使用しないリレーションで試す
\Auth::user()->has('gmbLocations')->exists();
期待する挙動をしない。
1と同様にModelからhasメソッドを呼び出す。
中間テーブルを挟んでいるのがよくないのかと思って試してみたが1と同じくAuth::user()のidが考慮されない。
select exists(
select * from `users` where exists (
select * from `posts`
where `users`.`id` = `posts`.`user_table_id`
)
) as `exists`
3. QueryBuilderからhas()メソッドを呼び出す
UserModel::where('id', \Auth::id())->has('groups')->exists()
期待通りの挙動をする。
sqlは1にAuth::user()のidを考慮するための where users.id = ?
を追加したものになっている。
Modelのhas()とQueryBuilderのhas()では挙動が異なっていそう。
select exists(
select * from `users` where `users`.`id` = ? and exists (
select * from `groups` inner join `user_group` on `groups`.`id` = `user_group`.`group_table_id`
where `users`.`id` = `user_group`.`user_table_id`
)
) as `exists`
4. hasメソッドを使わずにリレーションメソッドを利用する
\Auth::user()->groups()->exists()
期待通りの挙動をする。
コードもsqlもシンプルでわかりやすい。
select exists(
select * from `groups` inner join `user_group` on `groups`.`id` = `user_group`.`group_table_id`
where `user_group`.`user_table_id` = ?
) as `exists`
まとめ
- QueryBuilderとModelのhas()は挙動が異なるよう
- Modelにリレーションが存在するか確かめるには
Model->relationTable()->exists()
- QueryBuilderにリレーションが存在するか確かめるには
Builder->has('relationTable')->exists()