20 Eloquent ORM Tricks
- Transfer
Eloquent ORM seems simple, but under the hood there are many half-hidden features and lesser-known ways. In this article I will show you some tricks.
1. Increment and decrement
Instead of this:
$article = Article::find($article_id);
$article->read_count++;
$article->save();
You can do this:
$article = Article::find($article_id);
$article->increment('read_count');
This will also work:
Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($produce_id)->decrement('stock'); // -1
2. X or Y methods
Eloquent has several functions that combine the two methods, for example, “please do X, otherwise do Y”.
Example 1 - findOrFail()
:
Instead of this:
$user = User::find($id);
if (!$user) { abort (404); }
We do this:
$user = User::findOrFail($id);
Example 2 - firstOrCreate()
:
Instead of this:
$user = User::where('email', $email)->first();
if (!$user) {
User::create([
'email' => $email
]);
}
We do this:
$user = User::firstOrCreate(['email' => $email]);
3. The boot () method of the model
There is a magic method in the Eloquent model boot()
where you can override the default behavior:
classUserextendsModel{
publicstaticfunctionboot(){
parent::boot();
static::updating(function($model){
// выполнить какую-нибудь логику// переопределить какое-нибудь свойство, например $model->something = transform($something);
});
}
}
Probably one of the most popular examples is setting the field value at the time the model object is created. Suppose you want to generate a UUID field at this point.
publicstaticfunctionboot()
{
parent::boot();
self::creating(function ($model) {
$model->uuid = (string)Uuid::generate();
});
}
4. Relationships with condition and sorting
This is a typical way of defining relationships:
publicfunctionusers(){
return$this->hasMany('App\User');
}
But did you know that we can add here where
or orderBy
?
For example, if you need a special relationship for certain types of users, sorted by email, you can do this:
publicfunctionapprovedUsers(){
return$this->hasMany('App\User')->where('approved', 1)->orderBy('email');
}
5. Model properties: timestamps, appends, etc.
There are several “parameters” of the Eloquent model in the form of class properties. The most popular ones are probably the following:
classUserextendsModel{
protected $table = 'users';
protected $fillable = ['email', 'password']; // какие поля могут быть заполнены выполняя User::create()protected $dates = ['created_at', 'deleted_at']; // какие поля будут типа Carbonprotected $appends = ['field1', 'field2']; // доп значения возвращаемые в JSON
}
But wait, there are more:
protected $primaryKey = 'uuid'; // не должно быть "id"public $incrementing = false; // и не должно быть автоинкрементомprotected $perPage = 25; // Да, вы можете переопределить число записей пагинации (по умолчанию 15)const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at'; // Да, даже эти названия также могут быть переопределеныpublic $timestamps = false; // или не использоваться совсем
And there are even more, I have listed the most interesting of them, for more details, check out the default code of the abstract Model class and look at all the traits used.
6. Search for multiple entries
Everyone knows the method find ()
, right?
$user = User::find(1);
I was surprised how few people know that it can accept multiple IDs as an array:
$users = User::find([1,2,3]);
7. WhereX
There is an elegant way to turn this:
$users = User::where('approved', 1)->get();
In it:
$users = User::whereApproved(1)->get();
Yes, you can change the name of any field and add it as a suffix to “where”, and it will work as if by magic.
Also in Eloquent ORM there are predefined methods related to date and time:
User::whereDate('created_at', date('Y-m-d'));
User::whereDay('created_at', date('d'));
User::whereMonth('created_at', date('m'));
User::whereYear('created_at', date('Y'));
8. Sort with relationships
A little more than a trick. What if you have forum topics, but want to sort them by their latest posts ? A fairly popular requirement in forums with the latest updated topics at the top, right?
First, describe a separate link for the last post in the topic:
publicfunctionlatestPost(){
return$this->hasOne(\App\Post::class)->latest();
}
And then, in your controller, you can do this “magic”:
$users = Topic::with('latestPost')->get()->sortByDesc('latestPost.created_at');
9. Eloquent :: when () - without “if-else”
Many of us write conditional queries with if-else, something like this:
if (request('filter_by') == 'likes') {
$query->where('likes', '>', request('likes_amount', 0));
}
if (request('filter_by') == 'date') {
$query->orderBy('created_at', request('ordering_rule', 'desc'));
}
But the best way is to use when()
:
$query = Author::query();
$query->when(request('filter_by') == 'likes', function($q){
return $q->where('likes', '>', request('likes_amount', 0));
});
$query->when(request('filter_by') == 'date', function($q){
return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});
This example may not seem shorter or more elegant, but it will be more correct to forward parameters:
$query = User::query();
$query->when(request('role', false), function($q, $role){
return $q->where('role_id', $role);
});
$authors = $query->get();
10. The default model for relationships
Let's say you have a post owned by the author and the Blade code:
{{ $post->author->name }}
But what if the author is removed or not installed for any reason? You will get an error similar to "property of non-object".
Of course, you can prevent this as follows:
{{ $post->author->name ?? '' }}
But you can do it at the Eloquent relationship level:
publicfunctionauthor(){
return$this->belongsTo('App\Author')->withDefault();
}
In this example, the relation author()
returns an empty model App\Author
if the author is not attached to the post.
In addition, we can set default property values for this model.
publicfunctionauthor(){
return$this->belongsTo('App\Author')->withDefault([
'name' => 'Guest Author'
]);
}
11. Sort by converter
Imagine that you have such a converter:
functiongetFullNameAttribute(){
return$this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
Do you need to sort records by field full_name
? Such a solution will not work:
$clients = Client::orderBy('full_name')->get(); // не работает
The solution is quite simple. We need to sort the records after we receive them.
$clients = Client::get()->sortBy('full_name'); // работает!
Note that the name of the functions is different - this is not orderBy , this is sortBy .
12. Default Sort
What if you want to User::all()
always sort by field name
? You can assign a global preset (Global Scope). Let us return to the method boot ()
that we have already discussed above.
protectedstaticfunctionboot(){
parent::boot();
// Сортировка по полю name в алфавитном порядкеstatic::addGlobalScope('order', function(Builder $builder){
$builder->orderBy('name', 'asc');
});
}
13. Raw expressions
Sometimes we need to add raw expressions to our Eloquent request.
Fortunately, there are functions for this.
// whereRaw
$orders = DB::table('orders')
->whereRaw('price > IF(state = "TX", ?, 100)', [200])
->get();
// havingRaw
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();
// orderByRaw
User::where('created_at', '>', '2016-01-01')
->orderByRaw('(updated_at - created_at) desc')
->get();
14. Replication: make a copy of the record
Without deep explanation, here is the best way to make a copy of a record in a database:
$task = Tasks::find(1);
$newTask = $task->replicate();
$newTask->save();
15. Chunk () method for large tables
Not really about Eloquent, it's more about collections, but still a powerful method - for processing large data sets. You can break them into pieces.
Instead of this:
$users = User::all();
foreach ($users as $user) {
// ...
You can do it:
User::chunk(100, function($users){
foreach ($users as $user) {
// ...
}
});
16. Creating additional files when creating a model
We all know the Artisan team:
php artisan make:model Company
But did you know that there are three useful flags for creating additional model files?
php artisan make:model Company -mcr
- -m creates a migration file ( migration )
- -c creates controller ( controller )
-r indicates that the controller should be a resource ( resourceful )
- *
17. Overwriting updated_at during save
Do you know that a method ->save()
can take parameters? As a result, we can “ignore” the updated_at
functionality, which should have set the current timestamp by default.
Take a look at the following example:
$product = Product::find($id);
$product->updated_at = '2019-01-01 10:00:00';
$product->save(['timestamps' => false]);
Here we rewrote updated_at
our predefined value.
18. What is the result of the update () method?
Have you ever thought about what this code returns?
$result = $products->whereNull('category_id')->update(['category_id' => 2]);
I mean, the update is performed in the database, but what will this one contain $result
?
Answer: affected lines . Therefore, if you need to check how many lines have been affected, you do not need to call anything - the method update()
will return this number for you.
19. Convert brackets to Eloquent request
What to do if you have AND and OR in your SQL query, as here:
... WHERE (gender = 'Male'and age >= 18) or (gender = 'Female'and age >= 65)
How to convert this request to an Eloquent request? This is the wrong way:
$q->where('gender', 'Male');
$q->orWhere('age', '>=', 18);
$q->where('gender', 'Female');
$q->orWhere('age', '>=', 65);
The order will be wrong. The correct way is a bit more complicated using closures as subqueries:
$q->where(function($query){
$query->where('gender', 'Male')
->where('age', '>=', 18);
})->orWhere(function($query){
$query->where('gender', 'Female')
->where('age', '>=', 65);
})
20. orWhere with several options
You can pass an array of parameters to orWhere()
.
The “normal” way:
$q->where('a', 1);
$q->orWhere('b', 2);
$q->orWhere('c', 3);
You can do it like this:
$q->where('a', 1);
$q->orWhere(['b' => 2, 'c' => 3]);