Dependency Injection and Service Locators in PHP

In this second tutorial of the series, I show how the combined functionality provided by dependency injection and a service locator can be quite helpful in the construction of testable object graphs.

Unless you build your object graphs by poisoning your constructors with tons of “new” operators (which is a cardinal sin), you know that dependency injection and service locators are currently the two most popular approaches to connecting sets of related objects.

In the PHP world, dependency injection (AKA inversion of control) is quickly being adopted, due to its relatively simple implementation and the benefits that it brings to the table, such as the construction of easily testable classes. But in truth, the utilization of a service locator deserves a close analysis as well. This approach can be quite helpful in those use cases where the injection of collaborators is not a viable option.

In line with this idea, in the first part of this series I went through the development of a basic registry library. It used a single service locator to provide a client class with its dependency — in this case, a naive array-based registry. Moreover, to help you see the differences between using dependency injection and a service locator, I deployed the library by using each approach individually (more hard work for me, but hopefully in pursuit of a just cause).

It’s probable that you’re thinking that these approaches are mutually exclusive. They really aren’t. In fact, it’s possible to inject a service locator either through a constructor or a setter and get the dependencies of a given object. In most cases, this method is used when those dependencies have a shorter life cycle than the object that needs them. In a situation like this, the locator is a plain factory or a builder, which spawns collaborators on request.

Since the scenario just described is pretty common in modern web application development, in this second tutorial I’m going to modify the sample registry library previously developed. This time, it will combine the functionality of dependency injection and a service locator within the same client class.

Are you ready to see how this will be accomplished? Then start reading!

Recap: building a simple service locator

Just in case you haven’t read the previous article, where I explained how to implement a basic service locator, below I included the source classes (and the interface) corresponding to this example. As noted in the introduction, the locator was utilized as part of a registry library, which permitted you to save and fetch data from a plain array.

Having said that, here’s the interface that defines the methods of an abstract registry:  

(Registry/Registrable.php)

<?php

namespace Registry;

interface Registrable
{
    public function set($key, $value);
   
    public function get($key);
   
    public function clear(); 
}

Understanding the contract that the “Registrable” interface establishes is a process that doesn’t bear any further discussion. So, the next step is to show the definition of a concrete registry, which in this case stores and retrieves data from a protected array. This one is an implementer of the previous interface, and its source code is as follows: 

(Registry/ArrayRegistry.php)

<?php

namespace Registry;

class ArrayRegistry implements Registrable
{
    protected $_data = array();
   
    /**
     * Save the specified value to the array registry
     */
    public function set($key, $value)
    {
        $this->_data[strtolower($key)] = $value;
    }
   
    /**
     * Get the specified value from the array registry
     */
    public function get($key)
    {
        $key = strtolower($key);
        return isset($this->_data[$key]) ?
               $this->_data[$key] :
               null;
    }
   
    /**
     * Clear the array registry
     */
    public function clear()
    {
        $this->_data = array();
    }     
}

So far, so good. With this concrete array-based registry up and running, I could create a script that puts it to work directly. But, since I want to abstract the storage and retrieval of data, I’m going to hide the registry from the outside and use a client class instead.

If you’re wondering how this client class obtains an instance of the array registry, the following service locator should answer your question quickly. Check it out:

(Registry/RegistryLocator.php)

<?php

namespace Registry;

class RegistryLocator
{
    /**
     * Private constructor
     */
    private function __construct(){}
     
    /**
     * Get the array registry
     */
    public static function getArrayRegistry()
    {
        return new ArrayRegistry;
    } 
}

Effectively, the above locator is a static factory responsible for creating the pertinent array-based registry. With this element added to this sample library, building a client that consumes the registry is as simple as defining the following class:

(Registry/Registry.php)

<?php

namespace Registry;

class Registry
{  
    protected $_registryBackend;
   
    public function __construct()
    {
        $this->_registryBackend = RegistryLocator::getArrayRegistry();
    }
    
    /**
     * Save the specified element to the registry
     */
    public function set($key, $value)
    {
        $this->_registryBackend->set($key, $value);
        return $this;
    }

    /**
     * Get the specified element from the registry
     */
    public function get($key)
    {
        return $this->_registryBackend->get($key);
    }
   
    /**
     * Clear the registry
     */
    public function clear()
    {
        $this->_registryBackend->clear();
    }         
}

Even though the implementation of the above “Registry” class is somewhat primitive, it shows in a nutshell the role of a service locator. In this case, the class appeals to the locator to get its collaborator, via a call to its static “getArrayRegistry()” method.

Of course, the complexities of this internal process are shielded from client code. This is demonstrated by the following code snippet (the autoloader’s definition has been omitted for the sake of brevity, but you can see it here). Look at it:

<?php

// example using a service locator
use RegistryRegistry as Registry;

// include the autoloader
require_once ‘Autoloader.php’;
Autoloader::getInstance();

// create an instance of the registry and save some data in it
$registry = new Registry;
$registry->set(‘name’, ‘Mary Wilson’)
         ->set(‘email’, ‘mary@domain.com’);

// get the data from the registry
echo ‘Name: ‘ . $registry->get(‘name’) . ‘ Email: ‘ . $registry->get(‘email’);

I don’t want to make Mary Wilson angry, but the way that previous script manipulates her data is in this case is completely irrelevant. What’s really important here is that the “Registry” class gets its dependency through a service locator, which is invoked statically. 

But, before I hear you insisting that the same functionality can be achieved with dependency injection, first take a deep breath and calm down. If you analyze the “Registry” and “RegistryLocator” classes, you’ll realize that it’s possible to modify their respective definitions and make the former ask for the latter in the constructor.

Does this sound confusing? Well, fear not, as this approach is pretty common nowadays. It consists of injecting a service locator, instead of invoking it statically. Therefore, in the next section I’m going to amend the source code of the aforementioned classes, so the locator can be injected into the client registry’s internals.

To see how this will be done, move ahead and keep reading.

{mospagebreak title=Modifying the library by injecting the registry locator}

Merging the functionality provided by dependency injection with that given by a service locator is a  straightforward, easy-to-understand process. Since I want to demonstrate how to use this approach with the earlier registry library, the first thing I must change is the definition of the registry locator, which needs to be injectable. 

To make this change, the locator’s originating class must no longer be a static factory, but a dynamic one instead. The following code sample clearly reflects this modification:

(Registry/RegistryLocator.php)   

<?php

namespace Registry;

class RegistryLocator
{
    /**
     * Get the array registry
     */
    public function getArrayRegistry()
    {
        return new ArrayRegistry;
    } 
}

Done. Now that the “RegistryLocator” class can be instantiated, the next step is to make the client registry ask for the locator in its constructor. Again, the following snippet accomplishes this goal:

(Registry/Registry.php)

<?php

namespace Registry;

class Registry
{  
    protected $_registryLocator;
    protected $_registryBackend;
   
    /**
     * Constructor
     */
    public function __construct(RegistryLocator $registryLocator)
    {
        $this->_registryLocator = $registryLocator;
    }
   
     /**
     * Get the registry backend
     */
    public function getRegistryBackend()
    {
        if ($this->_registryBackend === null) {
            $this->_registryBackend = $this->_registryLocator->getArrayRegistry();
        }
        return $this->_registryBackend;
    }
     
    /**
     * Save the specified element to the registry
     */
    public function set($key, $value)
    {
        $this->getRegistryBackend()->set($key, $value);
        return $this;
    }

    /**
     * Get the specified element from the registry
     */
    public function get($key)
    {
        return $this->getRegistryBackend()->get($key);
    }
   
    /**
     * Clear the registry
     */
    public function clear()
    {
        $this->getRegistryBackend()->clear();
    }         
}

That looks much better, doesn’t it? As you can see, now the client registry class accepts, through its constructor, an instance of the locator, which is stored as a protected property. This simple change demonstrates that injecting a service locator is in fact much simpler than one might think. Plus, there’s an additional benefit to using this approach: now it’s perfectly possible to replace the locator with a mock-up, which makes testing the client registry a breeze.

Unfortunately, an injected locator also has its own drawbacks. If the client registry needs to use a different registry backend, the only way to achieve this is to add a new factory method to the locator and call it explicitly. This isn’t very flexible.

However, in those cases where collaborators of a client have a shorter life cycle than the client itself, injecting locators that are concrete factories, responsible for spawning only a single dependency, makes a lot of sense.

Well, having clarified those concepts, we still need to test the improved version of the registry library, once the previous changes have been properly implemented.

This will be done below, so keep reading. 

A final example

As I promised above, here’s a concrete example that shows how to work with the enhanced registry library, now that it uses an injected service locator:

<?php

// example injecting a service locator
use RegistryRegistry as Registry,
    RegistryRegistryLocator as RegistryLocator;

// include the autoloader
require_once ‘Autoloader.php’;
Autoloader::getInstance();

// create an instance of the registry and save some data in it
$registry = new Registry(new RegistryLocator);
$registry->set(‘name’, ‘Mary Wilson’)
         ->set(‘email’, ‘mary@domain.com’);

// get the data from the registry
echo ‘Name: ‘ . $registry->get(‘name’) . ‘ Email: ‘ . $registry->get(‘email’);

Although the above script does nothing spectacular, other than saving and retrieving some information about our fictional user, Mary Wilson, it does demonstrate how the functionality of dependency injection and service locators working together can help to write classes that are easier to test in isolation.

Naturally, if you’ve developed applications composed of a large number of object graphs, much more complex than the one above, dependency injection on its own is, in my opinion, the best approach. However, in relatively simple use cases, an injected locator may fit your needs.   

Closing thoughts

In this second tutorial of the series, I attempted to show how the combined functionality provided by dependency injection and a service locator can be quite helpful in the construction of testable object graphs.

While the example that you just learned was pretty illustrative, it would be desirable to set up one that resembles a more realistic use case. Thus, in the next part I’m going to show you how to implement a service locator in the construction of a simple blog application.

Don’t miss the next part!

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan