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.
