logo

Why does Eloquent query for a parent it already loaded?

Let chaperone hand it over instead

Joel Clermont
Joel Clermont
2026-10-22

Eager loading solves the N+1 problem when you go from a parent to its children. But going back up from a child to its parent is a different story.

$posts = Post::with('comments')->get();

foreach ($posts as $post) {
    foreach ($post->comments as $comment) {
        echo $comment->post->title;
    }
}

With 3 posts and 12 comments, this runs 14 queries. Two for the eager load, and then one for every single comment.

The frustrating bit is we already have the post. So why is Eloquent querying for it again?

The core issue is that each comment doesn't know it was loaded through a post. When you access $comment->post, Eloquent sees an unloaded relationship and fetches it fresh.

This is the exact scenario that the cleverly named chaperone() method is for. It tells Eloquent to set the parent on each child as the children are loaded.

public function comments(): HasMany
{
    return $this->hasMany(Comment::class)->chaperone();
}

Now the same loop runs 2 queries, no matter how many comments there are. And $comment->post isn't just an equal copy of the post. It's the exact same instance from the outer loop.

If you don't want it on the relationship all the time, you can opt in at query time instead.

$posts = Post::with([
    'comments' => fn ($comments) => $comments->chaperone(),
])->get();

Eloquent guesses the name of the inverse relationship from the foreign key or the parent model's name. If your relationship has a different name, pass it in, like chaperone('article').

One question you might have: Since chaperone() doesn't run an additional query, why isn't this the default behavior?

Because it changes what your models contain. Each comment instance now also has a loaded post relationship, so toArray() and JSON responses include the parent nested inside every child. If you return models straight from an API, that's a payload change you want to opt into on purpose.

Here to help,

Joel

P.S. Hidden query counts like this are easy to miss in development and painful in production. Spotting them is a regular part of our code review.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼
email
No spam. Only real-world advice.