Skip to content

Laravel error

Laravel: Attempt to read property on null

The error

Attempt to read property "name" on null

PHP 8 turned a notice into a warning and a great deal of code into visible breakage. The message names the property, not the reason it is missing, and the reason is what you need.

The error

ErrorException

Attempt to read property "name" on null

In PHP 7 the same code produced a notice, an empty string, and a page that looked almost right. PHP 8 made it a warning, which in a Laravel application with strict error handling becomes an exception and a 500.

The code did not get worse. It got visible.

What it means

Something was null, and a property was read from it. That is the whole message, and it names the property rather than the variable - so Attempt to read property "name" on null tells you a ->name was read, not which object was missing.

The useful question is why it was null, and there are four distinct answers with four different fixes.

1. The record does not exist

$user = User::find($id);
echo $user->name;

find() returns null when nothing matches. If the record is genuinely expected to be there, say so and get a 404 instead of a 500:

$user = User::findOrFail($id);

Route model binding does this for you, which is one reason to prefer it:

Route::get('/users/{user}', fn (User $user) => view('profile', compact('user')));

2. The relation is not loaded

$order = Order::find($id);       // no eager load
echo $order->customer->name;     // null, if lazy loading is disabled

This is the one that confuses people, because the customer does exist. What does not exist is the loaded relation. With lazy loading disabled - as it should be outside production - accessing it returns null rather than issuing a query.

The fix is to load it where the model is retrieved:

$order = Order::with('customer')->findOrFail($id);

3. The relation exists and is empty

A hasOne or belongsTo that is legitimately optional - an order with no assigned agent, a user with no profile yet. Here null is correct and the code has to handle it:

{{ $order->agent?->name ?? 'Unassigned' }}

This is where the null-safe operator belongs. Using it everywhere, as a reflex against this error, is how the first two cases get hidden instead of fixed.

4. The chain is longer than you think

$invoice->order->customer->address->city

Any link can be null, and the message names only the last property read. Four possible causes, one message.

Where a chain like this appears in a template, the better answer is usually to stop building it there - a computed attribute or a view model resolves it once, in one place, where it can be tested.

Finding the actual line

The exception is thrown where the property was read, which in a Blade template means a compiled view path rather than your source. Look further down the stack trace for the .blade.php file, or set a breakpoint on the compiled line - the top frame is rarely the useful one.

In production, where the page is a generic error, the log entry has the same trace. If it does not have enough context to identify the record, that is worth fixing separately: an exception with no id in it is an exception nobody can reproduce.

Preventing the category

Three habits remove most of these permanently.

findOrFail and route model binding, so a missing record is a 404 rather than a null travelling into a template. Lazy loading disabled outside production, so an unloaded relation fails in a test rather than rendering blank. And nullable columns that the application never expects to be null made non-nullable in the schema, so the impossible state is actually impossible.

The same message inside a queued job usually has a different cause. The property was not null when the job was dispatched; it was dropped on the way to the worker, which makes it a serialisation problem.

Most of that list is schema work, and fixing nullability on a table that already has data takes planning. The lazy-loading habit has an exception of its own: Laravel will name the unloaded relation if you let it.

Related questions

Is the null-safe operator the fix?
It is a way to stop the message, which is not the same thing. ?-> is right where null is a legitimate, expected state - an optional relation, a nullable field. Where null means something went wrong, silencing it just moves the failure somewhere less informative, usually into a report with a blank in it.
Why did this start after a PHP upgrade?
Because PHP 8 raised reading a property on null from a notice to a warning. The code was always wrong; the difference is that earlier versions produced an empty string and carried on, so the fault was invisible until the output looked odd to somebody.
It happens in Blade but the controller looks fine.
Then the relation is probably not loaded rather than not existing. An unloaded relation on a model that was retrieved without it returns null when lazy loading is off - which is a different problem from a record that is genuinely missing, and it has a different fix.
Should we make the column non-nullable instead?
Often yes, and it is the fix people skip because it means touching the schema. A column that is nullable but that the application treats as always present is a bug waiting on the right row - and a constraint makes that impossible rather than unlikely.
Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page