Eloquent Query Scopes & Custom Builders – Optimizing Database Queries
Efficient database querying is crucial for performance in Laravel applications, especially as your project scales. Laravel's Eloquent Query Scopes and Custom Builders provide a way to reuse query logic, making your code more modular and maintainable. In this blog, we’ll explore how to optimize database queries using these techniques.
1. Understanding Query Scopes in Laravel
Eloquent Query Scopes allow you to define common query constraints once and reuse them throughout your application.
1.1 Defining a Local Scope
A local scope is defined within an Eloquent model and used for reusable query conditions.
Example: Filtering Active Users
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function scopeActive($query)
{
return $query->where('status', 'active');
}
}
1.2 Using a Local Scope in Queries
Now, we can filter active users easily:
$activeUsers = User::active()->get();
You can also chain scopes for more refined queries:
$verifiedUsers = User::active()->whereNotNull('email_verified_at')->get();
1.3 Defining a Scope with Parameters
Scopes can also accept parameters:
public function scopeRole($query, $role)
{
return $query->where('role', $role);
}
Usage:
$admins = User::role('admin')->get();
2. Using Global Scopes
Unlike local scopes, Global Scopes are automatically applied to all queries for a model.
2.1 Creating a Global Scope
Define a class implementing Scope:
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('status', 'active');
}
}
2.2 Applying a Global Scope to a Model
Register the scope in your model’s booted() method:
Recommended by LinkedIn
namespace App\Models;
use App\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected static function booted()
{
static::addGlobalScope(new ActiveScope);
}
}
Now, all User queries automatically include where('status', 'active').
To disable a global scope:
$users = User::withoutGlobalScope(ActiveScope::class)->get();
3. Creating a Custom Builder Class
For complex queries, a Custom Builder Class helps maintain cleaner code.
3.1 Creating the Custom Builder
Create a builder class in app/Builders/UserBuilder.php:
namespace App\Builders;
use Illuminate\Database\Eloquent\Builder;
class UserBuilder extends Builder
{
public function active()
{
return $this->where('status', 'active');
}
public function role($role)
{
return $this->where('role', $role);
}
}
3.2 Using the Custom Builder in a Model
Modify the User model to return our custom builder:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Builders\UserBuilder;
class User extends Model
{
public function newEloquentBuilder($query): UserBuilder
{
return new UserBuilder($query);
}
}
3.3 Using the Custom Builder in Queries
$users = User::query()->active()->role('admin')->get();
By encapsulating complex logic within a dedicated builder class, queries remain clean, reusable, and more maintainable.
4. When to Use Scopes vs. Custom Builders?
Use query scopes for reusable filtering conditions and custom builders when dealing with complex queries that require advanced chaining logic.
5. Conclusion
Optimizing database queries is essential for scalable Laravel applications. Query Scopes allow for concise and reusable query logic, while Custom Builders provide a structured way to handle complex queries. By mastering these techniques, you can write cleaner, more efficient code that enhances performance and maintainability.
Try implementing query scopes and custom builders in your next Laravel project and experience the difference! 🚀