bunty's blog

ググったこととか勉強したことのメモ

Eloquent の scope で join をする際に、重複してテーブルを join させないようにする

今回の内容はスコープに限った話ではないのですが、まあスコープをよく使うのでそれについて。

laravel.com

特定の条件で絞り込みを行う際に、スコープに切り出して使いまわせるようにすることがあると思います。

その際にそのスコープの中で 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);