#database #development #laravel #php

Scopes in Eloquent are a great way to define common sets of query constraints that you may easily re-use throughout your application.

They are usually defined inside the Model class:

 1namespace App\Models;
 2
 3use Illuminate\Database\Eloquent\Builder
 4use Illuminate\Database\Eloquent\Model;
 5
 6class User extends Model
 7{
 8    public function scopePopular(Builder $query): Builder
 9    {
10        return $query->where('votes', '>', 100);
11    }
12
13    public function scopeActive(Builder $query): Builder
14    {
15        return $query->where('active', 1);
16    }
17}

Once defined, you can use them like this:

1use App\Models\User;
2
3$users = User::popular()->active()->orderBy('created_at')->get();

One drawback is that sooner than later, you will get a lot of scopes in your model which clutters the code. It would be nice to have them in a separate class.

To achieve this, we'll create a custom Eloquent Builder. To create a custom builder, you need to create the appropriate class and it should extend Illuminate\Database\Eloquent\Builder. A custom builder should concern one, and only one, model.

In our example, the builder will be defined like this:

 1namespace App\Builders;
 2
 3use Illuminate\Database\Eloquent\Builder;
 4
 5class UserBuilder extends Builder
 6{
 7    public function popular(): self
 8    {
 9        return $this->where('votes', '>', 100);
10    }
11
12    public function active(): self
13    {
14        return $this->where('active', 1);
15    }
16}

What you can see is that you no longer need the scope prefix neither do you need the $query parameter as the function argument.

To use this in your model, you need to override the newEloquentBuilder method:

 1namespace App\Models;
 2
 3use Illuminate\Database\Eloquent\Builder
 4use Illuminate\Database\Eloquent\Model;
 5
 6class User extends Model
 7{
 8    public function newEloquentBuilder($query)
 9    {
10        return new UserBuilder($query);
11    }
12}

Usage is still exactly the same:

1use App\Models\User;
2
3$users = User::popular()->active()->orderBy('created_at')->get();

Thanks to this article for the idea.