Dependency injection¶
- Published at
March 10, 2020
- Author
Erlend ter Maat
- Tags
Drupal, Drupal service container, Dependency injection
Drupal services offer a way to expose functionality to other modules. Other modules commonly use that services by means of dependency injection.
Technical implementation¶
Dependency injection requires a mapping where the services are defined, and a way to inject the services into objects as they are loaded. Drupal borrowed the dependency injection functionality from the symfony framework. Hence, Services are stored in yaml files. A basic example.
services:
custom_service:
class: [ class name ]
another_service:
class: [ another class name ]
Frameworks exists, like Masonite (Python) or Laravel (PHP), where services are recognized automatically. At drupal you have to be explicit. Next two examples show the two common ways to add a service by the id of ‘another_service’ to the ‘custom_service’:
By means of services¶
At the services.yml file the dependency is defined a an argument.
# module-name.services.yml
services:
custom_service:
class: [ class name ]
arguments:
- '@another_service'
At the implementation of the class the dependencies are implemented / processed at the constructor.
class CustomService {
protected $anotherService;
public function __construct(AnotherServiceInterface $anotherService) {
$this->anotherService = $anotherService;
}
}
By means of Service Containers¶
Providing a create method that knows what other services are required to build the service:
class CustomServiceContainingClass implements ContainerInjectionInterface {
public static function create(Container $container) {
// Create an instance of this class using services from the service container.
return new static($container->get('another_service');
}
protected $anotherService;
public function __construct(AnotherServiceInterface $anotherService) {
$this->anotherService = $anotherService;
}
}
A ContainerInjection enabled class may be defined as a service, but services don’t require the ContainerInjectionInterface. You only need to implement the ContainerInjectionInterface if your service is not always loaded by the drupal services system; but only/also by the class resolver.
Class resolver service¶
The most common case of dependency injection is by means of services - classes that require one instance per request. Dependency injection can also be used for classes where multiple instances of the same class are required / make more sense.
services:
custom_service:
class: CustomService
arguments:
- '@class_resolver'
class CustomService {
function usingTheClassResolver() {
$containingInstance = $this
->classResolver
->getInstanceFromDefinition(
CustomServiceContainingClass::class
);
}
}
Benefits¶
The clean thing about using this is that when something changes in the dependencies of the CustomServiceContainingClass that is the only place where the change of code happens. Without a dependency injection a developer should change all classes that lead to the place where de dependency is used. Now you add a class resolver as dependency, and this object manages the dependencies at the place where they are needed. The combination of services and dependency injection offers a nice way to implement the single responsibility principle.
Dependency injection is a mechanism to inventorize the external dependencies of a class. It allows you to move code that solves another problem out of the scope of code that solves a specific business case.