If you use $with on your Eloquent models, you might be running more queries than you think.
I stumbled upon protected $with = ['modelA', 'modelB'] a few months back in an application that was experiencing slow page loads. I set up a dummy blog in my local Laravel Lab (laralab.test) to experiment with it. What I found was that $with cascades, and one query can quietly become five.
Here's the setup. We have two simple emtpy models, "Author" and "Category", and the rest are:
class Comment extends Model
{
use HasFactory;
protected $with = ['post', 'author'];
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
}
class Post extends Model
{
use HasFactory;
protected $with = ['author', 'category'];
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
And Laravel's default User model.
Notice the $with property on both Comment and Post. That's where things get interesting.
Without $with: 1 query
I commented out protected $with = ['post', 'author'] in the Comment model and requested the 10 latest comments:
Route::get('/test-with-cascade', function () {
DB::enableQueryLog();
Comment::latest()->limit(10)->get();
dump(DB::getQueryLog());
});
Result:
select * from "comments" order by "created_at" desc limit 10
One query. Clean.
With $with: 5 queries
I uncommented the $with property and ran the same route. Instead of 1 query, there were now 5:
select * from "comments" order by "created_at" desc limit 10
select * from "posts" where "posts"."id" in (5, 6, 8, 10, 11, 13, 15, 17, 19)
select * from "authors" where "authors"."id" in (1, 2, 3, 4)
select * from "categories" where "categories"."id" in (1, 2, 3, 4)
select * from "authors" where "authors"."id" in (1, 2, 3, 4, 5)
1 query became 5. Same route, same data, same code, the only difference is that $with property.
The cascade: $with triggers $with
What happened is that I told Laravel: every time you load a Comment, also load its Post and Author. But the Post model also has $with = ['author', 'category']. So loading those posts triggers the same process again:
- Load Post's author:
select * from "authors" where "authors"."id" in (1, 2, 3, 4) - Load Post's category:
select * from "categories" where "categories"."id" in (1, 2, 3, 4)
The $with property cascades through your model relationships. Every model it touches checks for its own $with and fires more queries.
I still don't have a good use case for $with on the model. Until I find one, I'm keeping it empty and loading relationships explicitly with ->with() on the query where I actually need them.
Top comments (0)