Writing Your Own Eloquent Builder in Laravel

Asfia AimanAsfia Aiman
5 min read

Creating a custom Eloquent Builder is a powerful way to extend Laravel's query capabilities and encapsulate complex query logic. This approach helps keep your code clean and maintainable while providing a fluent interface for your specific application needs.

Why Create a Custom Builder?

A custom Eloquent Builder allows you to:

  • Encapsulate common query patterns

  • Create domain-specific query methods

  • Keep your models clean by moving query logic to a dedicated class

  • Provide a consistent API for querying your models

  • Reduce code duplication across controllers and services

  • Improve testability by isolating query logic

When Should You Create a Custom Builder?

Knowing when to implement a custom builder is crucial for maintaining efficient code architecture. Consider creating a custom builder when:

  1. Your model has numerous scopes: If you find your model cluttered with many scope methods (scopePublished(), scopeActive(), etc.), it's a sign to move this logic to a dedicated builder.

  2. Query logic is repeated across controllers: When you notice the same query patterns being used in multiple controllers or services, a custom builder can centralize this logic.

  3. Domain-specific querying needs: When your application requires specialized filtering or sorting logic that's specific to your business domain.

  4. Complex conditional queries: If you're building complex conditional queries with many clauses, a custom builder can make this more manageable.

  5. Your team works on different parts of the application: Custom builders provide a clear API for all team members to use, regardless of which part of the application they're working on.

Signs It's Time to Implement a Custom Builder

How do you know when your application has reached the point where a custom builder would be beneficial? Look for these indicators:

  • Models becoming bloated: When your model class grows too large due to numerous query-related methods.

  • Repetitive query patterns: When you see the same query conditions used in multiple places.

  • Lack of query standardization: When different developers write similar queries in different ways.

  • Need for application-wide filtering: When you need consistent filtering across various parts of your application.

  • Testing difficulties: When testing complex queries becomes challenging due to their location within controllers or other classes.

Creating a Custom Builder

To create your own Eloquent Builder, follow these steps:

First, create a new class that extends the base Eloquent Builder:

<?php

namespace App\Builders;

use Illuminate\Database\Eloquent\Builder;

class PostBuilder extends Builder
{
    public function published()
    {
        return $this->where('is_published', true);
    }

    public function popular()
    {
        return $this->where('views', '>', 1000);
    }

    public function recentFirst()
    {
        return $this->orderBy('created_at', 'desc');
    }
}

Binding Your Builder to a Model

Next, you need to bind this builder to your model. This requires overriding the newEloquentBuilder method in your model:

<?php

namespace App\Models;

use App\Models\Builders\PostBuilder;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Create a new Eloquent builder for the model.
     *
     * @param \Illuminate\Database\Query\Builder $query
     * @return \App\Builders\PostBuilder
     */
    public function newEloquentBuilder($query)
    {
        return new PostBuilder($query);
    }
}

Using Your Custom Builder

Once you've set up your custom builder, you can use your new methods directly on your model:

// Get all published posts
$posts = Post::published()->get();

// Get popular posts ordered by most recent
$popularPosts = Post::popular()->recentFirst()->get();

// Chain with standard Eloquent methods
$recentPopularPosts = Post::popular()
    ->where('category', 'technology')
    ->whereBetween('created_at', [now()->subDays(7), now()])
    ->recentFirst()
    ->get();

Real-World Example: When to Implement

Let's consider a practical scenario to illustrate when a custom builder becomes necessary:

Imagine you're building an e-commerce platform. Initially, your Product the model has a few simple scopes:

class Product extends Model
{
    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopeInStock($query)
    {
        return $query->where('stock', '>', 0);
    }
}

As your application grows, you add more complex filtering requirements:

  • Products on sale

  • Featured products

  • Products in specific price ranges

  • Products with certain ratings

  • Filtering by multiple attributes simultaneously

Now your controllers contain complex query chains:

$products = Product::active()
    ->inStock()
    ->where('is_on_sale', true)
    ->where('price', '<', 100)
    ->whereHas('ratings', function ($query) {
        $query->where('average', '>=', 4);
    })
    ->orderBy('price')
    ->get();

This is the perfect time to implement a custom builder. Your code is showing signs of:

  • Repetitive query patterns

  • Complex conditional queries

  • Domain-specific filtering needs

Advanced Features

Method Chaining

The power of custom builders becomes evident when chaining methods together. Each method returns $this, allowing for a fluent interface:

public function withHighEngagement()
{
    return $this->where('comments_count', '>', 10)
                ->orWhere('likes_count', '>', 50);
}

Scope Reusability

Custom builders make your scopes reusable across different queries:

// In a controller
$trendingPosts = Post::published()
                     ->popular()
                     ->withHighEngagement()
                     ->recentFirst()
                     ->take(5)
                     ->get();

Best Practices

  1. Keep methods focused - Each method should have a single responsibility

  2. Use descriptive names - Method names should clearly describe what they do

  3. Document your methods - Add DocBlocks to explain parameters and return values

  4. Test your builder methods - Create unit tests to ensure proper functionality

  5. Start simple and expand - Begin with a few methods and add more as needed

Conclusion

Creating your own Eloquent Builder provides a clean, maintainable way to extend Laravel's query capabilities. By understanding when and why to implement custom builders, you can make informed architectural decisions that lead to more maintainable code.

The right time to create a custom builder is when your models become bloated with query logic, when you notice repetitive query patterns across your application, or when your business domain requires specialized query capabilities. By recognizing these signs early, you can refactor your code to be more modular and easier to maintain in the long run. Remember that custom builders, like all design patterns, are tools to be used when appropriate. They add a layer of abstraction that's invaluable for complex applications but might be unnecessary for simple projects. Let your application's complexity guide your decision on when to implement this powerful pattern.

1
Subscribe to my newsletter

Read articles from Asfia Aiman directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Asfia Aiman
Asfia Aiman

Hey Hashnode community! I'm Asfia Aiman, a seasoned web developer with three years of experience. My expertise includes HTML, CSS, JavaScript, jQuery, AJAX for front-end, PHP, Bootstrap, Laravel for back-end, and MySQL for databases. I prioritize client satisfaction, delivering tailor-made solutions with a focus on quality. Currently expanding my skills with Vue.js. Let's connect and explore how I can bring my passion and experience to your projects! Reach out to discuss collaborations or learn more about my skills. Excited to build something amazing together! If you like my blogs, buy me a coffee here https://www.buymeacoffee.com/asfiaaiman