DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Refactoring: Introduce Null Object

Practical PHP Refactoring: Introduce Null Object

Giorgio Sironi user avatar by
Giorgio Sironi
·
Oct. 26, 11 · Interview
Like (0)
Save
Tweet
Share
11.77K Views

Join the DZone community and get the full member experience.

Join For Free

In the scenario of today, we see repeated checks for an object's equality to null, false or another scalar value without behavior. These checks take a form like !== null and !== false in PHP. These multiple checks are the sign that a relevant case is not modelled with an object: all the logic pertaining to a null value for that role is being spread between its client objects.

Introducing a Null Object specifies clearly a point where to gather all this logic.

Null Object? null isn't an object

The Null Object pattern provides a way for making a null value and a real object indistinguishable: by making them support the same method calls.

You are already using Null Objects all the time. But they aren't objects:

$sum = 0;
foreach ($rows as $row) {
    $sum += $row['amount'];
}

$sum is a Null Object that supports the sum (null usually can in case of integers due to implicit casts, but not in all languages).

Without it, you would write continuous checks for nullity:

$sum = null;
foreach ($rows as $row) {
    if ($sum) {
        $sum += $row['amount'];
    } else {
        $sum = $row['amount'];
    }
}

This is another simple example:

$list = array();
foreach ($list as $element) {
    $this->doSomethingWith($element);
}

The logic of the Null Object pattern is the same, but extended to your own types instead of the interpreter's one. Your classes will have "special case" objects to pass around, instead of plain old arrays having a special case in the empty instance.

Refactoring towards a Null Object is a particular case of refactoring towards polymorphism: providing a new implementation of the contract of a class. This particular case features special semantics, like the absence of multiple instances of Null Objects.

A scenario in which this refactoring is badly needed is when the checks for nullity become repeated in different places in the codebase. Null Objects can be a subclass of a concrete class, or an alternate implementation; it's difficult to break the Liskov Substitution Principle with a Null Object. I'll stick with the subclass in the example as it is less invasive.

Steps

  1. Create a subclass of your concrete class.
  2. Add an isNull() method, which returns true in the case of the original class and false in its overridden version.
  3. When a null or false is returned, return a new instance of the Null Object. Usually a Flyweight implementation is enough: all Null Objects of a certain concrete class are equal (otherwise they become real objects, just a different example of polymorphism.)
  4. Find all comparisons and use isNull() instead of null checks. My alternative to this procedure introduce by Fowler is to use *instanceof NullObjectSubclass* as a rudimental isNull() when you're sure it will be gone by the end of the refactoring. It's not really useful to replace the *=== null* with an equivalent boolean method which will be replaced again shortly.
  5. Look for cases where an operation is invoked only on a real object, and move that code into the original class.
  6. Look for cases where an operation is invoked when the object is null, and move it into the Null Object. Pass as parameters the depenencies you find.

When you have moved all the code containing operations to execute only in one of the two cases, you can actually remove the conditionals. The refactoring has ended.

Example

The problem is similar to the previous example, but this time we haven't got a hierarchy to break up the conditional into. We can't move a method on null, right?

<?php
class IntroduceNullObject extends PHPUnit_Framework_TestCase
{
    public function testAUserWithAGroupShowsHisAffiliation()
    {
        $user = new User('giorgio', new Group('Engineers'));
        $this->assertEquals('giorgio belongs to Engineers', $user->getDescription());
    }

    public function testAUserWithoutAGroupDoesNotHaveABadge()
    {
        $user = new User('giorgio', null);
        $this->assertEquals('giorgio does not belong to a group yet', $user->getDescription());
    }
}

class User
{
    private $name;
    private $group;

    public function __construct($name, Group $group = null)
    {
        $this->name = $name;
        $this->group = $group;
    }

    public function getDescription()
    {
        if ($this->group === null) {
            return $this->name . ' does not belong to a group yet';
        }
        return $this->name . ' belongs to ' . $this->group->getName();
    }
}

class Group
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

Again, we create a hierarchy and add a conditional evaluating instanceof. Note that instanceof Group won't work as the Null Object is also an instance of Group.

There are little changes to make also in the relevant test (which does not pass null, but an instance of the Null Object) and in the type hint, which does not accept null anymore.

<?php
class IntroduceNullObject extends PHPUnit_Framework_TestCase
{
    public function testAUserWithAGroupShowsHisAffiliation()
    {
        $user = new User('giorgio', new Group('Engineers'));
        $this->assertEquals('giorgio belongs to Engineers', $user->getDescription());
    }

    public function testAUserWithoutAGroupDoesNotHaveABadge()
    {
        $user = new User('giorgio', new NoGroup);
        $this->assertEquals('giorgio does not belong to a group yet', $user->getDescription());
    }
}

class User
{
    private $name;
    private $group;

    public function __construct($name, Group $group)
    {
        $this->name = $name;
        $this->group = $group;
    }

    public function getDescription()
    {
        if ($this->group instanceof NoGroup) {
            return $this->name . ' does not belong to a group yet';
        }
        return $this->name . ' belongs to ' . $this->group->getName();
    }
}

class Group
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

class NoGroup extends Group
{
    public function __construct() {}
}

Now we can move the different behavior on the hierarchy of Group and its subclass. For more detail in the intermediate steps, see the previous refactoring to polymorphism; it's not difficult to move code in the Group classes once we abandon a bias towards keeping them without dependencies. Note that any unwanted dependency introduced by this refactoring can and should be broken with a small interface.

class User
{
    private $name;
    private $group;

    public function __construct($name, Group $group)
    {
        $this->name = $name;
        $this->group = $group;
    }

    public function getDescription()
    {
        return $this->group->belonging($this->name);
    }
}

class Group
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function belonging($name)
    {
        return $name . ' belongs to ' . $this->name;
    }
}

class NoGroup extends Group
{
    public function __construct() {}

    public function belonging($name)
    {
        return $name . ' does not belong to a group yet';
    }
}
Object (computer science) PHP

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Simulating and Troubleshooting BLOCKED Threads in Kotlin [Video]
  • How To Use Linux Containers
  • DevOps for Developers — Introduction and Version Control
  • 10 Most Popular Frameworks for Building RESTful APIs

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: