Enabling Model::preventLazyLoading() in Local Development
Laravel Eloquent makes it very easy to work with relationships. We can simply access a relationship like $user->posts, and Laravel will fetch the related records for us.
This is convenient, but there is one problem. If we are not careful, Laravel can execute additional database queries behind the scenes when a relationship is accessed. This is known as lazy loading.
Lazy loading is not always bad, but it can easily lead to the N+1 query problem, especially when working with collections.
Laravel provides Model::preventLazyLoading() to help us catch these problems during development.
In this article, we will understand what lazy loading is, how it can cause N+1 queries, how preventLazyLoading() works, and why enabling it in local development can help you write more efficient Laravel applications.
What is Lazy Loading in Laravel?
Let's start with a simple example.
Suppose we have a User model with a posts relationship:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Model
{
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
Now suppose we fetch a user:
$user = User::find(1);
At this point, Laravel has executed a query to retrieve the user. The posts relationship has not been loaded yet.
When we access the relationship:
$posts = $user->posts;
Laravel notices that the relationship has not been loaded and automatically executes another query to retrieve the user's posts.
This is called lazy loading.
What is the N+1 Query Problem?
Lazy loading becomes a problem when we access a relationship inside a loop.
For example:
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}
At first glance, this code looks completely fine. But let's understand what is happening behind the scenes.
The first query retrieves all users:
select * from users;
Then, for every user, Laravel loads the posts relationship separately.
If there are 100 users, you could end up with:
- 1 query to retrieve the users
- 100 queries to retrieve posts for each user
That means a total of 101 queries.
This is called the N+1 query problem.
Why Is N+1 a Problem?
The problem becomes more noticeable as the amount of data increases. Imagine a page that displays 500 users and the number of posts written by each user.
Instead of executing just a couple of optimized queries, your application could execute hundreds of database queries.
This can result in:
- Slower response times
- Higher database load
- More network round trips between the application and database
- Higher resource usage
- Poor performance as the application grows
The worst part is that the code itself may look completely normal. This is why detecting lazy loading problems during development is useful.
What is Model::preventLazyLoading()?
Laravel provides the preventLazyLoading() method on the Eloquent Model class.
When lazy loading prevention is enabled, Laravel can detect when an unloaded relationship is accessed and raise a LazyLoadingViolationException.
The basic usage is:
use Illuminate\Database\Eloquent\Model;
Model::preventLazyLoading();
After enabling it, accessing an unloaded relationship will no longer silently execute another query.
Instead, Laravel will tell you that lazy loading occurred.
How to Enable preventLazyLoading() in Laravel
The best place to enable this during local development is typically the AppServiceProvider.
Open:
app/Providers/AppServiceProvider.php
Then add the following inside the boot() method:
<?php
namespace App\Providers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
Model::preventLazyLoading();
}
}
Now Laravel will prevent lazy loading throughout the application.
Only Enable It in Local Development
Although preventLazyLoading() is very useful, you may not want your production application to throw exceptions simply because an unloaded relationship is accessed.
A common approach is to enable it only when the application is running in the local environment.
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::preventLazyLoading($this->app->isLocal());
}
With this approach, lazy loading prevention is enabled when your application is running locally.
In production, the behaviour is left unchanged.
What Happens When Lazy Loading Is Detected?
Let's go back to our previous example. Suppose lazy loading prevention is enabled:
Model::preventLazyLoading();
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}
When Laravel reaches $user->posts, the relationship has not been loaded.
Instead of silently running another database query, Laravel throws a lazy loading violation exception.
This immediately tells you that the code needs attention.
That is exactly what we want during development.
How to Fix the Problem with Eager Loading
Once preventLazyLoading() exposes the problem, the usual solution is to use eager loading.
Instead of:
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}
Load the relationship before using it:
$users = User::with('posts')->get();
foreach ($users as $user) {
echo $user->posts->count();
}
Now Laravel knows that the posts relationship should be loaded along with the users.
Instead of executing one query for every user, Laravel can retrieve the users and their posts using a small number of queries.
Before and After preventLazyLoading()
Let's compare the two approaches.
Without Lazy Loading Prevention
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}
This may silently generate an N+1 query problem.
With Lazy Loading Prevention
Model::preventLazyLoading();
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}
Now the lazy loading violation is detected instead of allowing the relationship to be silently loaded.
Properly Eager Loaded
$users = User::with('posts')->get();
foreach ($users as $user) {
echo $user->posts->count();
}
This is generally the better approach when you know that the relationship is required.
Nested Relationships
The same problem can occur with nested relationships.
For example, suppose a post belongs to a category:
$users = User::with('posts')->get();
foreach ($users as $user) {
foreach ($user->posts as $post) {
echo $post->category->name;
}
}
Here, posts is eager loaded, but category is not. If category is accessed and has not already been loaded, lazy loading can still occur.
You can eager load nested relationships like this:
$users = User::with('posts.category')->get();
foreach ($users as $user) {
foreach ($user->posts as $post) {
echo $post->category->name;
}
}
Now both posts and the nested category relationship are loaded ahead of time.
Using preventLazyLoading() in a Laravel Application
A practical setup for a Laravel application is to put the following in AppServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
Model::preventLazyLoading($this->app->isLocal());
}
}
This gives you a useful development-time safety net without changing the behaviour of production environments.
What If You Intentionally Need Lazy Loading?
Not every lazy-loaded relationship is automatically a performance bug. There may be situations where you intentionally want to load a relationship only when it is actually needed.
For example, if a relationship is rarely accessed and loading it for every model would be unnecessary, lazy loading may be acceptable.
The purpose of preventLazyLoading() is not to say that lazy loading should never exist. Its main purpose is to make accidental lazy loading visible during development.
If you intentionally need the relationship, you can explicitly load it:
$user = User::findOrFail(1);
$user->load('posts');
echo $user->posts->count();
Here, the relationship is loaded explicitly using load().
preventLazyLoading() and API Resources
This is particularly useful when building APIs with Laravel.
Suppose your API resource accesses a relationship:
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'posts' => PostResource::collection($this->posts),
];
}
If your controller retrieves many users without loading posts, the resource can accidentally cause an N+1 query problem.
For example:
$users = User::paginate(50);
return UserResource::collection($users);
Instead, if the resource requires posts, make that requirement explicit:
$users = User::with('posts')->paginate(50);
return UserResource::collection($users);
This makes the database requirements of your API much clearer.
Why You Should Enable It in Local Development
The biggest advantage of preventLazyLoading() is that it turns a hidden performance problem into an obvious development-time problem.
Without it, the application may appear to work perfectly during development. The N+1 problem may only become visible later when the application has more users, more records, or higher traffic.
With lazy loading prevention enabled, you discover the problem much earlier.
This encourages you to:
- Think about relationships before querying data
- Use eager loading when relationships are required
- Identify N+1 problems during development
- Write more predictable database queries
- Keep API and web requests efficient
Best Practice
A good approach is to enable lazy loading prevention in local development and explicitly load relationships whenever they are required.
For example:
public function boot(): void
{
Model::preventLazyLoading($this->app->isLocal());
}
Then, when you need a relationship:
$users = User::with(['posts', 'roles'])->get();
This makes it clear which relationships the query needs.
Conclusion
Laravel's Model::preventLazyLoading() is a small configuration that can make a big difference in how you detect Eloquent performance problems.
Lazy loading itself is a useful Eloquent feature, but accidental lazy loading can easily create N+1 queries. The problem is especially common when relationships are accessed inside loops, API resources, views, or other layers that are separated from the original database query.
By enabling:
Model::preventLazyLoading($this->app->isLocal());
you can catch these issues while developing instead of discovering them after the application reaches production.
When a relationship is required, explicitly load it using methods such as with() or load(). This makes your queries more predictable and helps prevent unnecessary database calls.
So if you are building a Laravel application, enabling lazy loading prevention in your local environment is a simple habit worth adopting. 🚀
Thank you for reading this article 😊
For any query, do not hesitate to comment 💬
-compressed.jpg)