Eloquent の scope で join をする際に、重複してテーブルを join させないようにする
今回の内容はスコープに限った話ではないのですが、まあスコープをよく使うのでそれについて。
特定の条件で絞り込みを行う際に、スコープに切り出して使いまわせるようにすることがあると思います。
その際にそのスコープの中で join させて、join 先のカラムのデータを where 句に指定することがあるのですが、もし同じテーブルをそのまま join させてしまうとエラーになってしまいます。
<?php // 省略 public function scopeWhereSomething1(Builder $query): Builder { return $query->join('user_profiles', 'users.id', 'user_profiles.user_id') ->where('user_profiles.first_name', 'hoge'); } public function scopeWhereSomething2(Builder $query): Builder { return $query->join('user_profiles', 'users.id', 'user_profiles.user_id') ->where('user_profiles.family_name', 'fuga'); } // 同じテーブルを join してるからエラーになる User::whereSomething1()->whereSomething2()->get();
回避するには、scope の中では join をしないようにする、テーブルに別名をつける、すでに join していたら join をしないようにするとかかなと思います。
join しているテーブルを一覧を確認するには、$query の joins を確認すればわかります。
そのため、こんな感じで join 済のテーブルは一覧で抜き出せます。
$ php artisan tinker >>> $tables = array_column(User::join('user_profiles', 'users.id', 'user_profiles.user_id')->where('users.id', 1)->getQuery()->joins, 'table') => [ "user_profiles", ] >>> in_array('user_profiles', $tables, true) => true
スコープの方で join していない場合にのみ join するようにすれば、同じテーブルを join する場合にもエラーにもならず別名をつける必要がなくなりました。
<?php public function scopeWhereSomething1(Builder $query): Builder { $tables = array_column($query->getQuery()->joins, 'table'); if (!in_array('user_profiles', $tables, true)) { $query->join('user_profiles', 'users.id', 'user_profiles.user_id'); } return $query->where('user_profiles.first_name', 'hoge'); }
別名をつける場合には、そのまま join の際に as ~ で別名を付ければ OK です。
その場合には where 句などで別名を指定する必要があります。
$query->join('table as table2', 'users.id', 'table2.user_id') ->where('table2.user_id', 1);