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

This entry is part 1 of 2 in the series Managing Class Dependencies: An Introduction to Dependency Injection, Service Locators, and Factories

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

In the previous installment of this two-part series, I went though the development of a few straightforward examples exploring in a fairly approachable fashion a couple of methodologies new to PHP when it comes to handling class dependencies. In this primer, I covered the inclusion of sinful “new” operators in constructors, a method that should be quickly thrown in the trash can with no trace of guiltiness, as well as the use of injected factories.

While it’s fair to admit that factories do have a neat niche in a number of special use cases, I’m not so merciless as to condemn Service Locators and plain Dependency Injection to an unfair exile. In this final part we’ll take a closer look at the implementation of these popular patterns so that you can pick up the one that best suits the need at hand.

The Middle Man – Getting Class Collaborators via a Service Locator

While a Service Locator is considered in many cases a fancy, mind-blowing approach in the world of PHP, the truth is that the pattern, with some creative slants of course, has enjoyed of a long and venerable life within the language’s domain. At its roots, a Service Locator is nothing but a centralized registry, most of the time static (although dynamic ones are appearing in some popular frameworks), filled with a bunch of objects. Nothing more, nothing less.

As usual, one didactical approach to understand what’s going on under the hood of a Service Locator is by example. If we wanted to appeal to the pattern’s virtues for giving the FileStorage object from last time its dependency, the locator could be implemented like this:

<?php
namespace LibraryDependencyInjection;

interface ServiceLocatorInterface
{
    public function set($name, $service);
    public function get($name);
    public function has($name);
    public function remove($name);
    public function clear();
}
<?php
namespace LibraryDependencyInjection;

class ServiceLocator implements ServiceLocatorInterface
{
    protected $services = array();
    
    public function set($name, $service) {
        if (!is_object($service)) {
             throw new InvalidArgumentException(
                "Only objects can be registered with the locator.");
        }
        if (!in_array($service, $this->services, true)) {
            $this->services[$name] = $service;
        }
        return $this;
    }
    
    public function get($name) {
        if (!isset($this->services[$name])) {
            throw new RuntimeException(
                "The service $name has not been registered with the locator.");
        }
        return $this->services[$name];
    }

    public function has($name) {
        return isset($this->services[$name]);
    }
    
    public function remove($name) {
        if (isset($this->services[$name])) {
            unset($this->services[$name]);
        }
        return $this;
    }

    public function clear() {
        $this->services = array();
        return $this;
    }
}

Take my opinion as a form of catharsis if you want to, but I must confess that I’m rather reluctant to use a service locator over plain dependency injection, even if the locator is dynamic, rather than a static registry plagued with mutable global access issues. In either case, it’s worth looking at and seeing how it can be passed along into the FileStorage class. Here we go:

<?php
namespace LibraryFile;
use LibraryDependencyInjectionServiceLocatorInterface;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "data.dat";
    protected $serializer;
    protected $file;
    
    public function __construct(ServiceLocatorInterface $locator, $file = self::DEFAULT_STORAGE_FILE) {
        $this->setFile($file);
        $this->serializer = $locator->get("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());
        }
    }
}

To make things clear, I dropped the FileStorage class again from top to bottom, as this probably makes it easier to understand how its driving logic remains untouched with regards to its read()/write() methods. The constructor is by far the most relevant block as it consumes the locator which is then charged with the responsibility of getting in a serializer object.

While its implementation is straightforward, this approach is a far cry away from being innocent. First, FileStorage now has a strong dependency with the locator, even when it’s possible to pass around different implementations of it. Second, since the locator is inherently an intermediate provider of the class’ dependency, it infringes on the Law of Demeter at some point as well. This is an unavoidable artifact tied to the roots of the pattern. we should either learn to live with the issue or just forget about the pattern altogether. There’s no middle ground to ponder to here.

Here’s the code that shows how to get things finally rolling with the locator:

<?php
$locator = new ServiceLocator;
$locator->set("serializer", new Serializer());
$fileStorage = new FileStorage($locator);
$fileStorage->write("This is a sample string.");
echo $fileStorage->read();

Although rather primitive, the example shows the locator resembles at some point the structure of a basic Dependency Injection Container (DIC). The main difference is that the locator is usually injected or statically consumed inside the client classes, while a DIC always lives and breaths outside of them.

So far, we’ve covered a decent amount of common approaches used for managing class dependencies. Still, we haven’t swum in the waters of the simplest one of all… yep, the sweet creek of raw Dependency Injection!

The Greatest and Simplest Finale – using Plain Dependency Injection

It might sound obvious, I know, but the most efficient and easiest way to provide FileStorage with a serializer object is with plain ol’ Dependency Injection, thus moving away from any coupling issues or breaking the commandments imposed by the Law of Demeter. Of course, I assume you’re clever enough and already knew that from the very beginning. Even so, it doesn’t hurt to show how the class in question would look when hooked up to this approach:

<?php
namespace LibraryFile;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "data.dat";
    protected $serializer; 
    protected $file;
    
    public function __construct(Serializable $serializer, $file = self::DEFAULT_STORAGE_FILE) {
        $this->setFile($file);
        $this->serializer = $serializer;
    }

    // the remaining methods go here
}

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

That’s ridiculously easy to assimilate. In this case, the whole object graph is so anemic that appealing to the nuts and bolts of an external DIC to create it would be just pretty much overkill. On behalf of an instructive cause, though, we could build a primitive container, similar to the swift, lightweight Pimple, and see in a jiffy how to use it for wiring up all the objects that compose the file storage module:

<?php
namespace LibraryDependencyInjection;

interface ContainerInterface
{
    public function set($name, $service);
    public function get($name, array $params = array());
    public function has($name);
    public function remove($name);
    public function clear();
}
<?php
namespace LibraryDependencyInjection;

class Container implements ContainerInterface
{
    protected $services = array();
    
    public function set($name, $service) {
        if (!is_object($service)) {
             throw new InvalidArgumentException(
                "Only objects can be registered with the container.");
        }
        if (!in_array($service, $this->services, true)) {
            $this->services[$name] = $service;
        }
        return $this;
    }
    
    public function get($name, array $params = array()) {
        if (!isset($this->services[$name])) {
            throw new RuntimeException(
                "The service $name has not been registered with the container.");
        }
        $service = $this->services[$name];
        return !$service instanceof Closure 
            ? $service : call_user_func_array($service, $params);
    }
    
    public function has($name) {
        return isset($this->services[$name]);
    }
    
    public function remove($name) {
        if (isset($this->services[$name])) {
            unset($this->services[$name]);
        }
        return $this;
    }
    
    public function clear() {
        $this->services = array();
    }
}

The similarities between the DIC and the service locator coded before are anything but casual. The former, however, implements a pinch of extra functionality as it’s capable of storing and calling closures on request, something that closely mimics the forces behind Pimple.

With this naïve DIC in place, the whole file storage object graph could be assembled on demand:

<?php
$container = new Container();
$container->set("filestorage", function() {
    return new FileStorage(new Serializer());
});
$fileStorage = $container->get("filestorage");
$fileStorage->write("This is a sample string.");
echo $fileStorage->read();

It’s clear that the DIC is (or in theory it should be) an element stepping beyond the boundaries of the client classes, which are completely agnostic about its rather furtive and silent existence. This form of unawareness is quite possibly one of the biggest differences that exist between a DIC and a service locator, even though it’s possible to inject a DIC into other classes as well through a sort of “recursive” injection.

In my opinion, this process not only unnecessarily degrades the DIC to the level of a plain service locator, but corrupts its natural “outsider” condition. As a rule of thumb, regardless of if you’re using a DIC or a service locator, make sure the elements will be playing the role they’re supposed to play without stepping on each other toes.

Closing Remarks

It seems that the old bad days when managing class dependencies was just a matter of dumping a few “new” operators into fat, bloated constructors are finally fading away. By contrast, an emerging combination of patterns, with Dependency Injection leading the charge, are strongly pushing through to every corner of PHP, a paradigm shift that has already had a beneficial impact on the quality of several existing codebases out there.

Still, the big question keeps floating around in circles: DICs, service locators, injected factories… which ultimately fits the bill the best? As I said before, making the right decision largely depends on what you’re dealing with in the first place. In all cases, they’re just variants of Inversion of Control, decorated with some nice refinements and fancy mixtures. And you know that IoC is the way to go with polymorphism, hence with testability. Let your personal needs be your most confident advisors; they won’t disappoint you.

Image via SvetlanaR / Shutterstock

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

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://chrisoconnell.info Chris

    Hey Alejandro, great post!
    Quick question, in your ServiceLocator class, a couple of functions return $this (set, has, remove, clear). What is that point of that? I’ve not seen that practice before and wondering how you’d make use of that value.

    Thanks,
    Chris

    • Alex Gervasio

      Hi Chris,
      Thanks for the comments. Well, when you return $this from several methods you’re actually designing a fluent interface (AKA chainable interface), as $this is a reference to the instance of the class that uses it (remember it’s an instance-level reference, not a class-level one, which is totally different) . This quick trick allows us to write something like $locator->set(‘foo’, $foo)->set(‘bar’, $bar) and so on, all in just one-liner. A nice client-side analogy is what jQuery does under the hood, and that’s precisely the reason why you can chain in a snap its methods using JavaScript’s dot notation. Of course, you don’t have to make your classes APIs that fluent; the practice, however, lets you write slightly more concise and compact code. Thanks!

      • Aaron

        Wow! I never realized that and had the same question. It makes total sense now. In fact, I’m going to start doing this right away! Love it!

  • http://blog.twelvecode.com Greg

    Okay… As far as I see, both DI and SC can use the same implementation of the Container, since the main difference lies in calling ‘call_user_func_array()’. Thus it’s also possible to push a closure returning ‘FileStorage(new Serializer());’ into the internal SC array which makes it the same as DI… Am I missing something?

    • Alex Gervasio

      Hey Greg,
      In this case in particular, the DIC and the SL do share large chunks of the same interface. But I did so just to keep things overall simpler to follow, hence making it easier to spot on the different roles that each pattern plays in the same context. Even though, a service locator could be implemented as a static/dynamic registry, mapping keys to services or exposing a bunch of named setters/getters. On the flip side, a DIC could be a much more complex beast, using reflection/annotations to inspect recursively a class and figure out what and how to inject class collaborators. In such situations, both elements certainly would implement disparate interfaces. The point here is to able to differentiate the operational mode of a DIC (externally consumed) and the SL (internally consumed).
      I hope that clarifies your doubts. Thanks!

  • http://activedeveloper.info mhitza

    A suggestion, don’t change the mod of the file; first because it is dependent of the umask; and second you don’t know how people set their file ownership.

    • Alex Gervasio

      Agreed with your suggestion. But you should take the class just like it’s meant to be…an example which attempts to set a readable/writable target file, when the one passed around doesn’t honor those permissions. At worst, the method in question will end up throwing an exception, which by the way is the expected behavior. Thanks.

  • http://www.mosheteutsch.com Moshe Teutsch

    I typically work with two interfaces for my ServiceLocators: ServiceLocator and ServiceLoader. ServiceLocator is used for caching services and ensuring lazy-loading, while ServiceLoader is used for loading the service. Obviously, ServiceLocator is passed a ServiceLoader to use.

    • Alex Gervasio

      At a glance, it looks like a slightly distilled SL implementation, which adheres more faithfully to the “separation of concerns” paradigm. Nicely done.

  • Alexander Cogneau

    Can you inject your service locator into components? That would make it possible to combine depency injection and a service locator?