DEV Community

Cover image for What $with Does to Your Queries in Laravel
Bakoulis George
Bakoulis George

Posted on • Originally published at georgebakoulis.dev

What $with Does to Your Queries in Laravel

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
});
Enter fullscreen mode Exit fullscreen mode

Result:

select * from "comments" order by "created_at" desc limit 10
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)