Dependency injection - a quick intro

One of the great new things in Drupal 8 is that it comes with a dependency injection container ("DIC").
In fact, it borrows the dependency injection container component from Symfony..

If you don't like the DIC from Symfony, you could try Pimple instead.
Or you can skip to the next article, and build your own.

There are plenty of great resources about dependency injection and containers on the web. e.g.
"Service Container in the Symfony docs

The following blog post is not going to tell you anything new, but it will be a nice intro for future articles.

Services, dependencies and injection

A DIC is a tool that helps to organize and lazy-instantiate your "services".
But maybe we need to start a little earlier. What are services? What are dependencies? What is injection?

Prerequisites:

  1. Your code is organized in classes, that will be instantiated as objects.
    (This is just object-oriented programming, nothing magical so far)
  2. Some objects encapsulate data: A user, a blog post, an invoice, etc.
    Let's forget about these for a moment.
  3. Other objects will be used all around the application to perform specific tasks.
    E.g. a database connection, a page cache, etc.
    These are typically meant to stick around for the entire request, and we only need one instance for different consumers.
    Traditionally, such objects were often implemented as singletons, but this has always been a bad idea.
    We will refer to these as "services".
  4. Objects that want to use a service should have this service "injected" in the constructor (or a setter method). We say this object "depends" on the service, and we refer to the service as a "dependency".
  5. Non-service objects can depend on services.
  6. Services can depend on other services.
  7. Services can be lazy-instantiated when they are needed.

In fact, the term "Service" can have different meanings depending on the context.
For the sake of this article, it is really any object that we want to make available with the container. The definition on the Pimple doc page is quite close:

A service is an object that does something as part of a larger system. Examples of services: Database connection, templating engine, mailer. Almost any object could be a service.

With the dependency injection requirement, we avoid the following:

  1. Service instances no longer need to be available through global symbols such as global variables, static class properties, procedural functions, or static methods.
  2. Components no longer need to care about where their dependencies come from, or how they are implemented.
    E.g. a component that needs a database connection does not care whether the connection uses MySQL or something else, and whether it the actual main database or a "fake" database that we created for testing purposes.
  3. Authors of service classes don't need to worry about how and where their services are going to be used or instantiated.

Plugging it together

So far, so good. We have a bunch of services with interdependencies, and want to use them together.
Example:

<?php
$cook
= new Cook();
$stove = new Stove();
$kitchen = new Kitchen($stove, $cook);
$restaurant = new Restaurant($kitchen);
$restaurant->serveDinner();
?>

This works, but it can become unmanageable if we have too many services. So, what next?

Identifying the services

Before we start with the container, we first want to identify which services we have or need, and give them names.
In our case, this is "cook", "stove", "kitchen", "restaurant".

Note that there is not always a 1:1 relationship of services and classes!

  • It can happen that the same class is used for more than one service. E.g. if we had a "cook" and a "chef" which would both use the "Cook" class.
  • We might use different classes for the same service, depending on environment parameters. E.g. the "stove" service might be an "ElectricStove" or a "GasStove", depending on the situation.

The container

To better organize the various components - cook, stove, kitchen, restaurant - we want an object that

  • knows how to instantiate each of the components.
  • keeps an instance of each component, once created.
  • creates each component when needed, if it was not already created.

E.g.

<?php
class RestaurantContainer {

  private
$preferElectricStove;

  private
$services = array();

  function
__construct($preferElectricStove = TRUE) {
   
$this->preferElectricStove = $preferElectricStove;
  }

  function
getService($name) {
    return isset(
$this->services[$name])
      ?
$this->services[$name]
     
// Create the service, if it does not already exist.
     
: $this->services[$name] = $this->createService($name);
  }

  private function
createService($name) {
    switch (
$name) {
      case
'stove':
       
// Use a different stove type depending on environment parameters.
       
return $this->preferElectricStove
         
? new ElectricStove()
          : new
GasStove();
      case
'cook':
        return new
Cook();
      case
'chef':
        return new
Cook();
      case
'kitchen':
        return new
Kitchen($this->getService('stove'), $this->getService('cook'), $this->getService('chef'));
      case
'restaurant':
        return new
Restaurant($this->getService('kitchen'));
      default:
        throw new \
Exception("Unknown service '$name'.");
    }
  }

}
?>

This is quite a naive implementation of a service container. But it works!
The getService() method is already quite similar to what you will find in other implementations. The createService() part, however, will usually look quite different, and allow for dynamic wiring of service factories.

We will talk more about this in future blog posts. Keep following!