Articles

November 28, 2025

Quick Tip: A Cleaner Way to Apply Custom Eloquent Builders in Laravel

> There are times when the built-in scope methods (scopeActive, scopeLatest, etc.) aren’t enough — especially when your model starts to accumulate lots of complex query logic. A common way to keep things clean is by creating a custom `Eloquent\Builder` class and telling your model to use it, like this: ```php use Illuminate\Database\Eloquent\Builder; class TicketEloquentBuilder extends Builder { // Your query scopes goes here... } ``` Then in your model: ```php use Illuminate\Database\Eloquent\Model; class Ticket extends Model { /** * Override parent Eloquent\Builder class */ public function newEloquentBuilder($query) { return new TicketEloquentBuilder($query); } } ``` It works — but it feels a little messy. You have to override a method, and it isn’t immediately obvious that the model is using a custom builder. ### 📌 The cleaner (and more elegant) way: PHP Attributes Laravel now supports a much more elegant approach using the `UseEloquentBuilder` attribute. No more method overriding. No cluttered model. Just this: ```php use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder; #[UseEloquentBuilder(TicketEloquentBuilder::class)] class Ticket extends Model { // That's it — no need to override anything 👌🏽 } ``` Much cleaner, more readable, and instantly clear what builder your model is using. ### 🎯 When to use this? This approach shines when: ✔ You’re encapsulating reusable query logic (active, pending, filtered, by role, etc.) ✔ You want a dedicated class for complex query functionality ✔ You value explicit, clean, and attribute-based Laravel features Of course, it’s not required — it’s simply another elegant tool Laravel gives you. Use it when it improves clarity and consistency.

October 10, 2018

Dynamic Query Scopes

Long controller methods and complicated queries often times go hand in hand. One of the common reasons I see is that developers don't utilize dynamic scopes for their models enough (or at all). As a rule of thumb, whenever I find 'where' query calls that needs a callback, I usually do throw that into a scope. I find this most common when I need to do some sort of constraint on the relationship. Throwing your query calls into a scope abstracts the actual implementation, and you can just re-use the scope whenever you want. ## Example: We are developing a medical service app and we are tasked to get all the doctors having cases assigned to the current authenticated doctor and include a count of cases assigned by each doctor. Our original implementation goes like this.. use App\Models\Doctor; use Illuminate\Support\Facades\Auth; ... public function index() { return Doctor::whereHas('cases', function ($query) { $query->where('assignee_id', Auth::id()); }) ->withCount(['cases' => function ($query) { $query->where('assignee_id', Auth::id()); }]) ->get(); } This implementation isn't bad at all. However, reading this takes a while to process what this code is trying to accomplish. Also, if we are going to use the scope on other places aside from the `index()` method, it makes more sense to just extract it to a scope. Let's see how we can refactor this.. use App\Models\Doctor; use Illuminate\Support\Facades\Auth; ... public function index() { return Doctor::onlyWithCasesAssignedTo(Auth::user()) ->withCountOfCasesAssignedTo(Auth::user()) ->get(); } // Then in our Doctor model, we add couple of query scopes.. use Illuminate\Database\Eloquent\Builder; ... public function scopeOnlyWithCasesAssignedTo(Builder $query, Doctor $doctor) { return $query->whereHas('cases', function ($query) { $query->where('assignee_id', $doctor->id); }); } public function scopeWithCountOfCasesAssignedTo(Builder $query, Doctor $doctor) { return $query->withCount(['cases' => function ($query) { $query->where('assignee_id', Auth::id()); }]); } And there you go! We've successfully extracted the complicated queries from our controller to their dedicated query scope methods. Cheers!

Marvin Quezon · Copyright © 2025 · Privacy · Sitemap