PHP
Article
By Alejandro Gervasio

Managing Class Dependencies: An Introduction to Dependency Injection, Service Locators, and Factories, Part 1

By Alejandro Gervasio

Managing Class Dependencies: An Introduction to Dependency Injection, Service Locators, and Factories

Let’s face it: for good or bad, OOP has been actively drilling deep holes in the soil of PHP in the last few years, hence its ubiquitous presence now is anything but breaking news. Furthermore, while this steady incremental “objectification” in the language’s terrain is generally considered a beneficial shift towards new and more promising horizons, for those still rooted in the procedural paradigm, the process has been far from innocuous.

There’s a few solid arguments that sustain this mindset: first and foremost the nature of OOP is awkward and inherently complex, plagued with nuance that can take literally years to tame. Also, defining the APIs that OO components use for interacting with one another can be quite a burden at times, especially when it comes to designing systems whose backbone rests on facilities provided by large and tangled object graphs.

From a programmer’s perspective, the process of designing easily consumable APIs while still keeping a decent level of decoupling between the involved classes is always a tricky trade-off, as the more expressive the dependencies are, the harder the work is that needs to be done behind the scenes for wiring up the collaborators in their proper sequence. This fact itself is a dilemma that has plagued the minds of developers from long ago, which is certainly harder to digest as time goes by. With applications becoming increasingly bloated, housing inside their boundaries a growing number of classes, the construction of clean, declarative APIs isn’t just a loose practice anymore that can eventually be pushed into the language’s mainstream from time to time. It’s simply a must.

This raises a few interesting questions: how should a class be provided with its dependencies without cluttering its API, or even worse, without coupling it to the dependencies themselves? Just by appealing to the benefits of Dependency Injection? Through the reigns of an injected Service Locator?

For obvious reasons, there’s no a one size fits all solution to the problem, even when all of the aforementioned approaches have something in common. Yes, to some extent they all make use of some form of Inversion of Control, which not only allows you to decouple the dependencies from the class but also keeps a neat level of insulation between the dependences’ interfaces and the implementation, making mocking/testing a breeze.

Evidently, the topic is a world away from being banal, and as such it deserves a close, in-depth analysis. In this two-part series I’ll be doing a quick roundup of some of the most common methodologies that can be employed for managing class dependencies in modern application development, ranging from using service locators and injectable factories, to sinking your teeth into plain vanilla Dependency Injection, and even implementing a naive Dependency Injection Container.

A (Hopefully) Vanishing Plague – “new” Operators in Constructors

The mantra is somewhat old, sure, but its claim still rings loud and clear: “placing ‘new’ operators in constructors is just plain evil.” We all know that now, and even take for granted that we’ll never commit such a cardinal sin. But this was literally the default method used for years for a class to look up its dependencies before Dependency Injection reached the PHP world. For the sake of completeness, let’s recreate this clunky, old-fashioned scenario and remind ourselves why we should get away from such a harmful plague.

Say that we need to build a simple file storage module which must internally consume a serializer in order to read and write data to a specific file. The implementation of the serializer would look something like to this:

<?php
namespace LibraryEncoder;

class Serializer implements Serializable
{
    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 unserialized must be a non-empty string.");
        }
        if (($data = @unserialize($data)) === false) {
            throw new RuntimeException("Unable to unserialize the supplied data."); 
        }
        return $data;
    }
}

The Serializer class is just a lightweight implementation of the native Serializable interface and exposes the typical serialize/unserialize duet to the outside world. With this contrived strategy class doing its thing as expected, the aforementioned file storage module could be sloppily coded as follows:

<?php
namespace LibraryFile;
use LibraryEncoderSerializer;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "data.dat";
    protected $serializer;
    protected $file;
    
    public function __construct($file = self::DEFAULT_STORAGE_FILE) {
        $this->setFile($file);
        $this->serializer = new Serializer();
    }
    
    public function setFile($file) {
        if (!is_file($file)) {
            throw new InvalidArgumentException(
                "The file $file does not exist.");
        }
        if (!is_readable($file) || !is_writable($file)) {
            if (!chmod($file, 0644)) {
                throw new InvalidArgumentException(
                    "The file $file is not readable or writable."); 
            }
        } 
        $this->file = $file;
        return $this;
    }
    
    public function read() {
        try {
            return $this->serializer->unserialize(
                @file_get_contents($this->file));
            
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
    
    public function write($data) {
        try {
            return file_put_contents($this->file,
                $this->serializer->serialize($data));
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
}

The behavior of FileStorage boils down to just saving and pulling in data from a predefined target file. But the seemingly benign nature of the class is nothing but an illusion, as it blatantly instantiates the serializer in its constructor!

This form of “dictatorial” dependency lookup not only introduces a strong coupling between the class and its dependency, but it condemns testability to a quick death. The following code shows how the artifacts ripple through to client code when using this approach:

<?php
$fileStorage = new FileStorage();
$fileStorage->write("This is a sample string.");
echo $fileStorage->read();

Feel free to run the code and it’ll work like a charm, that’s for sure. But we know the coupling is there just beneath the surface, not to mention the fact that there’s not a single clue about if the FileStorage has an internal dependency on another component, or how this one was acquired. Definitively, this is pretty much like an antipattern which goes against the grain when it comes to defining clean, expressive class APIs.

There’re a few additional approaches that are worth looking into that can improve the way the two previous classes interoperate with each other. For instance, we could be a bit bolder and inject a factory straight into the internals of FileStorage and let it create a serializer object when necessary, thus making the class’ dependency a little more explicit.

--ADVERTISEMENT--

Deferring the Creation of Dependencies through an Injected Factory

Factories, in any of their forms, are nifty elements that allow us to nicely distill object construction from application logic. While such ability is quite remarkable in itself, it can be taken a bit further when mixed with the goodness of Dependency Injection.

If you’re the curious sort like I am, you’ll be wondering how to exploit the benefits provided by a factory for enhancing how the earlier FileStorage class looks up its collaborator, getting rid of the infamous “new” operator in its constructor. At a very basic level, the lookup process could be reformulated through the following factory class:

<?php
namespace LibraryDependencyInjection;

interface SerializerFactoryInterface
{
    public function getSerializer();
}
<?php
namespace LibraryDependencyInjection;
use LibraryEncoderSerializer;

class SerializerFactory implements SerializerFactoryInterface
{   
    public function getSerializer() [
        static $serializer;
        if ($serializer === null) {
            $serializer = new Serializer;
        }
        return $serializer;
    }
}

Having at hand a dynamic factory charged with the task of creating the serializer on demand, now the FileStorage class can be refactored to accept a factory implementer:

<?php
namespace LibraryFile;
use LibraryDependencyInjectionSerializerFactoryInterface;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "data.dat";
    protected $factory;
    protected $file;
    
    public function __construct(SerializerFactoryInterface $factory, $file = self::DEFAULT_STORAGE_FILE) {
        $this->setFile($file);
        $this->factory = $factory;
    }
    
    public function setFile($file) {
        if (!is_file($file)) {
            throw new InvalidArgumentException(
                "The file $file does not exist.");
        }
        if (!is_readable($file) || !is_writable($file)) {
            if (!chmod($file, 0644)) {
                throw new InvalidArgumentException(
                    "The file $file is not readable or writable."); 
            }
        } 
        $this->file = $file;
        return $this;
    }
    
    public function read() {
        try {
            return $this->factory->getSerializer()->unserialize(
                @file_get_contents($this->file));
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
    
    public function write($data) {
        try {
            return file_put_contents($this->file,
                $this->factory->getSerializer()->serialize($data));
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
}

The functionality of FileStorage remains pretty much the same, but the tight coupling with the serializer has been broken by injecting an implementer of the SerializerFactoryInterface. This simple twist turns the class into a flexible and testable creature, which exposes a more expressive API, as is now easier to see from the outer world what dependencies it needs. The code below shows the result of these enhancements from the perspective of the client code:

<?php
$fileStorage = new FileStorage(new SerializerFactory());
$fileStorage->write("This is a sample string.");
echo $fileStorage->read();

Let’s not fall into blind, pointless optimism, though. It’s fair to say the refactored implementation of the storage module looks more appealing; this approach makes it trivial to switch out different factory implementations at runtime. But in my opinion, at least in this case, it’s a masked violation of the Law of Demeter since the injected factory acts like an unnecessary mediator to the module’s real collaborator.

This doesn’t mean that injected factories are a bad thing at all. But in most cases they should be used only for creating dependencies on demand, especially when the dependencies’ life cycles are shorter than that of the client class using them.

Closing Remarks

Quite often unfairly overlooked in favor of more “seductive” topics, managing class dependencies is unquestionably a central point of object-oriented design and whose underlying logic certainly goes much deeper than dropping a few “new” operators in factories, or worse, in stingy constructors that hide those dependencies from the outside world so they can’t be properly decoupled at will or tested in isolation.

There’s no need to feel that all is lost, however, There exists a few additional approaches that can be utilized for handling in an efficient manner the way that classes are provided with their collaborators, including the use of the Service Locator pattern, raw Dependency Injection, and even appealing to the benefits of a Dependency Injection container. All these ones will be covered in detail in the follow up, so stay tuned!

Image via SvetlanaR / Shutterstock

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