How to build a custom eloquent builder class in Laravel.
Tags: laravel, webdev, tutorial, database
Published: 2023-04-05 02:38:39

In Laravel, Eloquent is the default ORM (Object-Relational Mapping) that provides an elegant and easy way to interact with the database. An Eloquent builder is a query builder that provides a convenient, fluent interface to create and run database queries.

When you create a new instance of an Eloquent model, you can use its query builder methods to create and run queries. For example, you can use the "where" method to specify conditions for the query, the "orderBy" method to order the results, and the get method to retrieve the results.

In addition to the basic query builder methods, the Eloquent builder also provides a number of advanced features, such as eager loading of related models, lazy loading of relationships, and support for polymorphic relationships. Overall, the Eloquent builder is a powerful tool that makes it easy to work with databases in Laravel.

Let's dive into the code! In this example, we have a model called Bookings, where a user can make a reservation to rent out a company's vehicles. The goal is to fetch a user's reservation records and allow the user to filter the records based on whether the reservation was cancelled, if the user picked up the reserved vehicle, and a general search based on the name on the reservation or the email used for the reservation. To achieve this, we will create our first eloquent builder and improve upon it as we go along.

```
Booking::query()->where('user_id', $this->user_id)
    ->when($this->cancelled != null, function($query){
        $query->where('cancelled', filter_var($this->cancelled, FILTER_VALIDATE_BOOLEAN));
    })
    ->when($this->picked_up != null, function($query){
        $query->where('confirm_pick', filter_var($this->picked_up, FILTER_VALIDATE_BOOLEAN));
    })
    ->when($this->search != '', function($query){
        $query->where('name', "LIKE", "%{$this->search}%")
                ->orWhere('email', 'LIKE', "%{$this->search}%");
    })->paginate(5)
```
Let me explain the code above.

1. We begin the query builder by calling the model Booking and `query` method `Booking::query()`, this is how we start query builders in Laravel.

2. Then we add the `where` method to add constraints which implies that we are looking for bookings where the `user_id` matches the current user's id.

3. Next, `when($this->cancelled != null, function($query){...})` is a conditional statement using the when() method. If the `$cancelled` property of the current class is not null, it will execute the function passed as the second argument. Inside the function, `$query` represents the query builder instance. Here, we're adding another filter to the query builder where cancelled column should be equal to the boolean value of `$this->cancelled` property.

4. Similarly, `when($this->picked_up != null, function($query){...})` adds a filter to the query builder based on the `$picked_up` property, and `when($this->search != '', function($query){...})` adds a filter based on the $search property.

5.Finally, the paginate(5) method is used to paginate the results and limit the number of results per page to 5.

So, this code generates a query to fetch the bookings where the `user_id` is equal to the current user's `user_id`, and then applies additional filters based on the values of $cancelled, $picked_up, and $search, before paginating the results. 

Now that we understand what the code means, let's clean up our code. Writing clean code has many benefits like:

- Readability: Clean code is easier to read and understand, which helps other developers on the team to quickly grasp the code and make modifications if needed.
- Maintainability: Clean code is easier to maintain and update, which saves time and effort in the long run.
- Collaboration: Clean code promotes collaboration among developers as it is easier to understand and modify. This means that multiple developers can work on the same codebase without stepping on each other's toes.
- Debugging: Clean code makes debugging easier since it is easier to identify where errors or bugs may be located.
- Scalability: Clean code is more scalable since it is easier to add new features or functionality without introducing bugs or breaking existing code.
- Efficiency: Clean code is more efficient since it requires fewer resources to execute and runs faster, which can lead to improved performance and user experience.

We are going to implements the scope approach to clean up the initial form of our code.

In Laravel, a scope query is a reusable query constraint that can be applied to a model. It allows you to define a specific set of constraints for a query and reuse it in different parts of your application.

You can define a scope query method in a model by prefixing its name with "scope".

```
<?php

namespace App\Models;

use App\Builders\BookingsBuilder;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Booking extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'email', 'booking_date', 'pickup_date', 'return_date', 'confirm_pick', 'user_id', 'vehicle_id', 'total_price', 'num_days'];

    
    public function scopeCancelled($query, $cancelled)
    {
        return $query->when($cancelled != null, function($q) use($cancelled){
            $q->where('cancelled', filter_var($cancelled, FILTER_VALIDATE_BOOLEAN));
        });
    }

    public function scopePickedUp($query, $pickedUp)
    {
        return $query->when($pickedUp != null, function($q) use($pickedUp){
            $q->where('confirm_pick', filter_var($pickedUp, FILTER_VALIDATE_BOOLEAN));
        });
    }

    public function scopeSearch($query, $search)
    {
        return $query->when($search != null, function($q) use($search){
            $q->where('name', 'like', '%'.$search.'%')
                    ->orWhere('email', 'like', '%'.$search.'%');
        });
    }
}
```
The code above achieves the same goal as the original query builder, but with the added benefit of creating reusable methods that can be called on the model from anywhere in our application. This means we can avoid duplicating our code and have a more organized and efficient way of handling our queries.

Let's take a look at what our current code looks like.

```
public function render()
    {
        return view('livewire.frontend.user-bookings', [
            'bookings' => Booking::query()->where('user_id', $this->user_id)
                ->Cancelled( $this->cancelled)
                ->PickedUp( $this->picked_up)
                ->Search( $this->search)
                ->paginate(5)
        ]);
    }
```
It's much cleaner and more readable than its initial form. However, as you can see, our model class now has a lot of methods, which can make it messy and harder to maintain. To take it a step further, let's build a custom Eloquent builder class.

When using Laravel's Eloquent ORM, it's common to define scopes on models to encapsulate commonly used query logic. This makes it easier to maintain and reuse queries across different parts of your application. However, as your application grows and you add more and more scopes to your models, it can become cluttered and difficult to manage.

A solution to this problem is to use a custom Eloquent builder class. This class is responsible for building queries based on the defined scopes and filters, instead of putting all the logic in the model. This approach can help keep your models clean and focused on their main responsibilities, while also providing a more modular and testable codebase. By separating the query building logic from the model, you can also make it easier to reuse the queries across different models and even different projects.

First, create a new class that extends Laravel's base `Illuminate\Database\Eloquent\Builder` class and move all your methods from the model into this class:

```
<?php
namespace App\Builders;
use Illuminate\Database\Eloquent\Builder;



class BookingsBuilder extends Builder
{
    public function __construct($query)
    {
        parent::__construct($query);
    }

    public function Cancelled( $cancelled)
    {
        return $this->when($cancelled != null, function($q) use($cancelled){
            $q->where('cancelled', filter_var($cancelled, FILTER_VALIDATE_BOOLEAN));
        });
    }

    public function PickedUp( $pickedUp)
    {
        return $this->when($pickedUp != null, function($q) use($pickedUp){
            $q->where('confirm_pick', filter_var($pickedUp, FILTER_VALIDATE_BOOLEAN));
        });
    }

    public function Search( $search)
    {
        return $this->when($search != null, function($q) use($search){
            $q->where('name', 'like', '%'.$search.'%')
                    ->orWhere('email', 'like', '%'.$search.'%');
        });
    }
}
```
Next, update your model to use the new builder class:

```
<?php

namespace App\Models;

use App\Builders\BookingsBuilder;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Booking extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'email', 'booking_date', 'pickup_date', 'return_date', 'confirm_pick', 'user_id', 'vehicle_id', 'total_price', 'num_days'];

    public function newEloquentBuilder($query)
    {
        return new BookingsBuilder($query);
    }

    public function vehicle()
    {
        return $this->belongsTo(Vehicle::class);
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```
Now our model is clean and easily readable.

Thank you for reading and please be sure to follow me on my social media accounts [Twitter](https://twitter.com/RebelNii), [Github](https://github.com/RebelNii), [showwcase](https://www.showwcase.com/rebelnii) and also subscribe to my [Youtube](https://www.youtube.com/channel/UCdzJlunXysrx-HY69b3CblA), I have a tutorial about this blog post on there.