VOOZH about

URL: https://dev.to/bakgeorge/what-with-does-to-your-queries-in-laravel-2fj7

⇱ What $with Does to Your Queries in Laravel - DEV Community


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.