LoginSignup
4
4

More than 5 years have passed since last update.

[Laravel Eloquent] クエリスコープのreturnに0やfalseを返すように記述してもBuilderインスタンスが返ってきてしまう

Last updated at Posted at 2017-04-11

前提

  • Laravel5.4
    参考情報は1~2年以上前なので、おそらく5.1LTSなど古いバージョンでも同じ仕様だと思われる。(未確認)

現象

スコープはいつもクエリビルダインスタンスを返します。

上の記述は日本語版ドキュメントの記載である。
しかし、返り値に任意の値を渡せばクエリビルダインスタンス以外の値を返すことができる。

たとえばこういうことができる
public function scopeRenderHogeHogeJson($query)
{
    $json = $query->get();

    /** JSONを加工するいろいろな処理 */

    return $json;
}

コントローラーやBladeでの記載量を気軽に減らすことができるため多用していたが、
count()を使ったクエリスコープが予期せぬ動きをした。

アンチパターン的クエリスコープの記述
public function scopeCountNew($query)
{
    return $query
        ->whereBetween('created_at', [Carbon::today()->subDay(), Carbon::now()])
        ->count();
}
アンチパターン的Blade側の記述
    @if($query->countNew())
        新着{{$query->countNew()}}
    @endif

上記の処理、count()の件数が1件以上であれば期待通り動くが、
0件のときはErrorEXceptionが発生する。

結論から言うと

nickcl commented on 11 Aug 2014
I can't think of any use case where you'd want to return anything (other than the builder object) from a scope.

引用元:https://github.com/laravel/framework/issues/5405

つまり、scopeで$query以外の値を返してはいけない。

先の例では、あくまでクエリスコープではクエリビルダを返すべきであって、

正しいクエリスコープの書き方
public function scopeNew($query)
{
    return $query
        ->whereBetween('created_at', [Carbon::today()->subDay(), Carbon::now()]);
}
正しいBlade側の記述
    @if($query->new()->count()) // そもそもBlade内に条件式を書くべきでないという説もある
        新着{{$query->new()->count()}}
    @endif

という実装をすべきであったのだ。

Builderインスタンス以外の返り値を記述しても期待通りの値が帰ってくる例

public function scopeRet1
{
    return 1;
}

public function scopeRetTrue
{
    return true;
}

public function scopeRetObj
{
    return collect([1, 2, 3]);
}

//int(1)
Hoge::ret1()
//true
Hoge::retTrue()
//Collection[1, 2, 3]
Hoge::retObj()

あらゆるパターンを試したわけではないが、だいたいの値は問題なく返ってくる。

期待通りの値が返ってこない例

public function scopeRet0
{
    return 0;
}

public function scopeRetFalse
{
    return false;
}

public function scopeRetNull
{
    return null;
}

//Builder
Hoge::ret0()
//Builder
Hoge::retFalse()
//Builder
Hoge::retNull()

PHP的に偽と評価される値を返すことはできない。
スコープの値が偽の場合、自動的にBuilderインスタンスがセットされる。

おわりに

フレームワークの「お作法」を無視すると痛い目見ます。
とはいえ、scopeErrorMessage()などを定数代わりに使うとわりと便利なので、小さいプロジェクトだと使っちゃうかも。

参考情報

4
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4