Road to dependency injection

Posted on by Matthias Noback

Statically fetching dependencies

I've worked with several code bases that were littered with calls to Zend_Registry::get(), sfContext::getInstance(), etc. to fetch a dependency when needed. I'm a little afraid to mention façades here, but they also belong in this list. The point of this article is not to bash a certain framework (they are all lovely), but to show how to get rid of these "centralized dependency managers" when you need to. The characteristics of these things are:

  • They offer global access; they are not internal to classes, but external, and thereby reachable from any scope.
  • They offer static access; they don't require you to have an instance of the object.

This means they can and will be called all over the place. If someone feels the need to use the translator service in a domain model, they can simply call Zend_Registry::get('Zend_Translate'). If they need to send an e-mail in a JSON mapper, there's sfContext::getInstance()->getMailer().

Part of the issue is that these centralized dependency managers provide access to other things than services, like user session data, request parameters, etc. (see also my previous article about this topic - "Context passing"). But when it comes to fetching services from these global service registries/locators, the main issue is that we're coupling all our code to this specific singleton thing.

Fetching the service locator

When trying to get rid of that-framework-that-was-totally-it-in-2012, we are in big trouble because of all that coupling. The setup of our services is not completely under our control. So, when migrating to a more recent service container implementation, like Symfony's dependency injection container, the first migration step might be to reuse the old mechanism for retrieving dependencies, making the new service container available to all the old code:

/*
 * At some point, we'll build and boot the Symfony service container.
 * We then inject it right into the old `Zend_Registry`.
 */
$container = ...;
Zend_Registry::set('container', $container);

/*
 * Anywhere else, we can retrieve the container and get a service from it.
 */
$container = Zend_Registry::get('container');
$translator = $serviceLocator->get('translator');

Since we can now define the translator service in any way that the dependency injection component of our choice supports, we can now migrate all existing uses of Zend_Registry::get('Zend_Translate') to the slightly better Zend_Registry::get('container')->get('translator').

"Service container" vs "service locator"

It's good to point out that the code in the example above uses the service container as a service locator. This is by far inferior, since we're still fetching services manually. This requires us to know the service ID and be aware of the service retrieval mechanism itself. Dependency injection is more useful if you leave all the instantiation details and dependency resolving to the service container (also known as "dependency injection container") itself.

Injecting the service locator

Ideally, the framework should be able to route a request to a controller, defined as a service in the service container (or dependency injection container). I know that Symfony is capable of that. It would retrieve the fully instantiated controller, and no other object would ever need the service locator anymore. But this isn't how most frameworks work (at least the older ones). So we need to be a bit more realistic. As an example, most applications still have multiple actions per controller class. Since all these actions use a different set of dependencies, the controller gets the service locator injected, and the actions can all take out the services they need.

So, as a compromise, where should we allow the use of a service locator?

  • Inside controllers for (web or CLI)
  • Inside controller helpers (in the case of Zend Framework), since they are still close to the infrastructure/outer boundary of the application.

Definitely not in any other service, only as an intermediate step on the road to dependency injection.

Performance

The question always seems to be: if we inject everything upfront, this means we also need to instantiate everything upfront. Some dependency injection components allow lazy loading for this. What gets injected is a proxy of the real object, that still satisfies any type-hints used. Once a method gets called on the proxy, it will instantiate the real service, including its real dependencies. However, if you would set up controllers-as-a-service, and would have only one action per controller, it would not be a problem to instantiate everything upfront. There's no performance penalty, since everything that gets instantiated, will in fact be used in the current request.

Pretending dependency injection

The solution I picked for some services in a recent migration was - contrary to the rule stated above - to inject the container itself, but the constructor still sets up the services as if they were injected, like this:


final class SomeService { private $translator; public function __construct(Container $container) { $this->translator = $container->get('translator'); } }

Service locator as a transitional phase

Once you're able to fetch dependencies in the constructor of a service, you're still fetching dependencies, but you're already preparing it for dependency injection. Once you are in a place where you can create the service with all its dependencies in one go, you can "externalize" the setup code:

final class Container
{
    public function getSomeService(): SomeService
    {
        return new SomeService(new Translator(...));
    }
}

final class SomeService
{
    private $translator;

    public function __construct(Translator $translator)
    {
        /*
         * No longer inject the container, but expect a translator to be 
         * provided.
         */

        $this->translator = $translator;
    }
}

At this point, SomeService doesn't use the service container as a service locator anymore. It simply accepts all its dependencies, unaware of where they come from.

When constructor injection isn't possible

In fact, when you're in complete control of how an object (service) gets instantiated, you shouldn't inject the whole container/service locator, but only the dependencies that are relevant to this service. Consider a counter-example, where, like in Zend Framework 1, framework-related objects (but also controllers) are almost always constructed like this:

$object = new $className();

You get no chance to provide constructor arguments, so you need to settle with inferior ways of injecting dependencies, e.g. setter injection, or property injection.

I recently applied both solutions to get the service container where I wanted it to be. The following example uses setter injection:

final class SomeService
{
    private static $serviceLocator;

    public static function setServiceLocator(ServiceLocator $locator)
    {
        self::$serviceLocator = $locator;
    }

    private function get(string $serviceId)
    {
        if (!self::$serviceLocator instanceof ServiceLocator) {
            throw new \LogicException(
                'Call setServiceLocator() first'
            );
        }

        return self::$serviceLocator->get($serviceId);
    }

    public function someMethod(): void
    {
        // ...

        $this->translator()->translate(...);
    }

    private function translator(): Translator
    {
        return $this->get('translator');
    }
}

// don't forget to do this first, before using an instance of SomeService
SomeService::setServiceLocator(...);

Introducing the static setter, but also a private non-static getter, gives us a false sense of encapsulation. But it also makes the translator feel like an injected dependency. If we're ever able to have proper dependency injection, we can just replace $this->translator() with $this->translator.

A more crude, but also more honest version of this style of "injection" could be to use (static) field injection:

final class SomeService
{
    public static $serviceLocator;
}

// don't forget to do this first, before using an instance of SomeService
SomeService::$serviceLocator = ...;

The advantage of the first approach (at least for PHP) is that you can only call setServiceLocator() with an object of the proper type (ServiceLocator). If you use the second approach, PHP allows you to assign any type of value to the public static attribute. Also, when using the private getter to locate a specific service, we get a convenient LogicException telling us that we should first call setServiceLocator().

Finally, you could also decide to inject specific services, like only the translator:

final class SomeService
{
    private static $translator;

    public static function setTranslator(Translator $translator)
    {
        self::$translator = $translator;
    }

    // ...
}

// don't forget to do this first, before using an instance of SomeService
SomeService::setTranslator(...);

I found that for a Symfony application, any bundle's boot() method is a good place to inject the service container statically into some classes.

final class AppBundle extends Bundle
{
    // ...

    public function boot()
    {
        SomeService::setServiceLocator($this->container);

        // or

        SomeService::setTranslator($this->container->get('translator'));
    }
}

Is setter/field injection better than retrieving the service locator?

There is a big difference between this approach of injecting the service locator using a public setter or field, and the approach we saw earlier where we fetch the service container from Zend_Registry for example. If we inject the service locator, at least we're not coupled to the global static centralized dependency manager anymore (be it Zend_Registry, sfContext or anything else). This is a big win in terms of flexibility and your ability to migrate the code base to another framework, dependency injection component, etc.

Temporal coupling and how to deal with it

The biggest issue with setter or field injection is in fact displayed by this annoying LogicException: we need to first make a certain call (setServiceLocator()), before it's safe to use the service. That's why earlier I called these types of injection "inferior".

When using setter or field injection, the code in the service class now has to deal with possible null values. The service can be instantiated but it's not yet safe to use. This will very likely lead to bugs and code that is hard to understand.

The name for "call this method first, than that one" is "temporal coupling". It should always be prevented. You should only be able to instantiate an object in a way that makes sense. The object, once it exists, should always be ready for use. That's why injecting a service locator like this should always be a transitional thing only.

Doing things in the wrong place

Earlier I made a little joke about sending an email from a domain object. If you have a global static mechanism for retrieving services it's possible. I don't think it's good design though. In terms of architecture it makes no sense that something in the core of the application would reach out and get in touch with "the world outside", without any indirection or abstraction.

I recently experienced again how migrating from global/static mechanisms of dependency retrieval to injecting dependencies, reveals how some objects have been doing things they really shouldn't be doing. Inside an object that looked like an active record entity, I encountered email sending functionality, but also repository-like loading of more data from a database, touching files, etc. It will be a lot of fun to figure out how to fix these classes on the way to dependency injection.

PHP dependency injection legacy legacy code code quality
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Roman O Dukuy

Can you explain me why getServiceLocator approach better to declare setter method in service.yaml file?

Matthias Noback

You mean setServiceLocator()? It isn't better, but I propose it here because it may not yet been possible to define the service in the service container. However, if you have service definitions somewhere, don't use setter injection, since you can already use constructor injection easily then.

Roman O Dukuy

looks like singleton for service usage

Matthias Noback

Yes, but the dependency still gets inverted: instead of fetching the service locator, we get it injected now. This is not awesome, but better than fetching.

Roman O Dukuy

understand, thanks

Matthias Noback

Cool, you're welcome. Thanks for dropping by!

ellisgl

What about using rdlowrey/auryn ?

Matthias Noback

Seems interesting, thanks for sharing.