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:

namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
public function scopePopular(Builder $query): Builder
{
return $query->where('votes', '>', 100);
}
 
public function scopeActive(Builder $query): Builder
{
return $query->where('active', 1);
}
}

Once defined, you can use them like this:

use App\Models\User;
 
$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:

namespace App\Builders;
 
use Illuminate\Database\Eloquent\Builder;
 
class UserBuilder extends Builder
{
public function popular(): self
{
return $this->where('votes', '>', 100);
}
 
public function active(): self
{
return $this->where('active', 1);
}
}

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:

namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
public function newEloquentBuilder($query)
{
return new UserBuilder($query);
}
}

Usage is still exactly the same:

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

Thanks to this article for the idea.