🙆♀️
[Laravel] EloquentリレーションのBelongsToMany(多対多)を使う過程でキーが上書きされる件の対処
前提
テーブル構成
-
users
テーブル -
posts
テーブル -
post_user
テーブル(中間テーブル)-
id
カラムを持っている
-
事象
public function posts(): BelongsToMany
{
return $this->belongsToMany(Post::class);
}
のとき、
User::posts()->get();
のようにリレーションを呼ぶと
posts
.id
がpost_user
.id
で上書きされてしまう事象が起きる。
※ withPivot()などで取得カラムを指定しても発生。
原因
(Eloquent)BuilderのbelongsToMany()
メソッドではinner join
を内部的に行なっているため。
※ 詳細
dd($posts->toSql());
の結果が
select `posts`.`*` inner join `post_user` on ...
であり、join
の際に同一名のカラムはMySQL側で上書きされるため。
対応
対応①: Laravelライクな対応
JOINをさせなければ良いので、Eloquentの通常的なリレーションを呼べば発生しないはず。
例
$user->postUser()->post()->get();
-> おそらくN+1個のクエリが発行されてしまう。
select * from post_user where user_id = X;
select * from posts where id = 1;
...
※ 本記事は対応②を実施した備忘録なので、今回は未検証です。
対応②: belongsToManyっぽいメソッドを作る
これを避けるために、なんとかデフォルトのbelongsToManyっぽくふるまう関数を作成することで対応した。
public function posts(): Builder
{
return Post::join('post_user', 'posr_user.post_id', '=', 'posts.id')
->select('posts.*', 'post_user.sort')
->where('post_user.user_id', $this->id)
->orderBy('post_user.sort', 'asc');
}
メリット
- 一応Post.php側のモデルクラスインスタンスに、belongsToMany withPivotを使ったときと同じ値の入り方をしてくれる。
- 返り値はBuilderなので、他のscopeなどとチェーンできる。
- クエリの発行数が抑えられる(はず。なんか最近N+1問題が解消しつつあるみたいな情報を見た気もするので違ったら教えてください。)
デメリット
- これ自体がスコープ関数ではないので最初に使わなければならない。scopeにするなら
Post.php
側に作って、User
側からはそれを呼ぶだけになるが、どちらにせよ依存の謎矢印問題が起きるし、変なstatic関数がPost
側にできる方が気持ち悪いので今回はUser
側においた。 - 変更に弱い。
- PostテーブルのEloquent Builderを作成する関数がUser側にできてしまいやや密結合的。
一旦これで乗り切ろうと思います。何かいい方法があれば教えてください。
Discussion