PHP - - By Alejandro Gervasio

If I ever had to make an ad-hoc “Solomonic” decision concerning the relevance of each SOLID principle, I would just dare say that the Dependency Inversion Principle (DIP) is the most underrated of all.

While some central concepts in the realm of object-oriented design are generally harder to digest at first, such as separation of concerns and implementation switching, more intuitive and untangled paradigms on the other hand are simpler, like programming to interfaces. Unfortunately, the DIP’s formal definition is surrounded by a double-edged curse/blessing that often makes programmers gloss over it, as in many cases there’s an implicit assumption that the principle is nothing but a fancy expression for the aforementioned “programming to interfaces” commandment:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend upon details. Details should depend upon abstractions.

At first blush, the above statements seem to be self-explanatory. Considering at this point no one would disagree that systems built upon a strong dependency on concrete implementations are an ominous sign of bad design, switching over a few abstractions makes perfect sense. So this would lead us right back to the start, thinking that the DIP’s main concern is all about programming to interfaces.

In reality, decoupling interface from implementation is just a half-baked approach when it comes to fulfilling the principle’s requirements. The missing portion is achieving the real inversion process. Of course, the question that comes up is: inversion of what?

In a traditional sense, systems were always designed to work with high-level components, be it classes or procedural routines, depending upon low-level ones (the details). For instance, a logging module could have a strong dependency on a set of concrete loggers down the chain which actually log information onto the system. It’s no surprise then this scheme would noisily ripple side effects up toward the upper layers whenever the protocol owned by the loggers is modified, even if the protocol has been abstracted away.

The implementation of the DIP though helps to mitigate to some extend these ripples by making the logging module own the protocol instead, therefore inverting the overall dependency’s flow. Upon inversion, the loggers should adhere faithfully to the protocol hence changing accordingly and accommodating themselves to its fluctuations if one ever occurs further down the road.

In a nutshell, this shows that the DIP is, under the hood, slightly more complex than just relying on the heaps of benefits brought by a standard interface-implementation decoupling. Yes it talks about making both high and low-level modules dependent on abstractions, but at the same time the high-level modules must have ownership of those abstractions – a subtle yet relevant detail that just can’t be overlooked so easily.

As you might expect, one way that may help you more easily understand what’s actually under the umbrella of the DIP is through a few hands-on code samples. Therefore, in this article I’ll be setting up some examples so you can learn how to get the most out of this SOLID principle when developing your PHP applications.

Developing a Naive Storage Module (the missing “I” in the DIP)

There are many developers, particularly those with an aversion to the chilly waters of object-oriented PHP, who tend to see the DIP and other SOLID principles as rigid dogma that pushes hard against the language’s inherent pragmatism. I can understand such thinking, as it’s rather difficult to find pragmatic PHP examples in the wild that demonstrate the principle’s real benefits. Not that I want to play myself up as an enlightened programmer (that suit just doesn’t fit well), but it’d be useful to strive for a good cause and demonstrate from a practical standpoint how to implement the DIP in a realistic use case.

To start things off, consider the implementation of a simple file storage module. The module is charged with the task of reading from and writing data to a specified target file. At a very minimalist level, the module in question could be written like this:

<?php
namespace LibraryEncoderStrategy;

class Serializer implements Serializable
{
    protected $unserializeCallback;

    public function __construct($unserializeCallback = false)  {
        $this->unserializeCallback = (boolean) $unserializeCallback;
    }

    public function getUnserializeCallback() {
        return $this->unserializeCallback;
    }
    
    public function serialize($data) {
        if (is_resource($data)) {
            throw new InvalidArgumentException(
                "PHP resources are not serializable.");
        }

        if (($data = serialize($data)) === false) {
            throw new RuntimeException(
                "Unable to serialize the supplied data.");
        }

        return $data;
    }
    
    public function unserialize($data) {
        if (!is_string($data) || empty($data)) {
            throw new InvalidArgumentException(
                "The data to be decoded must be a non-empty string.");
        }

        if ($this->unserializeCallback) {
            $callback = ini_get("unserialize_callback_func");
            if (!function_exists($callback)) {
                throw new BadFunctionCallException(
                    "The php.ini unserialize callback function is invalid.");
            }
        }

        if (($data = @unserialize($data)) === false) {
            throw new RuntimeException(
                "Unable to unserialize the supplied data."); 
        }
        
        return $data;
    }
}
<?php
namespace LibraryFile;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "default.dat";

    protected $serializer; 
    protected $file;

    public function __construct(Serializable $serializer, $file = self::DEFAULT_STORAGE_FILE) {
        $this->serializer = $serializer;
        $this->setFile($file); 
    }
    
    public function getSerializer() {
        return $this->serializer;
    }
    
    public function setFile($file) {
        if (!is_file($file) || !is_readable($file)) {
            throw new InvalidArgumentException(
                "The supplied file is not readable or writable.");
        }

        $this->file = $file;
        return $this;
    }
    
    public function getFile() {
        return $this->file;
    }
    
    public function resetFile() {
        $this->file = self::DEFAULT_STORAGE_FILE;
        return $this; 
    }
    
    public function write($data) {
        try {
           return file_put_contents($this->file, 
               $this->serializer->serialize($data));    
            
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
    
    public function read()
    {
        try {
            return $this->serializer->unserialize(
                @file_get_contents($this->file));
            
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
}

The module is a pretty naive structure, composed of just a few basic components. The first class reads and writes data to the file system, and the second is a simplistic PHP serializer used internally to generate a storable representation of the data.

These sample components neatly do their business in isolation, and could be wired together to work in sync as follows:

<?php
use LibraryLoaderAutoloader,
    LibraryEncoderStrategySerializer,
    LibraryFileFileStorage;    

require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$fileStorage = new FileStorage(new Serializer); 
$fileStorage->write(new stdClass());
print_r($fileStorage->read());

$fileStorage->write(array("This", "is", "a", "sample", "array"));
print_r($fileStorage->read());

$fileStorage->write("This is a sample string.");
echo $fileStorage->read();

At first glance, the module exhibits fairly decent behavior considering that its functionality allows to save and fetch a nice variety of data from the file system without much hassle. Moreover, the FileStorage class injects in the constructor a Serializable interface, thus depending on the flexibility provided by an abstraction rather than a rigid concrete implementation. With this slew of benefits shining bright on their own, what could be wrong with the module?

As usual, shallow first impressions can be tricky and blurred. Looking a bit closer, not only is it clear FileStorage actually depends on the serializer, but because of this tight dependency, storing and pulling in data from the target file is constrained to using only PHP’s native serializing mechanism. What would happen if the data must be passed along to an external service as XML or JSON? The carefully-crafted module isn’t reusable anymore. Sad but true!

The situation raises a few interesting points. First and foremost, FileStorage still exhibits a strong dependency on the low-level Serializer, even when the protocol that makes them interoperate with each other has been isolated from the implementation. Second, the level of genericity exposed by the protocol in question is very restrictive, limited to merely swapping out one serializer for another. Not only is depending on an abstraction a delusive perception in this case, but the real inversion process encouraged by the DIP is never achieved.

It’s possible to refactor some portions of the file module to make it adhere faithfully to the DIP’s mandates. In doing so, the FileStorage class would acquire the ownership of the protocol utilized to store and pull in file data, thus getting rid of the dependency on a lower-level serializer and affording you the ability to switch between several storage strategies at runtime. In doing so, you’d be actually gaining a lot of flexibility for free. Therefore, let’s continue along and see how to turn the file storage module into a true DIP-compliant structure.

Inverting Protocol Ownership and Decoupling Interface from Implementation (getting the most out of the DIP)

While there aren’t a plethora of options per se, nevertheless there are a few approaches that can be employed for effectively inverting the ownership of the protocol between the FileStorage class and its low-level collaborator while still keeping the protocol abstract. There’s one in particular, though, which turns out to be pretty intuitive because it relies on the natural encapsulation provided right out of the box by PHP’s namespaces.

To translate this somewhat intangible concept into concrete code, the first change that should be made to the module is to define a more relaxed protocol for saving and retrieving file data, that way it is easy to manipulate it through formats other than just PHP serialization.

A slim, segregated interface like the one shown below does the trick with elegance and simplicity:

<?php
namespace LibraryFile;

interface EncoderInterface
{
    public function encode($data);
    public function decode($data);
}

The existence of the EncoderInterface doesn’t seem to have a profound impact in the file module’s overall design, but it does a lot more than what it promises at face value. The first improvement is the definition of a highly-generic protocol for encoding and decoding data. The second, which is equally important as the first one, is that now the protocol’s ownership belongs to the FileStorage class since the interface lives and breaths in the class’ namespace. Simply put, we’ve managed to make still undefined low-level encoders/decoders depend on the high-level FileStorage just by writing a properly namespaced interface. In a nutshell, this is the actual inversion process that the DIP promotes behind its academic veil.

Naturally, the inversion would be a clumsy halfway attempt if the FileStorage class wasn’t amended to inject an implementer of the previous interface, so here’s the refactored version:

<?php
namespace LibraryFile;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "default.dat";
    
    protected $encoder; 
    protected $file;
    
    public function __construct(EncoderInterface $encoder, $file = self::DEFAULT_STORAGE_FILE) {
        $this->encoder = $encoder;
        $this->setFile($file); 
    }
    
    public function getEncoder() {
        return $this->encoder;
    }
    
    public function setFile($file) {
        if (!is_file($file) || !is_readable($file)) {
            throw new InvalidArgumentException(
                "The supplied file is not readable or writable.");
        }
        
        $this->file = $file;
        return $this;
    }
    
    public function getFile() {
        return $this->file;
    }
    
    public function resetFile() {
        $this->file = self::DEFAULT_STORAGE_FILE;
        return $this; 
    }
    
    public function write($data) {
        try {
           return file_put_contents($this->file,
               $this->encoder->encode($data));    
            
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
    
    public function read() {
        try {
            return $this->encoder->decode(
                @file_get_contents($this->file));
            
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
}

With FileStorage now declaring explicitly in the constructor the ownership of the encoding/decoding protocol, the only thing left to do is to create a set of concrete low-level encoders/decoders, thus allowing you handle file data in multiple formats.

The first of these components is just a refactored implementation of the PHP serializer written before:

<?php
namespace LibraryEncoderStrategy;

class Serializer implements LibraryFileEncoderInterface
{
    protected $unserializeCallback;
    
    public function __construct($unserializeCallback = false) {
        $this->unserializeCallback = (boolean) $unserializeCallback;
    }
    
    public function getUnserializeCallback() {
        return $this->unserializeCallback;
    }
    
    public function encode($data) {
        if (is_resource($data)) {
            throw new InvalidArgumentException(
                "PHP resources are not serializable.");
        }
        
        if (($data = serialize($data)) === false) {
            throw new RuntimeException(
                "Unable to serialize the supplied data.");
        }
        
        return $data;
    }
    
    public function decode($data) {
        if (!is_string($data) || empty($data)) {
            throw new InvalidArgumentException(
                "The data to be decoded must be a non-empty string.");
        }
        
        if ($this->unserializeCallback) {
            $callback = ini_get("unserialize_callback_func");
            if (!function_exists($callback)) {
                throw new BadFunctionCallException(
                    "The php.ini unserialize callback function is invalid.");
            }
        }
        
        if (($data = @unserialize($data)) === false) {
            throw new RuntimeException("Unable to unserialize the supplied data."); 
        }
        
        return $data;
    }
}

It would certainly be redundant to dissect the logic behind Serializer. Even though, it’s worth to pointing out that it not only depends now upon a more permissive encoding/decoding abstraction, but the abstraction’s ownership is explicitly exposed at the namespace level.

Likewise, we could go one step further and set out to write a few more encoders so that the benefits brought by the DIP are highlighted. With that said, here’s how another additional low-level component could be written:

<?php
namespace LibraryEncoderStrategy;

class JsonEncoder implements LibraryFileEncoderInterface
{
    public function encode($data) {
        if (is_resource($data)) {
            throw new InvalidArgumentException(
                "PHP resources cannot be JSON-encoded.");
        }
        
        if (($data = json_encode($data)) === false) {
            throw new RuntimeException(
                "Unable to JSON-encode the supplied data.");
        }
        
        return $data;
    }
    
    public function decode($data) {
        if (!is_string($data) || empty($data)) {
            throw new InvalidArgumentException(
                "The data to be decoded must be a non-empty string.");
        }
        
        if (($data = json_decode($data)) === false) {
            throw new RuntimeException(
                "Unable to JSON-decode the supplied data.");
        }
        
        return $data;
    }
}

As expected, the underlying logic that’s behind extra encoders generally resembles that of the first PHP serializer, except for any obvious refinements and variants. Additionally, the components conform to the requirements imposed by the DIP, therefore adhering to the encoding/decoding protocol defined within the FileStorage namespace.

With both high-level and low-level components in the file module depending upon an abstraction, and encoders exposing a clear dependency on the file storage class, we could safely claim that the module behaves as a true DIP-compliant structure.

Moreover, the following example shows how to bring the components together:

<?php
use LibraryLoaderAutoloader,
    LibraryEncoderStrategyJsonEncoder,
    LibraryEncoderStrategySerializer,
    LibraryFileFileStorage;    

require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$fileStorage = new FileStorage(new JsonEncoder); 
$fileStorage->write(new stdClass());
print_r($fileStorage->read());

$fileStorage = new FileStorage(new Serializer);
$fileStorage->write(array("This", "is", "a", "sample", "array"));
print_r($fileStorage->read());

Aside from some naive subtleties that the module exposes to client code, it’s useful for making the point and to demonstrate in a pretty instructive manner why the DIP’s predicates are actually more extensive than the old “programming to an interface” paradigm. It describes and explicitly prescribes an inversion of dependencies, and as such, it should be fulfilled through different mechanisms. PHP’s namespaces are a great way to accomplish this without excessive burden, even though traditional approaches like defining well-structured, expressive application layouts can yield the same results.

Closing Remarks

In general, opinions based on subjective expertise tend to be rather biased, and certainly the ones I expressed at the beginning of this article aren’t any exceptions. There is, however, a slight tendency to overlook the Dependency Inversion Principle in favor of its more convoluted SOLID counterparts, as it’s quite easy to misunderstand it as a synonym for dependency on abstractions. Furthermore, some programmers tend to react intuitively and think of the term “Inversion” as an abbreviated expression for Inversion of Control, which, although related to each other, is ultimately a wrong conception.

Now that you know what’s really under the hood of the DIP, make sure to exploit all of benefits that it brings to the table, something that will surely make your applications a lot less vulnerable to Fragility and Rigidity issues that might eventually arise as they grow over time.

Image via kentoh/ Shutterstock

Sponsors