Skip to content

Laravel error

Laravel: Attempted to lazy load a relation

The error

Attempted to lazy load [customer] on model [App\Models\Order] but lazy loading is disabled.

What the LazyLoadingViolationException is actually telling you, why it is a feature rather than a bug, and the three fixes - only one of which is usually right.

The error

Illuminate\Database\LazyLoadingViolationException

Attempted to lazy load [customer] on model [App\Models\Order] but lazy loading
is disabled.

What it means

Somewhere in your code, $order->customer was accessed on a model that was retrieved without that relation being loaded. Eloquent would normally go and fetch it - silently, one query at a time - and somebody has turned that behaviour off:

// AppServiceProvider::boot()
Model::preventLazyLoading(! app()->isProduction());

So this is not a bug report. It is a feature doing its job: the query that would have been issued invisibly is now an exception with a stack trace pointing at the line that caused it.

The fix

Load the relation where the models are retrieved, not where they are used.

// before
$orders = Order::latest()->take(50)->get();
 
// after
$orders = Order::with('customer')->latest()->take(50)->get();

If the access is in a Blade view or an API resource and you cannot see which query produced the model, the stack trace above the exception will name the controller or job that did.

For a relation you need only sometimes, load it conditionally at the point of retrieval rather than lazily at the point of use:

$orders = Order::with($includeCustomer ? ['customer'] : [])->get();

And if what you actually want is a number rather than the rows, do not eager load at all:

// not ->with('items') then ->items->count()
$orders = Order::withCount('items')->get();

What looks like a fix and is not

Turning the check off. preventLazyLoading(false) makes the exception disappear and leaves the query where it was. You have restored the silence, not the performance. If this was found in CI, the pull request that would have been blocked is now merged.

Calling load() inside the loop. $order->load('customer') satisfies the check because the load is now explicit - and it issues exactly the same query per iteration. The exception was about the query, not about the syntax.

Adding the relation to $with on the model. This makes the relation load on every retrieval of that model, everywhere in the application, including the dozen places that never touch it. It fixes one page and adds a join to everything else.

Catching the exception. We have seen this. It is not a mistake anyone makes twice.

Making sure it stays fixed

The exception catches the fault the first time. A query-count assertion catches it on the day somebody removes the eager load again:

DB::enableQueryLog();
$this->get('/orders')->assertOk();
expect(DB::getQueryLog())->toHaveCount(4);

The full picture on N+1s in Laravel - where they hide, and when eager loading is the wrong answer.

The same unloaded relation, met from the other side, is a request that runs out of memory. The loop that would have issued a thousand queries loads a thousand models instead and holds them all.

Related questions

Can I just turn preventLazyLoading off?
You can, and it is almost always the wrong call. The exception is not the problem - it is the report of a problem that was already there and was previously invisible. Switching it off restores the silence, not the performance.
Why does this only happen in development?
Because the usual configuration enables the check everywhere except production, so that a code path nobody tested degrades in production rather than 500s. If you are seeing it in production, somebody enabled it there deliberately.
Does this mean I have an N+1?
Not necessarily - a single lazy load on a detail page is one extra query, which is fine. It means you have an unplanned query. Whether it is an N+1 depends on whether the code around it is a loop.
Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page