Cleaner models with Laravel Eloquent Builders

rocksheep

Marinus van Velzen

Posted on September 5, 2021

Cleaner models with Laravel Eloquent Builders

Over the past few years I have created tons and tons of models in Laravel. These models have always blown up in size with tons of accessors and added scopes. For the unintroduced, model scopes are methods containing queries that can be chained while retrieving data from the database. For example:

// Models/Article.php
class Article extends Model
{
    public function scopePublished(Builder $builder)
    {
        return $builder->whereNotNull('published_at');
    }
}

// usage of the scope
Article::published()->get();
Enter fullscreen mode Exit fullscreen mode

As you might imagine, these methods will add up after a while resulting in bloated models, but what if I tell you that you can clean this up easily?

Writing your own Eloquent Builder

It's possible to create your own Eloquent Builder and bind it to your models. This can be done by creating a class which extends the Eloquent Builder. I'll use the example above for the model that we will clean up. So let's start by creating a ArticleBuilder. It doesn't really matter where you place it, but I tend to create a directory for it in the App namespace.

<?php

declare(strict_types=1);

namespace App\EloquentBuilders;

use Illuminate\Database\Eloquent\Builder;

class ArticleBuilder extends Builder
{
    public function published(): self
    {
        return $this->whereNotNull('published_at');
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it uses the same methods as before, because the scope uses a query builder in the background!

Registering your brand new Eloquent Builder

Now all that's left is to bind our custom query builder to the Article Model. This can be done by overriding the newEloquentBuilder method. After overriding it, you can remove any of the old scopes. Your end result will look something like this!

<?php

declare(strict_types=1);

namespace App\Models;

use App\EloquentBuilders\ArticleBuilder;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasFactory;

    public function newEloquentBuilder($query): Builder
    {
        return new ArticleBuilder($query);
    }
}
Enter fullscreen mode Exit fullscreen mode

Using our new builder

Using your brand new query builder is just the same as with the scopes. All you need to do is chain it on your query like you usually do.

Article::published()->get();
Enter fullscreen mode Exit fullscreen mode

In the end nothing changed functionality wise, but your model just became a lot cleaner.

💖 💪 🙅 🚩
rocksheep
Marinus van Velzen

Posted on September 5, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related