前提
- 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();
}
@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()]);
}
@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()
などを定数代わりに使うとわりと便利なので、小さいプロジェクトだと使っちゃうかも。