Leveraging Global Query Scopes in Laravel 9 to Effortless Filter Data

Published Jan 1, 2024

Query Scopes are reusable methods that can be used to limit the data returned by a database query. They are used to handle repetitive query logic and can be shared among multiple Eloquent models. Query scopes allow you to define common database filters and apply them to different parts of your application, making it easy to fetch data based on specific criteria.

For example, if you have a blog and you want to fetch all the published articles, you can create a scope in your Article model to only return the published articles. You can then use this scope in your models, controllers or anywhere else in your application where you want to fetch the published articles.

Using query scopes in Laravel 9 is incredibly easy. Here is an example of how to add a scope to your model:

Step one is to create the query scope class. The Laravel documentation prescribes creating it in the App\Models\Scopes directory so create the directory if needed and add the scope class.

terminal
<?php

namespace App\Models\Scopes;

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

class PublishedScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('publish_at', '<', now());
    }
}

The next step is to add the query scope to the model. This can be done by adding the scope class you created as a global scope in the booted function of the model.

terminal
<?php

namespace App\Models;

use App\Models\Scopes\PublishedScope;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'title',
        'body',
        'publish_at',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
        'publish_at' => 'datetime',
    ];

    protected static function booted()
    {
        static::addGlobalScope(new PublishedScope);
    }
}

Another option would be to create an anonymous global query scope:

terminal
protected static function booted()
{
    static::addGlobalScope('published', function (Builder $builder) {
        $builder->where('publish_at', '<', now());
    });
}

Now whenever you call the model it will filter the data and ensure that no articles that have a publish_at date that is after the current date are shown.

This is great in most scenarios but sometimes you my need to access the data without the filter. If you want to call data from the model without the global scope you can just specify which scopes you want to exclude from the call.

terminal
$articles = Article::withoutGlobalScope(PublishedScope::class)->get();

Query scopes in Laravel are a powerful tool for handling repetitive query logic and making your data management tasks easier and more efficient. By creating reusable scopes, you can simplify your code base and ensure consistency throughout your application. Try incorporating query scopes into your next Laravel projects and see what a difference they can make!