Now, let's talk about repeating queries in Eloquent. For example, you have a where()
condition, and you want to repeat the same condition in other parts of your application and other Controllers, in other classes, and so on.
$users = User::whereNotNull('email_verified_at')->get(); foreach ($users as $user) { dump($user->id . ': ' . $user->name);}
You may want to extract that condition into some function, which you would be able to reference in a shorter way. For that, you can use scopes.
You can define that where()
condition and put that in a Model in a function with the prefix scope
and then scope name.
For example, we can call it scopeVerified()
. The parameter is the Eloquent builder, and you provide the where statement in the function.
app/Models/User.php:
use Illuminate\Database\Eloquent\Builder; class User extends Authenticatable{ // ... public function scopeVerified(Builder $query): Builder { return $query->whereNotNull('email_verified_at'); }}
To use this scope, instead of the where we call the scope name, which in this case is verified()
.
$users = User::whereNotNull('email_verified_at')->get(); $users = User::verified()->get(); foreach ($users as $user) { dump($user->id . ': ' . $user->name);}
I have three users in the database, but only two are with verified email.
The result after executing the query is two users with the verified email:
Now, you can use the verified()
scope in other application parts.
Scopes may have parameters, and such scopes are called dynamic scopes. For example, a user has a type. In the scope, you can add parameters as many as you need.
app/Models/User.php:
$users = User::verified()->typeOf('admin')->get(); foreach ($users as $user) { dump($user->id . ': ' . $user->name);}
Again, in the database, I have three users, but only one is with the type of admin and with verified email.
The result is only the first user:
These scopes are called local scopes, which you call locally from your controller or wherever.
Global scopes are applied automatically globally on all the Models. There are two ways to use global scopes.
The first option is called Anonymous Global Scopes, where you add global scope in the Model inside the booted()
method.
app/Models/User.php:
use Illuminate\Database\Eloquent\Builder; class User extends Authenticatable{ // ... protected static function booted(): void { static::addGlobalScope('verified', function (Builder $builder) { $builder->whereNotNull('email_verified_at'); }); }}
If we get all the users, the global scope will be applied, and only the verified users will be shown.
$users = User::all(); foreach ($users as $user) { dump($user->id . ': ' . $user->name);}
The second option is to generate a global using the make:scope
artisan command and add the query in the apply()
method of the generated scope class.
php artisan make:scope VerifiedScope
Scopes are generated in the app/Models/Scopes
folder. We can add the where statement to the VerifiedScope
class.
app/Models/Scopes/VerifiedScope.php:
class VerifiedScope implements Scope{ public function apply(Builder $builder, Model $model): void { $builder->whereNotNull('email_verified_at'); }}
Next, we must apply the global scope using the ScopedBy
attribute on the Model.
app/Models/User.php:
use App\Models\Scopes\VerifiedScope;use Illuminate\Database\Eloquent\Attributes\ScopedBy; #[ScopedBy([VerifiedScope::class])] class User extends Authenticatable{ // ...}
Or, you can register it in the Model's booted()
method.
app/Models/User.php:
use App\Models\Scopes\VerifiedScope; class User extends Authenticatable{ // ... protected static function booted(): void { static::addGlobalScope(new VerifiedScope); }}
The result is the same two users with verified email:
In some cases, you might need to remove the global scope from the query. You can use the withoutGlobalScope()
method in the Eloquent query. Without any parameters, it will remove all global scopes.
$users = User::withoutGlobalScopes()->get();
You can provide the global scope which you need to be removed.
use App\Models\Scopes\VerifiedScope; $users = User::withoutGlobalScope(VerifiedScope::class)->get();
Or, provide only a name.
$users = User::withoutGlobalScope('verified')->get();
In the array, you can provide multiple scopes to be removed.
User::withoutGlobalScopes([ FirstScope::class, SecondScope::class])->get();
You can use scopes for repeating queries, but be specifically careful with global scopes because, in the future, some other developers may not even know that the scope exists and would have unexpected results for their queries.