Laravel 12.19: Use PHP Attribute for a More Elegant Query Builder
If you’ve ever felt your Eloquent models getting cluttered with query logic — multiple scopes, custom methods, and conditionals — then today’s feature in Laravel 12.19 is for you. Instead of overriding newEloquentBuilder()
, you can now point your model to a dedicated builder class using a simple PHP attribute.
Why Introduce a Custom Query Builder?
- Keep Models Lean
Your model should focus on relationships and attributes, not business-specific queries. - Centralize Reusable Logic
All your “wherePublished()” or “recent()” methods live in one place. - Better Organization
Future tweaks happen in a single file, not scattered across many models.
The Old Way: Overriding newEloquentBuilder()
Before 12.19, you’d do something like this:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Builders\ArticleBuilder;
class Article extends Model
{
public function newEloquentBuilder($query)
{
return new ArticleBuilder($query);
}
}
And your builder:
namespace App\Builders;
use Illuminate\Database\Eloquent\Builder;
class ArticleBuilder extends Builder
{
public function whereActive()
{
return $this->where('is_active', true);
}
public function publishedLastWeek()
{
return $this->whereBetween('published_at', [ now()->subWeek(), now() ]);
}
}
Works fine — but it feels boilerplate, right?
The New Way: #[UseEloquentBuilder]
Laravel 12.19 introduces the UseEloquentBuilder
attribute. Simply tag your model:
namespace App\Models;
use App\Builders\ArticleBuilder;
use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder;
use Illuminate\Database\Eloquent\Model;
#[UseEloquentBuilder(ArticleBuilder::class)]
class Article extends Model
{
// No override needed!
}
Now you can chain your custom methods as if they were built‑in:
$articles = Article::query()
->whereActive()
->publishedLastWeek()
->get();
Full Example: Cleaner Implementation
- Create Your Builder
namespace App\Builders;
use Illuminate\Database\Eloquent\Builder;
class ArticleBuilder extends Builder
{
public function whereActive(): static
{
return $this->where('is_active', true);
}
public function recent(int $days = 7): static
{
return $this->whereDate('created_at', '>=', now()->subDays($days));
}
public function byCategory(string $slug): static
{
return $this->whereHas('category', fn($q) => $q->where('slug', $slug));
}
}
2. Apply the Attribute
namespace App\Models;
use App\Builders\ArticleBuilder;
use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder;
use Illuminate\Database\Eloquent\Model;
#[UseEloquentBuilder(ArticleBuilder::class)]
class Article extends Model
{
// fillable, relationships, etc.
}
3. Use in Your Code
$page = Article::query()
->whereActive()
->recent(14)
->byCategory('tech')
->orderByDesc('published_at')
->paginate(10);
Pro Tips
- Don’t Overdo It
Only extract logic you’ll really reuse. - Name Methods Clearly
A method likerecent()
with a parameter is more flexible thanlastWeek()
. - Document Briefly
Add one‑line docblocks to explain non‑obvious queries. - Write Tests
Assert that each builder method returns the correct results. - Combine With Global Scopes
Move global scopes into your custom builder for even cleaner models.
Embrace #[UseEloquentBuilder]
in Laravel 12.19 to keep your models sleek and your query logic organized. Give it a try in your next project, and let your code speak for itself!