Key Takeaways
- The Null Object Pattern is a design pattern that uses polymorphism to reduce conditional code, making it cleaner and easier to maintain. It provides a non-functional object that can be used in place of a real object, eliminating the need for null value checks.
- The Null Object Pattern can be used with other design patterns, such as the Factory Pattern to create and return null objects, or the Strategy Pattern to change the behavior of an object at runtime.
- A potential drawback of the Null Object Pattern is that it can lead to the creation of unnecessary objects, which can increase memory usage. It can also make the code more complex, as it requires the creation of additional classes and interfaces.
- Implementing the Null Object Pattern involves creating a null object class that implements the same interface as the real object. This null object provides default implementations for all the methods in the interface, allowing it to be used in place of a real object. This makes the code more robust and less prone to errors.
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.
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 previousUser
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 FotoliaFrequently Asked Questions about the Null Object Pattern in PHP
What is the main purpose of the Null Object Pattern in PHP?
The Null Object Pattern in PHP is a design pattern that uses polymorphism to reduce the amount of conditional code. It is used to provide a default behavior when a method call returns a null object. Instead of checking for a null value, the Null Object Pattern provides a non-functional object that can be used in place of a real object. This reduces the need for conditional statements and makes the code cleaner and easier to maintain.
How does the Null Object Pattern improve code readability?
The Null Object Pattern improves code readability by reducing the number of conditional statements. Instead of having to check for null values, you can simply call methods on the returned object. This makes the code easier to read and understand, as it follows a more linear flow.
Can the Null Object Pattern be used with other design patterns?
Yes, the Null Object Pattern can be used in conjunction with other design patterns. For example, it can be used with the Factory Pattern to create and return null objects. It can also be used with the Strategy Pattern to change the behavior of an object at runtime.
What are the benefits of using the Null Object Pattern in PHP?
The Null Object Pattern offers several benefits. It reduces the amount of conditional code, making the code cleaner and easier to maintain. It also improves code readability and reduces the risk of null pointer exceptions. Furthermore, it provides a consistent way to handle null values, making the code more robust and reliable.
Are there any drawbacks to using the Null Object Pattern?
While the Null Object Pattern has many benefits, it also has a few drawbacks. One potential drawback is that it can lead to the creation of unnecessary objects, which can increase memory usage. Additionally, it can make the code more complex, as it requires the creation of additional classes and interfaces.
How does the Null Object Pattern relate to polymorphism?
The Null Object Pattern uses polymorphism to provide a default behavior when a method call returns a null object. This is done by creating a null object class that implements the same interface as the real object. The null object class provides default implementations for all the methods in the interface, allowing it to be used in place of a real object.
Can the Null Object Pattern be used in other programming languages?
Yes, the Null Object Pattern is not specific to PHP and can be used in any object-oriented programming language. The implementation details may vary depending on the language, but the basic concept remains the same.
How does the Null Object Pattern handle errors?
The Null Object Pattern handles errors by providing a default behavior when a method call returns a null object. Instead of throwing an error or exception, the null object simply does nothing. This makes the code more robust and less prone to errors.
How can I implement the Null Object Pattern in my own code?
Implementing the Null Object Pattern involves creating a null object class that implements the same interface as the real object. The null object class provides default implementations for all the methods in the interface. When a method call returns a null object, the null object is used in place of the real object.
What are some examples of the Null Object Pattern in real-world applications?
The Null Object Pattern is commonly used in applications that need to handle null values in a consistent and robust way. For example, it can be used in database applications to handle null values returned from queries. It can also be used in GUI applications to provide default behavior when a user action does not correspond to a real object.
Alejandro Gervasio is a senior System Analyst from Argentina who has been involved in software development since the mid-80's. He has more than 12 years of experience in PHP development, 10 years in Java Programming, Object-Oriented Design, and most of the client-side technologies available out there.