
Organising model scopes in Laravel
5 Jun 2022 #development #laravel #php #eloquent
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\Builderuse 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\Builderuse 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.