Laravel Advent Calendar 2022 2日目の投稿です
ローカルスコープについて
まずローカルスコープとは?
アプリケーション全体で簡単に再利用できる、共通のクエリ制約を定義できます。たとえば、「人気がある(popular)」と思われるすべてのユーザーを頻繁に取得する必要があるとしましょう。スコープを定義するには、Eloquentモデルメソッドの前にscopeを付けます。
以前にもローカルスコープについて記事を書いています。
具体的にはこんなやつです
class User extends Model
{
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
}
ローカルスコープを利用する時はscopeを除いて小文字から始めます。
同じ条件を複数回使うようであれば記述量が少なくなってとても便利です!
$users = User::query()
->popular()
->where('age', '>', 20)
->orderBy('created_at')
->get();
ただデメリットもあるなと思っています。
- ローカルスコープのメソッドを積み重ねるとModelが肥大化していく
- VSCodeなどで補完が効かない
- メソッドをクリックして定義元に飛べない
- ソースコード内を検索しても完全一致でヒットしない(scopeが先頭についているため)
このほかにも個人的には、そういうメソッドがLaravelには用意されていると思ってググりにいったこともあります。
そこで、カスタムクエリビルダーを作成してこれらのデメリットを解消する方法をご紹介します!
カスタムクエリビルダーの作成
まずは、EloquentのBuilderクラスを承継してUserBuilderクラスを新たに作成し、ローカルスコープで定義していたメソッドをこちらに移します。
namespace App\Builders;
use Illuminate\Database\Eloquent\Builder;
class UserBuilder extends Builder
{
public function popular(): static
{
return $this->where('votes', '>', 100);
}
}
そしてUserクラスにてModelクラスで定義されているquery() と newEloquentBuilder() をオーバーライドしてあげます。
use App\Builders\UserBuilder;
class User extends Model
{
public static function query(): UserBuilder
{
return parent::query();
}
public function newEloquentBuilder($query): UserBuilder
{
return new UserBuilder($query);
}
}
これで準備は完了です!
$users = User::query()
->popular()
->where('age', '>', 20)
->orderBy('created_at')
->get();
このようにコードを書けばModelの肥大化を防げて、コード補完も効いて、定義元にも飛べるようになりました。
よかったら使ってみてください!
laravel-ide-helper
のライブラリを入れているとうまく定義元にも飛べなかったのでご注意を!