Null Object Pattern in Laravel
There are many times when we take for granted the relations in our database or some fields in an external API, CSV file or other data source. And in such situations, we often receive some kind of NullPointerException.
Laravel with its convenient ORM might bring us in such troubles unless we are careful. Imagine a simple blog site where you have users who can write posts and you display the author’s name under the title. With Laravel, you just write $post->author->name, pretty cool right? But what happens when the author deletes his account. You might consider one of the following:
- Delete all user’s posts, but why losing such useful information without a valid reason?
- Denormalize the posts table and save some of the user information there, for example, the name, so you never need to worry, if the user is there.
- Use soft deletes and never delete the user, but this might be a problem with the new GDPR regulations. Furthermore, you need to remember to load the relation with the trashed records. You can fix these problems if on delete you rename the user to “Unknown” and assign them a random email and password, but this is logic write.
- Create a method on the blog post class which returns the author’s name, where you check for its existence and return some sensible default.
As you can see, there are a lot of possible solutions and depending on the case you might need some of them or even another one which is not listed here. But I would like to present you an alternative, that you can use when you think you need it.
The Null Object Pattern is a design pattern, where some function/method returns an object or literal, that should act as a “null” version of the expected one. You can read more on Wikipedia about it. A simple example might be a function, that should return an array of items, which you want to filter, map over its items or just pass it into foreach. But what happens when there are no items to return? You might return null, but now you need to check, every time before performing, that foreach loop. The null object alternative would be to return an empty array and skip the check, just run the foreach.
If you are using such techniques, you are implementing the pattern, so congrats on that.
You can find the pattern implemented in many places in the framework. Some examples are the get() method of the Models, which always returns Collection, no matter if there are records or not, or the magic methods of the models, which allows you to access not existing properties. But more interesting are the methods, that allows you to specify, your own version of the null value. Here are some examples of such methods:
You can specify a default value to be returned, on the “to one” relations, as they would return null by default. This is done via the withDefault() method when defining the relation, which can even accept an array of params to make the model with, so it has some sensible defaults. If you don’t pass anything when no User is found, empty one will be returned.
Now we can call $post->author->name, without worrying if there is a user, and when a user deletes its account we can just delete it.
Of course, there are other solutions to the problem and you can argue if the default name should be specified in the relation method, or in some method for determining the author’s name. Here is how it might look using the ?? operator:
Bellow, I will describe some other functions which utilize the null object pattern across the framework.
They are really useful, when dealing with responses from other systems, reading files, accepting requests or every time you are not sure that the value will be there. There are two functions array_get and object_get for the different types of data structures. You should use array_get for associative arrays, no matter that object_get seems logical there. Here are 2 quick examples for both of them:
- object_get($cloth, ‘brand’, ‘unknown brand’) — returns the brand property of the cloth object, if such exists, or unknown brand
- array_get($colors, ‘black’, ‘000000’) — returns the value for the black key of the colors array or our default black value
- another option is to simply use the ?? operator, which was introduced in php7, and simplify the code, even more, $colors[‘black’] ?? ‘000000’
Of course, you can use them without the default values and they will return null but will prevent exceptions about not defined indexes and items.
You can call the get() method on the $request object to receive the value of some input field, but it also accepts a second parameter, which is the default value returned, when the property does not exist. So $request->get(‘wants_emails’, false) return the value of wants_emails or false, if such is not defined. It could be also used with the request(‘wants_emails’, false) function if you prefer to use it.
Sometimes you might want to create “null” classes, which mimics the methods of the real implementation, but does “null”. You can implement all methods of the mimicked class or just a handful of them. This might be especially helpful in tests to replace some object. Another place to use the pattern might be when you have many similar classes and you don’t want to break the flow with null checks. If you create a class, that is instantiated in the null case, you can use it like the others and not clutter the code with null checks. In those cases, you can even implement common interfaces, if you are into it since it is a real class.
The optional() helper is another function, which helps to deal with nulls in the code, even tho it does not provide a way to specify a default value. Here is a little example where it can save us from unexpected exceptions, optional($user->address)->city. This way we will receive null even when there is no address property on the user class.
In conclusion, I hope that the next time you are wondering, how to deal with the possibility, of deleted relation or missing property of an API response, you would have one more weapon in your arsenal and don’t have to write if statement, because nobody likes them.
Originally published at donatix.net on September 28, 2018.