【Laravel】ローカルスコープとグローバルスコープについて

Laravel

スコープとは

Laravelに組み込まれている検索機能。
DBのレコードを絞り込む時に、設定条件を予め決めることができます。
つまり、where句による絞り込みが設定できます。

そのため、
・絞り込み忘れで意図しないデータが出てくる
ようなことを避けることができるのがメリットになると思います。

スコープ機能は、ローカルスコープと、グローバルスコープの2種類があります。

ローカルスコープとは

「ローカル」という名前からも想像されるかもしれませんが、必要な時だけ使えるメソッドになります。毎回使わなくてOK、hogeのケースだけ使いたい!ならこちらとなります。

使い方を見てみましょう。
例えば、Userのモデルに投票(vote)カラムが100以上の場合の方を絞り込みたいとします。

お作法としては、scopeHogeのように、scopeをつけて大文字にします。
第1引数は$queryを、第2引数はお好みの引数を入れることができます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 人気のあるユーザーのみを含むようにクエリのスコープを設定
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopePopular($query)
    {
        return $query->where('votes', '>', 100);
    }
}

呼び出すときは、このように記述します。

use App\Models\User;

$users = User::popular()->orderBy('created_at')->get();

こちらもお作法として、scopeは書かず、小文字のメソッドで書きますので注意してください。
これで、whereを書かずにすみます。
また、メソッドチェーン(->)で設定した関数をつなぐことができます。

グローバルスコープとは

対して、グローバルスコープは、特定のモデルのすべて(※)のクエリに絞り込みができます。
※後述しますが、「ここだけは効かせなくない」場合に絞り込みを外すこともできます。融通ききますね…!

スコープクラスを作成する必要がありますが、Laravelはどこで作成してもOKだそうです。

例として、作成日時が20世紀のデータを抽出するようにしてみます。
ファイルはどの場所でもいいのですが、Appディレクトリ直下にScopeディレクトリを作るとします。

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class AncientScope implements Scope
{
    /**
     * 指定のEloquentクエリビルダにスコープを適用
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('created_at', '<', now()->subYears(2000));
    }

実際に適用させるには

static::addGlobalScope(new AncientScope);

のように記述し、bootedメソッドにオーバーライドする形で新しくaddGlobalScope関数の引数にAncientScopeクラスをセットします。

<?php

namespace App\Models;

use App\Scopes\AncientScope;//作ったクラスを呼び出す!
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * モデルの「起動」メソッド
     *
     * @return void
     */
    protected static function booted()
    {
        static::addGlobalScope(new AncientScope);
    }
   // 参考 このようにクロージャを使って書くこともできる!
   // protected static function booted()
   // {
   //     static::addGlobalScope('ancient', function (Builder $builder) {
   //         $builder->where('created_at', '<', now()->subYears(2000));
   //     });
   // }
}

こうすると、User::all()を書くと、実際に狙い通りにwhereで絞り込まれたクエリが実行されます。

もし、この場合だけUser::all()で絞り込まないで欲しい!という場合には

User::withoutGlobalScope(AncientScope::class)->get();

と、withoutGlobalScopeメソッドを書くと、全てのグローバルスコープが適用されなくなりますし、

// グローバルスコープの一部を削除
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

と記述すれば、いらないものだけを指定することもできます。融通効きますね!

まとめ

スコープを活用することで、絞り込みの記述をスッキリでき、抜け漏れを防ぐことにもつなげられます。

追記

他のエンジニアの方とディスカッションして、Laravelのスコープは
・クエリの抜け漏れをなくすことができる
・メソッド名があるので何を取り出そうとしたいのかが分かりやすい
部分は開発者フレンドリーでメリットであると思いました。

ただし、
・定義ではScopeをつけ、呼び出しには不要である、という癖があるので
 IDEの補完が効かない
・初学者がメンバーにいるときにはややわかりにくい

などのデメリットもあり、メリデメを検討して採用していくべきだと思っています。

リーダブルにはこちらに記述があります。

Eloquentの準備 8.x Laravel

コメント

タイトルとURLをコピーしました