PHP
Article
By Alejandro Gervasio

The Null Object Pattern – Polymorphism in Domain Models

By Alejandro Gervasio

While certainly a far cry from being canonical, it can be said that Orthogonality is the quintessence of software systems that rest on the foundations of “good design”, where the constituent modules are neatly decoupled from one another, making the system less vulnerable to rigidity and fragility issues. Of course, it’s easier to chatter on about the benefits that orthogonal systems than getting an actual system up and running in production; the process is often a pursuit of utopia.

Even though, the implementation of highly-decoupled components in a system is everything but a utopian concept. The use of several programming concepts, such as Polymorphism, permit one to design flexible programs whose parts can be switched over at runtime and whose dependencies can be expressed in the form of abstractions rather than concrete implementations. I’d dare say the old “Programming to Interfaces” mantra has gained ubiquitous adoption over time, regardless of if we’re talking about implementing infrastructure or application logic.

Things, however, are radically different when stepping on the terrain of Domain Models. And to be frank, this is a predictable scenario. After all, why should a web of interconnected objects, whose data and behavior are constrained to well-defined business rules, be polymorphic? It doesn’t make much sense per se. There are a few exceptions to this rule, though, that might be eventually applicable to this case in particular.

The first exception is the use of Virtual Proxies, which effectively share the same interface as the one implemented by the actual domain objects. The other exception is the so-called “Null Case”, a special situation where an operation might end up assigning or returning a null value instead of a nicely populated entity. In a traditional, non-polymorphic approach, the consumer of the model must check for these “evil” nulls and handle the condition gracefully, thus generating an explosion of conditionals throughout your code.

Fortunately, this seemingly-tangled situation can be sorted out simply by creating a polymorphic implementation of the domain object, which would implement the same interface as the one of the object in question, only that its methods wouldn’t do anything, therefore offloading client code from doing repetitive checks for ugly null values when the operation is executed. Not surprisingly, this approach is a design pattern called Null Object, which takes the advantages of Polymorphism to extremes.

In this article I’ll demonstrate the pattern’s benefits in a few situations and show you how sweet they cling to a polymorphic approach.

Handling Non-Polymorphic Conditions

As one might expect, there are several paths to tread when showcasing the niceties of the Null Object pattern. One that I find particularly straightforward is the implementation of a data mapper which may eventually return a null value from a generic finder. Let’s say that we’ve managed to create a skeletal domain model made up of just one single user entity. The interface, along with its class, look like this:

<?php
namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();
     
    public function setName($name);
    public function getName();
    
    public function setEmail($email);
    public function getEmail();
}
<?php
namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }
    
    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setName($name) {
        if (strlen($name) < 2 || strlen($name) > 30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }
    
    public function getName() {
        return $this->name;
    }
    
    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }
    
    public function getEmail() {
        return $this->email;
    }
}

The User class is a reactive structure which implements some mutators/accessors in order to define the data and behavior of a few users.

With this contrived domain class in place, now we can go a step further and define a basic Data Mapper which will keep our domain model and the data access layer isolated from each other.

<?php
namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;
    
    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }
    
    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        return $this->createUser($row);
    }
    
    private function createUser(array $row) {
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user;
    }
}

The first thing that should have popped out is that the mapper’s fetchById() method is the naughty boy on the block as it effectively returns a null if no user in the database matches the given ID. For obvious reasons, this clunky condition makes the client code go through the hassle of checking for the null value every time the mapper’s finder is called.

<?php
use LibraryLoaderAutoloader,
    LibraryDatabasePdoAdapter,
    ModelMapperUserMapper;
    
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$adapter = new PdoAdapter("mysql:dbname=test", "myusername", "mypassword");

$userMapper = new UserMapper($adapter);

$user = $userMapper->fetchById(1);

if ($user !== null) {
    echo $user->getName() . " " . $user->getEmail();
}

At first glance that wouldn’t be much of an issue, so long as the check is done in a single place. But wouldn’t you hit your head against a brick wall if the same lines appear across multiple page controllers or inside a service layer? Before you know it, the seemingly-innocent null returned by the mapper produces a plague of repetitive conditions, an ominous sign of bad design.

--ADVERTISEMENT--

Removing Conditionals from Client Code

There’s no need for anguish, however, as this is exactly the kind of case where the Null Object pattern shows why Polymorphism is a godsend. If we want to get rid of those pesky conditionals once and for all, we can implement a polymorphic version of the previous User class.

<?php
namespace Model;

class NullUser implements UserInterface
{
    public function setId($id) { }
    public function getId() { }
    
    public function setName($name) { }
    public function getName() { }
    
    public function setEmail($email) { }
    public function getEmail() { }
}

If you were expecting a full-fledged entity class packaging all sort of bells and whistles, I’m afraid you’ll mighty disappointed. The “null” version of the entity conforms to the corresponding interface, but the methods are empty wrappers with no actual implementation.

While the existence of the NullUser class apparently doesn’t buy us anything useful to praise, it’s a neat creature that allows us to throw all the previous conditionals into the trash. Want to see how?

First and foremost, we should do some up front work and refactor the data mapper so its finder returns a null user object instead of the null value.

<?php
namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser,
    ModelNullUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;
    
    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }
    
    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        return $this->createUser($this->adapter->fetch());
    }
    
    private function createUser($row) {
        if (!$row) {
            return new NullUser;
        }
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user; 
    }
}

The mapper’s createUser() method hides a tiny conditional as now it’s responsible for creating a null user whenever the ID passed into the finder doesn’t return a valid user. Even so, this subtle penalty not only saves client code from doing a lot of repetitive checks, but turns it into a permissive consumer which doesn’t complain when it has to deal with a null user.

<?php
$user = $userMapper->fetchById("This ID is invalid...");

echo $user->getName() . " " . $user->getEmail();

The major pitfall with this polymorphic approach is that any application consuming it would become too permissive, as it’d never crash when working with invalid entities. In the worst case, the user interface would display just a few blank lines here and there, but nothing really noisy to make us cringe. This is particularly evident when scanning the current implementation of the earlier NullUser class.

Even though, it’s feasible, not to say recommended, to encapsulate logic in null objects while keeping its polymorphic nature untouched. I’d even say that null objects are great candidates for encapsulating default data and behavior that should be only exposed to client code in a few special cases.

If you were ambitious enough and wanted to give this concept a try using naïve null user objects, the current NullUser class could be refactored in the following way:

<?php
namespace Model;

class NullUser implements UserInterface
{
    public function setId($id) { }
    public function getId() {
        return "The requested ID does not correspond to a valid user.";
    }
    
    public function setName($name) { }
    public function getName() {
        return "The requested name does not correspond to a valid user.";
    }
    
    public function setEmail($email) { }
    public function getEmail() {
        return "The requested email does not correspond to a valid user.";
    }
}

The enhanced version of NullUser is slightly more expressive than its quiet predecessor now that its getters provide some basic implementation to return a few default messages when an invalid user is requested.

Although trivial, this change has a positive impact in the manner that client code processes null users, as this time the consumers have at least a clear idea that something went wrong when they attempted to pull in a non-existent user from storage. That’s a nice breakthrough, which shows not only how to implement null objects that actually aren’t null at all, but also how easy it is to move logic inside the objects in question according to specific requirements.

Closing Remarks

Some might say that going through the hassle of implementing null objects is overkill, especially in PHP where central OOP concepts, such as Polymorphism, are blatantly underrated. They’d be right to some extent.

Even though, the progressive adoption of well-trusted programming principles and design patterns, along with the level of maturity currently reached by the language’s object model, provides all of the groundwork necessary for moving forward with firm steps and starting to use a few little “luxuries” which were considered tangled, impractical concepts not so long ago. The Null Object pattern falls under this category, but its implementation is so straightforward and elegant that it’s hard not to find it appealing when it comes to cleaning up client code from repetitive null value checks.

Image via Fotolia

More:
Recommended
Sponsors
The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account