Dependency injection is an example of an inversion of control software design pattern. Inversion of control is a principle that involves having a generic and reusable framework to make calls and delegate to more custom code. In Drupal 7, an example of this is the menu system. A request comes in and is matched to a route and then Drupal loads the module file and calls the appropriate function. Dependency injection specifically allows you to define when your class needs a dependency and have those needs fulfilled by the system.
When creating new functionality in Drupal 8, you write your dependencies into your class constructor and, when the system creates your class, they are passed into it.
A key advantage to using dependency injection is that you do not have to know how to instantiate the services that your class depends on. Take, for example, a simple database connection. In order to connect to the database you need the appropriate driver, username, password, port, database name, or schema, and so on. These, in turn, could be stored in configuration files, in environment variables, or some other data storage. But when you want to use a database connection, you don't care about any of these implementation details. You ask for a database connection and one is provided.
Dependency injection also simplifies the process of testing. Dependency injection allows a developer to override the versions of services provided by the system with custom ones. This is often used to provide mock services that return pre-defined outcomes so you can easily test your logic. Imagine you were building a module that sent email and you wanted to test it without, of course, actually sending an email. You could create a class that implemented the MailManagerInterface and, instead of sending the email, allow your test to verify that it would have been sent.
The other side of the dependency injection coin is the service locator. There are times when you don't know specifically which functionality you'll need when you write your class. For example, Drupal 8 has built-in support for serialization to and from YAML, JSON, and basic PHP serialization. Suppose you wanted to take the TestBlock plugin from earlier and have to output its configuration to one of those three formats, depending on which one an administrator had selected. You could write that like:
protected $serializer; public function __construct($configuration, $plugin_id, $plugin_definition, Json $json_serializer, Yaml $yaml_serializer, PhpSerialize $php_serializer)
{
parent::__construct($configuration, $plugin_id,
$plugin_definition); switch ($configuration['serializer'])
{
case 'json': $this->serializer = $json_serializer; break;
case 'yaml': $this->serializer = $yaml_serializer; break;
case 'php': $this->serializer = $php_serializer; break;
}
}
There are a couple of downsides to this approach. The first is that it could require Drupal to instantiate serializers that are never used. The second is that it makes it difficult to extend if you wanted to added additional serializers: you'd have to add them to the constructor signature and then to the switch statement.
Alternatively, you could load the Service Container and request the serialization method you want by the machine name. So, that would look like:
protected $serializer; public function __construct($configuration, $plugin_id, $plugin_definition)
{
parent::__construct($configuration, $plugin_id,
$plugin_definition); $this->serializer = \Drupal::getContainer()
->get('serialization.' + $this->configuration['serializer']);
}