Matthias Noback
January 18, 2016
In the previous part of this series we looked at what are basically a lot of guidelines for achieving “clean code”. In this part I’d like to take a closer look at something we call. Our main goal will be: to get rid of it.
The meaning of null
Actually, there’s not one meaning we can ascribe to as a return value, it could mean:
- “I don’t know what I should return to you, so I’ll return .”
- “What I’d like to return to you doesn’t make sense anyway, so I’ll return .”
- “What you were expecting to find isn’t there, so I’ll return .”
- “I never return anything meaningful (because I’m a .”
I’m sure there are even more situations you can think of.
In the case of being passed for function arguments, it could mean:
- “Sorry, I don’t know what to provide, so I’ll provide .”
- “It seems this argument is optional, so I’ll provide .”
- “I got this value from someone else (it’s ), I’ll just pass it along now.”
All of this may seem funny, but it’s not if your code is actually having these kinds of conversations. It’s certainly a good idea to get rid of the uncertainty and vagueness that value in your program, you probably weren’t expecting it. You just call a method on it, thinking that it is an object and PHP will rightfully let your program crash.
Solutions
Every particular value in your code, try to pick one of the following strategies. The general guideline is to make sure that a value can always be used in the same way.
Use “null” equivalents
In the case of primitive variables (of type values:
- When your function usually returns an .
- When your function usually returns an .
- …
You get the idea. See what makes sense in your case (maybe does). If you only supply values of the expected types you can always use the same operators, functions, etc. on any value no matter what. For example, instead of:
<?php
if (is_array($values)) {
foreach ($values as $value) { ...}
}
you can always do:
<?php
foreach ($values as $value) { ... }
PHP (before version 7) only allows you to enforce the correct types of function arguments if their expected types are objects or arrays, in which case passing a won’t work. This means that primitive types should still be validated in your function body:
<?php
function (ClassName $a, array $b, $c) { // $a and $b can't be null, but $c can:
if (!is_int($c)) {
throw new \\InvalidArgumentException(...);
}
}
This will of course lead to a lot of extra (duplicate) code, so again I recommend you to use an assertion library, like beberlei/assert:
<?php
function (ClassName $a, array $b, $c) {
Assertion::integer($c);
}
This will prevent many mistakes or implicit type conversions down the road.
Use “null” objects
If you expect a value to be of type .
In many cases you can prevent this problem by introducing so-called “null objects”. Where you would normally return instead of an object of a given type, you now return another object of the same type (or subtype). This often (but not always) requires the introduction of an interface which serves as the shared type between actual objects and null objects.
Null services
A much seen example is that of optionally injecting a logger in a service-like object or method:
<?php
function doSomething(Logger $logger = null)
{
if ($logger !== null) {
$logger->debug('Start doing something');
}
...
}
The provided logger can be any concrete implementation of the which does nothing:
<?php
class NullLogger implements Logger
{
public function debug($message) { // do nothing }
}
This works very well since the implementation of all of the methods of such a collaborating service can be left empty: these methods are so-called command methods with a type return value.
Null data objects
It can be a bit more tricky to replace values in objects that hold interesting data, like domain or view model objects. These objects have (almost) no command methods, only query methods, meaning they have an informational return value. In these cases you’ll need to provide default or “null” implementations which follow the same contract and offer useful data. Consider the following code, which asks a factory to create a view model for the current user and prints its name:
<?php
$userViewModel = $userViewModelFactory->createUserViewModel();
if ($userViewModel === null) {
echo 'Anonymous user';
} else {
echo $userViewModel->getDisplayName();
}
We can get rid of the always return an object which has the same contract as the user view model for a logged in user:
<?php
// shared interface
interface UserViewModel
{
/* @return string */
public function getDisplayName();
}
// view model for logged in users
class LoggedInUser implements UserViewModel
{
public function getDisplayName()
{
return $this->displayName;
}
}
// "null object" implementation
class AnonymousUser implements UserViewModel
{
public function getDisplayName()
{
return 'Anonymous user';
}
}
class UserViewModelFactory
{
public function createUserViewModel()
{
if ( /* user is logged in */) {
return new LoggedInUser( ...);
}
return new AnonymousUser();
}
}
Now value being returned:
<?php
$userViewModel = $userViewModelFactory->createUserViewModel();
echo $userViewModel->getDisplayName();
If the “null object” is a special case of the normal object you may define a base class and extend it, instead of letting both classes implement a shared interface.
Throw exceptions
In some cases it isn’t possible or rational to return a null-equivalent value or null object. Returning anything else than what the client expects would be a lie. For example in the following class:
<?php
class UserRepository {
public function getById($id) {
if (/* user was not found */) {
return null;
}
return new User(...);
}
}
The object (because it expected the provided identifier to be valid). In case that user can’t be found, this is exceptional and we need to let the user know what went wrong. In other words, we should throw an exception:
<?php
class UserRepository
{
public function getById($id)
{
if ( /* user was not found */) {
throw UserNotFound::withId($id);
}
return new User(...);
}
}
class UserNotFound extends \\RuntimeException
{
public static function withId($id)
{
return new self(sprintf('User with id "%s" was not found', $id));
}
}
(See Formatting Exception Messages by Ross Tuck for more information about the above practice of using named constructors and encapsulated message formatting for exceptions.)
Encapsulate null
I don’t think we can go so far as to completely exterminate into a meaningful boolean:
<?php
class Value {
private $value;
public function isDefined() {
return $this->value !== null;
}
}
Or make sure that clients don’t have to worry about a middle name that’s :
<?php
class Name
{
private $firstName = 'Matthias';
private $middleName = null;
private $lastName = 'Noback';
public function fullName()
{
return implode(
' ',
array_filter(
[
$this->firstName,
$this->middleName,
$this->lastName
]
)
);
}
}
When you want to allow your objects to be constructed without certain information, you can hide the fact that you’re using s in that case by offering alternative named constructors:
<?php
class Person
{
private $name;
public static function fromName(Name $name)
{
$person = new self();
$person->name = $name;
return $person;
}
public static function anonymous()
{
$person = new self(); // $person->name will remain null return $person;
}
}
Conclusion
In this article we’ve seen several ways in which you can get rid of s in your code. When you apply these techniques combined with the ones from the previous article, the complexity of your code will be greatly reduced.
So far we’ve discussed statements and expressions in function bodies. In the next article we’ll take a look at different types of objects and their lifecycles. We’ll discuss object creation, references and state changes.