Services and Dependency Injection Container in Drupal 9
Same name and namespace in other branches
- 8 core/core.api.php \container
Overview of the Dependency Injection Container and Services.
Overview of container, injection, and services
The Services and Dependency Injection Container concepts have been adopted by Drupal from the Symfony framework. A "service" (such as accessing the database, sending email, or translating user interface text) is defined (given a name and an interface or at least a class that defines the methods that may be called), and a default class is designated to provide the service. These two steps must be done together, and can be done by Drupal Core or a module. Other modules can then define alternative classes to provide the same services, overriding the default classes. Classes and functions that need to use the service should always instantiate the class via the dependency injection container (also known simply as the "container"), rather than instantiating a particular service provider class directly, so that they get the correct class (default or overridden).
See https://www.drupal.org/node/2133171 for more detailed information on services and the dependency injection container.
Discovering existing services
Drupal core defines many core services in the core.services.yml file (in the top-level core directory). Some Drupal Core modules and contributed modules also define services in modulename.services.yml files. API reference sites (such as https://api.drupal.org) generate lists of all existing services from these files. Look for the Services link in the API Navigation block. Alternatively you can look through the individual files manually.
A typical service definition in a *.services.yml file looks like this:
path_alias.manager:
class: Drupal\path_alias\AliasManager
arguments: ['@path_alias.repository', '@path_alias.whitelist', '@language_manager']
Some services use other services as factories; a typical service definition is:
cache.entity:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
- { name: cache.bin }
factory: ['@cache_factory', 'get']
arguments: [entity]
The first line of a service definition gives the unique machine name of the service. This is often prefixed by the module name if provided by a module; however, by convention some service names are prefixed by a group name instead, such as cache.* for cache bins and plugin.manager.* for plugin managers.
The class line either gives the default class that provides the service, or if the service uses a factory class, the interface for the service. If the class depends on other services, the arguments line lists the machine names of the dependencies (preceded by '@'); objects for each of these services are instantiated from the container and passed to the class constructor when the service class is instantiated. Other arguments can also be passed in; see the section at https://www.drupal.org/node/2133171 for more detailed information.
Services using factories can be defined as shown in the above example, if the factory is itself a service. The factory can also be a class; details of how to use service factories can be found in the section at https://www.drupal.org/node/2133171.
Accessing a service through the container
As noted above, if you need to use a service in your code, you should always instantiate the service class via a call to the container, using the machine name of the service, so that the default class can be overridden. There are several ways to make sure this happens:
- For service-providing classes, see other sections of this documentation describing how to pass services as arguments to the constructor.
- Plugin classes, controllers, and similar classes have create() or createInstance() methods that are used to create an instance of the class. These methods come from different interfaces, and have different arguments, but they all include an argument $container of type \Symfony\Component\DependencyInjection\ContainerInterface. If you are defining one of these classes, in the create() or createInstance() method, call $container->get('my_service.name') to instantiate a service. The results of these calls are generally passed to the class constructor and saved as member variables in the class.
- For functions and class methods that do not have access to either of the above methods of dependency injection, you can use service location to access services, via a call to the global \Drupal class. This class has special methods for accessing commonly-used services, or you can call a generic method to access any service. Examples:
// Retrieve the entity_type.manager service object (special method exists).
$entity_type_manager = \Drupal::entityTypeManager();
// Retrieve the service object for machine name 'foo.bar'.
$foobar = \Drupal::service('foo.bar');
As a note, you should always use dependency injection (via service arguments or create()/createInstance() methods) if possible to instantiate services, rather than service location (via the \Drupal class), because:
- Dependency injection facilitates writing unit tests, since the container argument can be mocked and the create() method can be bypassed by using the class constructor. If you use the \Drupal class, unit tests are much harder to write and your code has more dependencies.
- Having the service interfaces on the class constructor and member variables is useful for IDE auto-complete and self-documentation.
Defining a service
If your module needs to define a new service, here are the steps:
- Choose a unique machine name for your service. Typically, this should start with your module name. Example: my_module.my_service.
- Create a PHP interface to define what your service does.
- Create a default class implementing your interface that provides your service. If your class needs to use existing services (such as database access), be sure to make these services arguments to your class constructor, and save them in member variables. Also, if the needed services are provided by other modules and not Drupal Core, you'll want these modules to be dependencies of your module.
- Add an entry to a modulename.services.yml file for the service. See Discovering existing services above, or existing *.services.yml files in Core, for the syntax; it will start with your machine name, refer to your default class, and list the services that need to be passed into your constructor.
Services can also be defined dynamically, as in the \Drupal\Core\CoreServiceProvider class, but this is less common for modules.
Service autowiring
Instead of specifying arguments explicitly, the container can also autowire a service's arguments from the constructor's type-hints. See the Symfony documentation on defining services dependencies automatically for details.
Service tags
Some services have tags, which are defined in the service definition. See Service Tags for usage.
Overriding the default service class
Modules can override the default classes used for services. Here are the steps:
- Define a class in the top-level namespace for your module (Drupal\my_module), whose name is the camel-case version of your module's machine name followed by "ServiceProvider" (for example, if your module machine name is my_module, the class must be named MyModuleServiceProvider).
- The class needs to implement \Drupal\Core\DependencyInjection\ServiceModifierInterface, which is typically done by extending \Drupal\Core\DependencyInjection\ServiceProviderBase.
- The class needs to contain one method: alter(). This method does the actual work of telling Drupal to use your class instead of the default. Here's an example:
public function alter(ContainerBuilder $container) {
// Override the language_manager class with a new class.
$definition = $container->getDefinition('language_manager');
$definition->setClass('Drupal\my_module\MyLanguageManager');
}
Note that $container here is an instance of \Drupal\Core\DependencyInjection\ContainerBuilder.
See also
https://www.drupal.org/node/2133171
\Symfony\Component\DependencyInjection\ContainerInterface
File
- core/
core.api.php, line 730 - Documentation landing page and topics, plus core library hooks.
Classes
Name | Location | Description |
---|---|---|
Container |
core/ |
Provides a container optimized for Drupal's needs. |
ContainerBuilder |
core/ |
Drupal's dependency injection container builder. |
CoreServiceProvider |
core/ |
ServiceProvider class for mandatory core services. |
PhpArrayContainer |
core/ |
Provides a container optimized for Drupal's needs. |
ServiceProviderBase |
core/ |
Base service provider implementation. |
Interfaces
Name | Location | Description |
---|---|---|
ServiceModifierInterface |
core/ |
Interface that service providers can implement to modify services. |
ServiceProviderInterface |
core/ |
Interface that all service providers must implement. |