← Back to blog

Eloquent Performance: Reusable relationships

| Laravel

This article was published over 2 years ago. Some information may be outdated.

Sometimes you end up causing N+1 problems by reloading the same relationships that were already loaded.

Say you have a method named isAuthor() in the App\Models\Comment.

The method determines whether the author of the comment is the same author of the post:

// App\Models\Comment

public function isAuthor(): bool
{
    return $this->post->user_id === $this->user_id;
}
// App\Controllers\PostsController

$post = Post::where('id', $id)
    ->with('comments.user')
    ->first();

return view('posts.show', compact('post'));

In the template, you show a badge if the comment's author is the same as the post's author:

@foreach ($post->comments as $comment)
<div class="card">
    <div class="card-body">
        <h6 class="card-title">Published at {{ $comment->created_at->toFormattedDateString() }}
            by {{ $comment->user->name }}
            @if ($comment->isAuthor())
                <span class="badge badge-warning">Author</span>
            @endif
        </h6>
        <p class="card-text">{{ $comment->comment }}</p>
    </div>
</div>
<div class="m-4"></div>
@endforeach

The isAuthor() method is called inside the foreach, which triggers an N+1 problem.

Since the App\Models\Post was already loaded in the controller, you should reuse it instead of hitting the database again to fetch the same model.

Do that by using the setRelation() method on the Illuminate\Database\Eloquent\Collection:

// App\Controllers\PostsController

$post = Post::where('id', $id)->with('comments.user')->first();

// This is our fix
$post->comments->each->setRelation('post', $post);

return view('posts.show', compact('post'));

No more N+1 problem.

In the next post, I will discuss counting records in a single query.

Summary

  • Reloading already-loaded relationships causes N+1 -- if a parent model is already in memory, do not let child models query the database for it again.
  • setRelation() reuses loaded models -- use $collection->each->setRelation('relationship', $model) to inject an already-loaded model into a collection of related models, eliminating redundant queries.
Share