Working with null objects in Laravel

Before starting the post I would like to say I’m not a “design patterns guru” or something like that. I’m just a passionate developer who loves clean code and elegant solutions, I come across a lot of issues every day and looking for solutions to share with the community is my way to deal with it. If you think my approach is not the best to deal with null return types or want to add something to my post, you can reach me on Twitter and I will be happy to start a polite discussion with you.

Intro

The problem: many times we don’t want to break the normal flow of the application even if we got a null instead of an expected object at some point.

But, what is a “null object”? Excellent question! A null object is an object that shares the same interface as another one but defines default values in it, so you can return it instead of a null value and don’t break the flow of your application.

Using null objects in Laravel

To demonstrate the usage of null objects we will be building a demo blog app, let’s do it.

Setting up the project

First we need to create the project:

composer create-project --prefer-dist laravel/laravel blog

Then configure the database in your .env file, I will be using SQLite but you can use whatever database engine you want.

DB_CONNECTION=sqliteDB_FOREIGN_KEYS=false

Don’t forget to create a file called database.sqlite in the database directory, and remove the DB_DATABASE key in your .env file.

We will use the default Laravel authentication, so run the following command in the root of your project.

php artisan make:auth

Now we will define the necessary models, controllers, factories, and migrations, by running the following commands.

At this point we can view a list of posts, an individual post, and an individual user by following the link in a specific post.

Now, what would happen if we delete a user? We didn’t define a cascade behavior on delete, and we don’t want that, we want the possibility of deleting a user but keep his/her posts, all this without breaking the application when someone follows the link to the user.

Introducing null objects

First of all we will delete the user with id 1, that way we can see how the application will blow up in one hundred pieces. We can achieve this using tinker, so we don’t need to write the functionality.

Now the application is broken and if you try to visit the /posts URL, you should see a message like “Trying to get property ‘id’ of non-object”, that is because there not exists a model for a User with id 1. We are going to introduce a null object for these cases.

Creating our first null object

As we said before, null objects should share the same interface as the real object, that’s the reason why our null object will inherit from the User class.

With this we are essentially creating a User model with a default name and email, note that we are not adding an id, we will address this shortly, now let’s use our null object in the relationship defined in the Post model, modify the user relationship to look like this.

As you can see we are adding an id property to the MissingUser model with the user_id set in the Post model.

At this point you should visit the /posts URL and see “Posted by: missing name” in the posts that belong to the deleted user, but the application is still working as expected. The only problem right now is that if you follow the link to the see the user information the application will respond with a 404, we don’t want that, we want to know the user is missing but we don’t want exceptional behavior, so let’s modify our UserController to return a MissingUser instead of a 404.

Now our application is fully functional without the need of deleting our records in cascade, or using a lot of guard clauses to verify the existence of an object, pretty neat!

Final thoughts

Laravel encourages developers to use null objects in models via the $attributes property as well as in relationships via the withDefault method, what I made here is just use a more traditional approach by defining a new class, but you can implement the same behavior without defining a custom class, I personally prefer to be as explicit as possible, but I could rewrite the whole article without the need for a custom class.

Post-credits

As always the application built in here is just for demonstration purposes, in a real application many things should be done differently. Also I’m not suggesting you should keep child rows without a parent in your database just to keep the application working as expected 100% of the time, as I said before this is just a demo, you should decide by yourself when to use this technique in your projects, I personally find myself using it frequently.