It usually means that the related record is NULL or soft-deleted. There are multiple ways to gracefully handle such a situation, without showing the error to the user.
A practical example would be a list of Posts with belongs To relation to Categories.
Migration file for Posts:
1 2 3 |
Schema::table('posts', function (Blueprint $table) { $table->foreignId('category_id')->nullable()->constrained(); }); |
app/Models/Post.php:
1 2 3 4 5 6 7 |
class Post extends Model { public function category() { return $this->belongsTo(Category::class); } } |
app/Http/Controllers/PostController.php:
1 2 3 4 5 6 7 8 9 10 11 |
use App\Models\Post; class PostController extends Controller { public function index() { $posts = Post::with('category')->get(); return view('posts', compact('posts')); } } |
resources/views/posts.blade.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<table> <thead> <tr> <th>Title</th> <th>Category</th> <th>Created at</th> </tr> </thead> <tbody> @foreach($posts as $post) <tr> <td>{{ $post->title }}</td> <td>{{ $post->category->name }}</td> <td>{{ $post->created_at }}</td> </tr> @endforeach </tbody> </table> |
Now, have you noticed that migration says the category_id
field is nullable()?
So yeah, the code of $post->category->name
would throw the error in that case:
Attempt to read property “name” on null
What are our options to handle this nullable field?
Option 1. ‘??’ Operator in Blade
Just replace this:
1 |
{{ $post->category->name }} |
With this:
1 |
{{ $post->category->name ?? '' }} |
It’s a PHP so-called “Null Coalescing Operator”
Instead of the empty string, you may provide any value you want:
1 |
{{ $post->category->name ?? 'No category' }} |
Option 2. optional() in Blade
A similar fix comes not from PHP but from the Laravel layer, in the form of optional()
helper.
Just replace this:
1 |
{{ $post->category->name }} |
With this:
1 |
{{ optional($post->category)->name }} |
It will show the empty string for the name, without throwing any errors.
It acts almost the same as the Null Coalesce Operator above, with the slight difference of intention how you use it.
Option 3. PHP 8: Nullsafe Operator
A newer addition to PHP language is an operator that allows us to call the chained objects or even methods, without being afraid of them returning null.
As Brent points out in his article on the Nullsafe Operator, PHP 8 allows you to write this:
1 |
$country = $session?->user?->getAddress()?->country; |
So, in our case, we can have a one-character solution!
Just replace this:
1 |
{{ $post->category->name }} |
With this:
1 |
{{ $post->category?->name }} |
It will also return an empty string, without errors.
Option 4. withDefault() in Model
All the options above work on the Blade level, or wherever you present that relationship. But what if you call it multiple times in different parts of the codebase, and you want to define that relation behavior once, instead, by providing some default values?
You can add a default “fallback” model inside of the related method.
app/Models/Post.php:
1 2 3 4 5 6 7 |
class Post extends Model { public function category() { return $this->belongsTo(Category::class)->withDefault(); } } |
Then, in case of the related model not existing, Eloquent will artificially create an empty model of Category. It would then not trigger the PHP error of “property on null“, because the object is not null. It has empty data inside, but not null anymore.
Not only that, you can specify the default values as an array inside of that withDefault()
model:
1 2 3 4 5 6 |
public function category() { return $this->belongsTo(Category::class)->withDefault([ 'name' => 'No category' ]); } |
This option is more suitable as a global solution when you don’t want to describe the defaults everywhere you would use it in Blade files or other places in code. Define it once in the Model and kinda forget about it.