エンジニアのひよこ_level10

【毎日更新!】新卒3年目エンジニアブログです! プログラムだけじゃなく、マネジメントとかも書いていきたい!

【Laravel】whereIn('id', [])で全件検索されないのはなぜ?(コードリーディング)【608日目】

whereInの第二引数が空

function getUsersByIds(array $ids)
{
    return User::whereIn('id', $ids)->get();
}

こんな関数を書いたときに、$idsがもし []だったら?
これでwhereが無いからって全件検索されたら、とんでもない障害に・・・

ですが、Laravelさん、ちゃんとそこはケアしてくれてます。

全件検索じゃないならどうなる?

# php artisan tinker
>>> \App\User::whereIn('id', [])->toSql();
=> "select * from `users` where 0 = 1"

where 0 = 1になってますね。

コードを追ってみよう

注意: この先のコードは、Laravel 5.7のコードになります。

whereInで何をする?

クエリビルダのwhereIn関数の一部を紹介すると、

Illuminate/Database/Query/Builder.php

$this->wheres[] = compact('type', 'column', 'values', 'boolean');

foreach ($values as $value) {
    if (! $value instanceof Expression) {
        $this->addBinding($value, 'where');
    }
}

この $this->wheresに値を入れるのと、 $this->bindingsに値を入れる作業がされます。
今回バインドされる値が無いので、 $this->whereに着目すると、

array:1 [
  0 => array:4 [
    "type" => "In"
    "column" => "id"
    "values" => []
    "boolean" => "and"
  ]
]

このような値が入ります。

だからどうしたというのは、実際にget関数などで、クエリ生成をするときに明らかになります。

getとかで何をする?

get関数などでクエリを生成するときですが、結論から言うと、

全件検索されるのは、
$this->whereが空のときなので、
$this->whereの中身があるから、全件検索されない

実際のコードは以下にあります。

Illuminate/Database/Query/Grammars/Grammar.php

protected function compileWheres(Builder $query)
{
    if (is_null($query->wheres)) {
        return '';
    }

    if (count($sql = $this->compileWheresToArray($query)) > 0) {
        return $this->concatenateWhereClauses($query, $sql);
    }

    return '';
}

if文で、 is_null($query->wheres)が書かれていますね。空じゃなければ、where句の生成がされます。

where 0 = 1はどこから?

では、 where 0 = 1はどっから来たんだと。

その秘密は、whereIn関数の解決にあります。

Illuminate/Database/Query/Grammars/Grammar.php

protected function whereIn(Builder $query, $where)
{
    if (! empty($where['values'])) {
        return $this->wrap($where['column']).' in ('.$this->parameterize($where['values']).')';
    }

    return '0 = 1';
}

さて、少し前の変数を思い出しましょう。

array:1 [
  0 => array:4 [
    "type" => "In"
    "column" => "id"
    "values" => []
    "boolean" => "and"
  ]
]

if文の中身は、 (! empty($where['values']))なので、一番下にある、 return '0 = 1'にたどり着くわけですね。

上手く出来てますね(^q^)

最後に

今回の例を見て、『へー』で終わらせるのもいいですが、
今後クエリビルダを使うときには、

Illuminate/Database/Query/Grammars/Grammar.php
Illuminate/Database/Query/Builder.php

この2ファイルを読むと、詰まったときに幸せになれるかもしれません。
あと、Laravelのコードは結構きれいに書かれているので、今後コードを書くときの参考になるかもしれませんよ!

コードリーディング、おすすめです!