Quicker, Easier, More Seductive: How To Tell A DI Container From A Service Locator

By | December 16, 2013

(In this unexpected series, we’ve been talking about the use of Service Locator versus Dependency Injection.)

UPDATE: Please see the next post in this series for important corrections and updates regarding the essay below.


It is easy to confuse a Dependency Injection container with a Service Locator. They are very similar to each other. The differences are subtle. Indeed, it’s even possible to use a Dependency Injection container as a Service Locator, although I think it’s difficult to use a Service Locator as a Dependency Injection container. They are both sub-patterns of a more generic pattern called Inversion of Contol, and some people confuse the term Dependency Injection with the more generic term Inversion of Control.

How To Tell The Difference

I’ve been thinking about what heuristic or rule-of-thumb one could use to easily tell which is which. So far I’ve been leaning in the direction of: “Can you create new instances without defining a service?” If you can, it’s a Dependency Injection container; otherwise, it’s a Service Locator. There are several variations on that prhasing:

  • If you can create a new instance of a class without having to define a service, it’s a Dependency Injection container.

  • If you have to define a service in order to get an object out of the container, it’s a Service Locator.

  • If you cannot create a new instance of a class without defining it as a service, it’s a Service Locator.

By this I mean that you should be able to repeatedly create new, discrete, independent instances of a class, without having to define it as a service in the container.

Examples

Given the above heuristic, here’s an example of Dependency Injection using Aura.Di:

// they are separate instances, not shared services
$foo_1 = $di->newInstance('Foo\Bar\Baz');
$foo_2 = $di->newInstance('Foo\Bar\Baz');
var_dump($foo_1 === $foo_2); // false

Might you need to set up constructor parameters, setters, etc. for the newInstance() calls? Sure; Aura.Di allows for inherited and overridable constructor parameters and setter methods:

// given __construct($gir)
$di->params['Foo\Bar\Baz']['gir'] = $di->newInstance('Dib\Zim\Gir');

// given a setter method setOperation($operation_name)
$di->setter['Foo\Bar\Baz']['setOperation'] = 'ImpendingDoom';

The main point is that whenever the Dependency Injection container creates a class, it can do so without having to define that class as a reusable service.

In contrast, a Service Locator requires that you define a service, and then you can retrieve a shared instance of that service from the container. Here’s an example with my trivial Service Locator example from earlier in this series:

// define shared services
$locator->set('gir', function () use ($locator) {
    return new \Dib\Zim\Gir;
}

$locator->set('foo', function () use ($locator) {
    $foo = new \Foo\Bar\Baz($locator->get('gir'));
    $foo->setOperation('ImpendingDoom');
    return $foo;
});

// these are shared services, not separate instances
$foo_1 = $locator->get('foo');
$foo_2 = $locator->get('foo');
var_dump($foo_1 === $foo_2); // true

Who’s Doing What?

I had a quick look at some containers in PHP land to see how well they fulfill the above heuristic; my assessment is as follows:

Some notes:

  1. Laravel: The documentation calls the container an “IoC” (Inversion of Control) container but mentions “Dependency Injection” throughout, never “Service Locator.” It is clear from the container’s usage throughout the rest of the codebase that it is in fact a Service Locator, since the container itself is injected into the classes that need services. This strikes me something that can be remedied with a documenation change, since the class itself is named in a generic way. UPDATE: the project lead has informed me of the public make() method on the container that creates new instances. Although I pick nits over the naming (using $class and newInstance() instead of $abstract and make() would make things more obvious IMO) it does fulfill the above heuristic.

  2. Pimple: Interestingly, Pimple has a factory() method that will return independent instance of a service. Some may think of this as meeting the heuristic above, but I don’t think it does; you still have to define the service, after all. I suppose one might define the service as a factory for a particular class name, and name the service for the class to be factoried, but that’s not quite the same; among other things, you still have to define a service for each class you want to factory.

  3. Slim: This uses $app as a Service Locator. To his credit, Josh Lockhart does not call it either a Service Locator or a Dependency Injection container, so kudos to @codeguy for avoiding the issue altogether.

  4. Symfony DependencyInjection component: My quick review of the codebase did not reveal a way to create a new instance of a class. Instead of a missing capability, It seems more likely to me that I just did not see it. If there are any Symfony guys reading this, please let me know how to create a new instance of a class with the Symfony DependencyInjection component, and I’ll update the description.

Conclusion

According to the heuristic above, some of the things calling themselves Dependency Injection systems are probably better described as Service Locators. They might be Containers (or Inversion of Control containers) in a generic sense, but they are not Dependency Injection containers. There’s nothing wrong with not-being a DI container, but using the wrong names for things leads to confusion on a topic filled with subtleties.

Of course, I’m sure that the folks using these Service Locator systems will have plently of rationalizations for why (1) the preferred system of their particular tribe really is a Dependency Injection container no matter what made-up rules Paul wants to apply, and (2) the distinction doesn’t really matter anyway, why even bring it up, just use their preferred system. I look forward to the comments on this one. ;-)

Afterword

Are you overwhelmed by a legacy PHP codebase? Do you want to improve it, but don’t know where to start? My upcoming book, Modernizing Legacy Applications in PHP, will lead you step-by-step through a series of small, incremental changes that will dramatically improve the quality of your legacy codebase. Subscribe to the mailing list below for pre-release chapters and more!

Sign up for Modernizing Legacy Applications in PHP:

First Name Email

11 thoughts on “Quicker, Easier, More Seductive: How To Tell A DI Container From A Service Locator

  1. Rouven Weßling

    Not sure if that’s what you’re looking for, but in Symfony2 DIC you can set the scope to prototype and a new instance will be created every time the service is injected someplace.

    I wonder how you’ve arrived at that rule though? Could you go more in depth how that is a defining characteristic of a DIC?

    Reply
    1. pmjones Post author

      @Hannes, @Rouven: Thanks! Links to examples?

      @Rouven: It is admittedly a rule of thumb and little more. Hell, it may be that there *really is* no difference, other than how the container is used. But I am not yet convinced that there’s no difference, since Fowler uses different terms for them in the same article, so I’m casting about for ways to *tell* the difference. As far as how I arrived at that rule, it strikes be that DI container should be able to, well, inject dependencies. If it does that *only* for services, then it seems more like a Service Locator to me.

      Reply
        1. pmjones Post author

          Thanks for this. I will update the post. Please note that I have a followup post that largely obviates this one, although this post is still required reading for full context.

          Reply
  2. Hannes

    Defining a service with

    scope: prototype

    yields a new instance each time you request a service from the symfony DIC.

    Reply
  3. Adrian Cardenas

    To be honest, I’m still confused.

    I understand that DI can be done through the constructor & getter/setter methods. Also that a Service Locator (usually injected via the constructor or a setter method) is an object that knows everything I may need in my object and said object can use it to pull those things already instantiated (via factory, or just instantiating the object).

    Where does the DIC come in? Is this something that is more specifici to framework/library code than app code?

    In my apps I tend to use (probably over use) a service locator that knows how to instantiate the objects that perform the business logic. Usually those have few dependencies, mostly the data mapper(s) and perhaps another object that performs actions on related data. So the only thing they get injected with at instantiation is the service locator to pull out the data mapper & the other object/service the one object may need. That’s if I’m using one of the larger frameworks (Zend/Symfony).

    If I’m doing a small project (using Slim or Silex) I typically don’t have very many objects and then I just instantiate them before the $app gets run and inject them into the app.

    So I don’t see where the DIC would come in a way that would make it different from a Service Locator. Then again, I’m still learning most of this and perhaps I’m just doing things wrong :)

    Reply
  4. Adrian Miu

    From a practical point of view I don’t see the difference. The fact that you can do $dic->newInstance($class) is irelevant for complex operations because the DiC should be able to construct a complex object. If $class depends on 3 other objects one being a singleton, another a new instance of a class and another being an object being a service registered within the DiC/Service locator you would need to register the object you want to get as a service. I don’t see a DiC having a newInstance() method capable of handling the example above without a performance hit/extra configuration work.

    The fact that the only thing that a ServiceLocator would need to become a DiC is a newInstance() method for simple classes makes the whole “fight” over the terminology unjustified.

    Maybe an “real-life” for the need of having newInstance() would help me see what I’m missing. For the moment I’m thinking of a data-mappper that must map the result of an SQL query onto a collection of objects, each object having to be a new instance of a class. Zend solved this problem by using a setResultsetPrototype($object) method and the mapping of the result is done on a clone of that object, so the importance of $dic->newInstance() still eludes me.

    Reply
  5. Pingback: Quicker, Easier, More Seductive: Names, Usage, and Intent | Paul M. Jones

  6. Pingback: Paul Jones: Quicker, Easier, More Seductive: How To Tell A DI Container From A Service Locator | htaccess

  7. Pingback: Paul Jones: Quicker, Easier, More Seductive: How To Tell A DI Container From A Service Locator | facebooklikes

Leave a Reply

Your email address will not be published. Required fields are marked *