Laravel Scopes

ehsan hosseiny
3 min readDec 19, 2020

We learn how to have simpler and cleaner queries

Most of the time there is a series of Eloquent queries in which we always have to comply with certain condition and restrictions. For example:

Post::where(‘status’, ‘published’)->get();

Post::where(‘status’, ‘published’)->where(‘category’, 9)->get();

Post::where(‘status’, ‘published’)->where(‘id’, 429)->first();

As we can see, the condition where(‘status’, ‘published’) is repeated in all our queries. One principle in software programming and development is to “avoid duplication as much as possible.

This becomes a problem when we want to change the status and published titles, to make changes we have to find all the queries in the program that are not optimal. To solve the above two problems, Laravel has provided us with an interesting solution called Query Scope, which we want to get acquainted with in this post.

What is Query Scope ?

With query scoops we can have shorter, more meaningful and beautiful queries in Eloquent. Consider the query at the beginning of the post. With scopes we can write it as follows:

Post::active()->get();

We can even make the terms we want apply without writing the conditions (for example, by writing Post::all()).

We have two types of scope. Global Scope and Local Scope

Global Scope

This type of scope applies our constraints globally to all eloquent queries. If you have used Laravel Soft Delete, it is interesting to note that this feature actually imposes a global limit on all queries.

Writing Global Scope

<?php

namespace App\Scopes;

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

class Published implements Scope
{
public function apply(Builder $builder, Model $model)
{
// …
}
}

The Class should implement “ Illuminate\Database\Eloquent\Scope” interface. This interface has a method called apply with the parameters that must be implemented by the class. In the apply method we can write our desired conditions and restrictions:

public function apply(Builder $builder, Model $model)
{
return $builder->where(‘status’, ‘published’);
}

The next step is to identify this class to the model. We open our model. We need to rewrite the booted method of the model. I write this method and in it we use the “addGlobalScope” method to identify the scope class to the model:

<?php

namespace App\Models;

// …
use App\Scopes\Published;

class Post extends Model
{
// …

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

From now on, every query we write will automatically include a defined constraint. For example if we write:

Post::all();

Its SQL query will be as follows:

select * from `post` where `status` = “published”

Unnamed Global Scoops

Instead of writing a special class, we can create a global scope in the model itself. This is possible with closures. Rewrite the booted method and addGlobalScope as follows:

protected static function booted()
{
static::addGlobalScope(‘published’, function(Builder $builder) {
$builder->where(‘status’, ‘published’);
});
}

In this method, the first argument of the addGlobalScope method is our preferred name for the scope.

Queries exempt from global scope

If we don`t want to apply global scoping to a specific query, we can use the withoutGlobalScope method in the query and pass the scope class path we want:

Post::withoutGlobalScope(App\Scopes\Published::class)->get();

If our global scope is a closure, just pass the name of the scope:

Post::withoutGlobalScope(‘published’)->get();

If we want to delete multiple scopes, we use the method withoutGlobalScopes

Post::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();

If we do not pass anything to this method, all global scope for this query will be deleted:

Post::withoutGlobalScopes()->get();

Local Scope

If some (but not all) of our queries have conditions and restrictions that are repetitive or complex in appearance, we can use local scope.

We write local scopes in the model itself. Suppose the name of the scope we are considering is published. To define a local scope, we need to write a method called scopePublished into the model.

public function scopePublished($query)
{
return $query->where(‘status’, ‘published’);
}

Even we can add other scopes:

public function scopePopular($query)
{
return $query->where(‘visits’, ‘>’, 1000);
}

To use the scope we have written, just mention their names in the queries as follows (without the scope prefix):

Post::popular()->published()->orderBy(‘created_at’)->get();

As we can see, our query became shorter and more readable
Without using scopes, this query will look like this:

Post::where(‘visits’, ‘>’, 1000)->where(‘status’, ‘published’)->orderBy(‘created_at’)->get();

In the above query, two where can be seen. If we want to have orWhere for these scopes, we use the following method:Post::popular()->orWhere->published()->get();

Dynamic local scopes

These types of scopes are used when we want to pass the argument scope to the query when writing a query. Here is how to write these scopes. When defining the scope, it is enough to define the desired parameter:

public function scopeStatus($query, $status)
{
return $query->where(‘status’, $status);
}

And we use them as follows:

Post::status(‘published’)->get();

--

--