DEV Community

Marinus van Velzen
Marinus van Velzen

Posted on

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.

Latest comments (18)

Collapse
 
yurich84 profile image
Yurich

Just curious, why not use traits?

Collapse
 
wblondel profile image
William Blondel

I was wondering the same

Collapse
 
ahmadmoussawi profile image
Ahmad Moussawi

Wow, clean and simple. Thanks for sharing

Collapse
 
peter_brown_cc2f497ac1175 profile image
Peter Brown

Or we could all just learn SQL. An ORM is almost never the right idea. They were born out of people's fear of the database and unwillingness to learn PL/SQL, stored procedures, triggers and how to engineer a proper data access layer.

Collapse
 
beatlesfansunit profile image
Beatlesfansunite

What nonsense. Hiding business logic in the database is an unholy nightmare in the making, and also a dated business practice.

Maybe if your project has 10 classes, writing raw SQL will work. 100’s of classes? No way.

Otherwise, 1999 is calling you.

Collapse
 
bdelespierre profile image
Benjamin Delespierre • Edited

They were born out of people's fear of bla bla bla

This is innacurate. ORM exists for the sole purpose of facilitating modularity, reusability, and portability. Not having to worry about the DAL is a welcome bonus.

Collapse
 
beatlesfansunit profile image
Beatlesfansunite

I cannot imagine a junior developer vandalizing a database with an additional trigger. No way. ORM’s keep the business layer sane.

Keep devs out of the database, and your DBA will hug you.

Collapse
 
peter_brown_cc2f497ac1175 profile image
Peter Brown

The only way that it's portable is if you're not using the database properly. If you're using the database properly then you're leveraging stored procedures triggers and a myriad of other database specific tools, scripts etc. The database is not simply a place to dump data. It should be the work horse of your DAL. Unfortunately, these days so many people don't even know how to write a line of database-side scripting.

Thread Thread
 
bdelespierre profile image
Benjamin Delespierre

Unfortunately, these days so many people don't even know how to write a line of database-side scripting.

Well then, we must ask ourselves why is it the case? I always found triggers & procedures difficult to code, to comprehend once they're in place (for most developpers, knowing a legacy project involves procedures is an immediate red-flag), and worst of all, to scale.

As powerful as they are, they're not helping most developers to get the job done. I mean, I could do my groceries math using linear algebra and get the same results, but is it helpful?

I believe ORM have been adopted far and wide because they're an objectively more ergonomic tool and, again, more portable. What if I wanted to install that cool CMS everyone uses but dang, it needs a specific RDBMS at a specific version?

I also believe there is a place for people with a real expertise of stored procedures, triggers, and all the database delicious features I clearly know nothing about. In their own place, I'm sure they're immensely helpful.

So, instead of demeaning the work of millions because in your eyes they aren't worthy, why not just live and let live?

Peace out.

Thread Thread
 
peter_brown_cc2f497ac1175 profile image
Peter Brown

I'm not demeaning the work of anyone. It is a fact that scripting done on the database is generally faster than scripting done on the server.. You can quantify it. You claim ignorance PL/SQL , but yet you claim to profess that it is cumbersome and unnecessary in most cases. You claim that it is a red flag. It is a red flag because most people don't take the time to learn it. Most people try to learn 50000 languages in instead of learning one scripting language 1 compiled language and 1 database and learning them completely. Unfortunately our education system as it is currently structured creates a sort of attention deficit in its manner of teaching. Gone are the days of repetition and rote memorization. Instead people today tend to flit around from topic to topic instead of gaining mastery in any one thing. Yes this is a judgment about the current state of the industry. It is not a condemnation of any individual. It is simply simply an observation from someone that has been in this industry for many years.. Do I learn new things? Certainly I do. But I also know that it is a firm understanding of the fundamentals that keeps us future proof and employed for the long haul.

Thread Thread
 
bdelespierre profile image
Benjamin Delespierre

Whatever float your boat mate. I hope you're happy with that kind of thinking.

I don't want to see more comments like this and I'm not interested in arguing with you. You are a bitter, old fashion person and you have no respect for opinions other than yours, so I'm blocking you.

Goodbye.

Thread Thread
 
peter_brown_cc2f497ac1175 profile image
Peter Brown • Edited

Let it be known that this individual resorted to name calling.. I am making an observation about the general state of things and he is attacking me as a person. What I am talking about is not old fashioned, it is time tested truth. code related to data manipulation belongs in the database. It's that simple. Sometimes people hide behind innovation Instead of doing the hard work. Learning PL/SQL is hard. So is everything worth learning.. You will truly future proof your skills if you master database programming.

Thread Thread
 
jonesrussell profile image
Russell Jones

Let it be known! I'm with Benjamin though. Your original comment is valueless in it's current form.

Collapse
 
codefinity profile image
Manav Misra

It's another case where we talk about degrees of separation. Ultimately writing Assembly directly is the most performant! :)
While there are some realistic speed considerations with database access, there's a reason why most teams have a dedicated DBA that's separate from developers.
There's only so much time in the day and limited mental energy.
Use what makes you productive in terms of what gets things done and meets deadlines.
If you love SQL or have an interest, get a lot deeper with it and bypass the ORM.

Collapse
 
rocksheep profile image
Marinus van Velzen

It's true that writing and using pure SQL is a lot quicker than to use an ORM, but then you'll have to manually map all the data to the required objects anyway, which in the end will turn into a kind of specialized ORM.

It's also true that stored procedures, triggers and other database functions can make it a lot faster and quicker to write to a database. It also makes it a lot more complex, while using an ORM makes programming more accessible.

In the end it all comes down to preference. If you want the fastest and most optimized code, you'll want to use procedures and such, but you can also make good software by using an ORM.

Collapse
 
maxgoryunov profile image
MaxGoryunov

The truth is, you do not have to map data to objects(models). I would even say this is a horrible idea. You should always separate data and objects, otherwise your models will become huge inevitably.

Collapse
 
wulfheart profile image
Alex

Sometimes an ORM is faster to develop with...

Collapse
 
peter_brown_cc2f497ac1175 profile image
Peter Brown

Development speed is only a one time hit. The slow data access however will exist for the entirety of the application.. Upfront engineering and design is essential for creating a performant system.