Catching N+1 Problems Early: Laravel's preventLazyLoading()

24th January 2025
Understanding the N+1 Problem
First, let's quickly recap what an N+1 query problem looks like. Consider this common scenario which you've probably seen a million times if you spent more than five minutes in the Laravel universe.
<?php
// Controller
$posts = Post::all();
return view('blog.index', [
'posts' => $posts,
]);
{{-- Blade --}}
@foreach ($posts as $post)
{{ $post->author->name }}
@endforeach
This code will execute one query to fetch all posts, plus an additional query for each post to fetch its author, hence the term "N+1" (1 initial query + N additional queries).
This may not seem like a big deal, but once you have 30 blog posts and you're fetching the email address and image for the author it gets a whole lot worse.
I've seen N+1 issues generate thousands of queries on a single page when only a handful are required.
The Power of preventLazyLoading()
Since Laravel 8 a powerful feature was introduced to help catch these issues during development. By adding this single line to your AppServiceProvider
to throw an exception in non production environments when you accidentally lean on lazy loading.
<?php
// app/Providers/AppServiceProvider.php
public function boot()
{
Model::preventLazyLoading(! app()->isProduction());
}
The advantages of this approach are as follows :-
It's automatically disabled in production with
! app()->isProduction()
It prevents N+1 issues during development and testing
It forces developers to be explicit about their data requirements
Accidentally introducing an N+1 issue is now almost impossible during development, the app will throw an exception instead of lazy loading relationships and introducing unseen N+1 issues.
How to Fix Lazy Loading Issues
When preventLazyLoading()
catches a lazy loading attempt, it throws a LazyLoadingViolationException
To fix this issue we just need to make sure that we use the with()
method on our models that utilise relationships, particularly ones inside loops.
<?php
// Instead of:
$posts = Post::all();
// Use eager loading:
$posts = Post::with('author')->get();
Notice how we are being explicit here, we are stating that we need the author to be loaded with all these posts, and that will be a single query instead of loading that relationship each time.
Conclusion
Laravel's preventLazyLoading()
is a powerful tool for maintaining Laravel applications.
By catching N+1 queries during development, you can ensure your application stays free from N+1 issues as it grows.
Remember, the goal isn't to eliminate lazy loading entirely - sometimes it's the right choice - but to make it a conscious decision rather than an accidental oversight.