6 Laravel Eloquent Secrets to improve your code
Published on by Stefan Bauer
Eloquent is the default ORM that ships with Laravel. It implements the Active-Record pattern and provides an easy way to interact with your database. Every single model represents a table in your database with which you can work. In this post, we’ll show you more or less hidden secrets, methods, and properties you might not know to improve your code.
Snake Attributes
Snake Attributes are an interesting one. Let’s take a look at what the code says:
/** * Indicates whether attributes are snake cased on arrays. * * @var bool */public static $snakeAttributes = true;
Very often, people make a mistake to use this property to change the way how to access properties. Many people believe that if they change this property, they can easily access the attributes using camel-case annotation. That’s not the case. We strongly advise against using it. It is merely there to define whether attributes are camel or snake-cased when the model is output as an array.
If you want to work camel-case based, we recommend you take a look at the package Eloquence by Kirk Bushell.
Pagination
If you use Laravel’s Eloquent ORM, you’re in luck. It provides an easy way to paginate results out of the box. You might be familiar with something like this:
$comments = Comment::paginate(20);
With this method, you can paginate the comment model with 20 items per page. Changing that value gives you the possibility to define how many items are displayed per page. If you do not specify anything, the default gets applied, which is 15.
Assume that you want to display comments in several places on your website. Always 30 comments per page. Then it would be bothersome if you have to pass the parameter 30 at every place. Therefore you can set a new default value directly on the model.
protected $perPage = 30;
Appending custom values to models
Eloquent has a great feature called “Accessors”. The feature allows you to add custom fields to models that don’t exist on the model or in the table. It doesn’t matter if you use existing values or define completely new ones. You can return anything. Here is an example of how Accessors work. Given there is a model called User
where we put the code below.
function getFullNameAttribute() { return sprintf('%s %s', $this->first_name, $this->last_name);}
Now you have access to a full_name
attribute on the post model, like this:
User::latest()->first()->full_name;
The problem is now if you return objects, like a collection, this attribute doesn’t get appended to the user model. Add the protected $appends
attribute to your model. It accepts an array with one or multiple fields that should automatically be appended from now. This is how it looks like:
protected $appends = ['full_name'];
Mutators for non-existing columns
Mutators are the opposite of Accessors. You can use them for really cool stuff. For example, to convert different inputs. Let’s show you something. Imagine you want to save a kind of time period. Usually, you always save the smallest possible unit. In our case seconds. For UX reasons, the user doesn’t want to enter seconds, but for example, minutes in one place, or hours in another place. It can all be solved very quickly.
class Video extends Model{ public function setDurationInMinutesAttribute($value) { $this->attributes['duration_in_seconds'] = $value * 60; } public function setDurationInHoursAttribute($value) { $this->attributes['duration_in_seconds'] = $value * 60 * 60; }}
What does it mean? It means that you can use the non-existent column duration_in_minutes
on the model, but in the background, the column duration_in_seconds
is updated. The same applies to the non-existent column duration_in_hours
. This results in the following logic, for example:
class AnyController{ public function store() { $video->update([ 'title' => request('title'), 'duration_in_minutes' => request('duration_in_minutes'), ]); }}
This saves you the calculation in the controller, and you can simply use a non-existent column and use a mutator to map it correctly to the correct column while performing some calculations.
Eager loading with $with
Let’s talk a little about relations. By default, Laravel uses lazy loading. What does that mean in terms of relations? The good thing is that lazy loading saves memory because not all data has to be kept, and we load the data when we need it. Consider this code sample:
$comments = Comment::all();foreach ($comments as $comment) { echo $comment->user->name;}
In the example above, we get all comments. Then we loop through the comments and display the username for each comment. The code works, but we run into a problem. Lazy loading now makes sure that the query to get the user is only executed when we want to output the username.
Welcome to your first N+1 problem. Why N+1? N is always the number of comments, and 1 is the query to get the comments at all. For example, if we have 500 comments, then the query to get all comments is fired once and then one query to get the corresponding user – per comment. So 500 + 1 queries. This means that as the number of comments increases, the number of queries increases as well.
To prevent it there is something called eager loading.
$comments = Comment::with('user')->get();foreach ($comments as $comment) { echo $comment->user->name;}
That ends in two queries. The first query fetches all comments, and the second query immediately fetches all associated users. In the background, the following happens (simplified):
SELECT id, user_id, body FROM comments;SELECT name FROM users WHERE user_id IN (1,2,3,4,5...);
Whether 10, 500 or 10000 comments are fetched at the end is not essential. Two queries remain.
You have now seen how you can use eager loading. But only how to use it manually. You can also automate the whole thing so that certain relations are always loaded automatically by eager loading. For this there is a property on the model.
protected $with = [];
So we can simply set the property protected $with = ['user'];
on our Comment
model, and from now, the user is automatically loaded at any time.
There are many more possibilities with eager loading. Loading of specific columns only, nested eager loading, multiple eager loadings, and much more. Head over to the Laravel documentation or deep dive into the core.
Model keys
From time to time, there is the need to fetch all IDs of a specific query. It does not matter whether this is a complex query or not. Most people would probably do the following:
User::all()->pluck('id');
This works great. But now you get a collection back. To get an array, you would have to pass it to the toArray()
method again.
User::all()->pluck('id')->toArray();
In most cases, however, this can be shortened. Like this:
User::all()->modelKeys();
This method returns an array. In our case, the IDs. It is important to understand that this method does not always necessarily return IDs. It returns, as the name says, all primary keys as an array. The primary keys can be defined in the model. The default is id
.
protected $primaryKey = 'id';
Creator of PingPing.io ・ Author of Laravel Secret ・ Full Stack developer and a huge fanboy of Laravel, InertiaJS, Livewire and TailwindCSS.