Dependency injection

How Drupal classes get services such as the current user: declared in services.yml or a create() method and passed to the constructor.

Learn DrupalDrupal glossary

Dependency injection means a class is handed the objects it needs instead of building or looking them up itself. In Drupal the objects are services, such as the database connection or the current user, and the service container supplies them when it creates the class. The container comes from the Symfony DependencyInjection component.

How dependency injection works in Drupal 11

A service is defined in a MODULE.services.yml file. The entry gives the service a machine name, a class and a list of arguments. Each argument written as @service_name is another service that the container passes to the constructor. Core's own definitions live in core.services.yml. A module can also define a different class for an existing service name.

Some classes are not services. When a controller, form or plugin class has a static create() method from the matching interface, Drupal calls it with the container to build the object. ContainerInjectionInterface::create() should pass the needed services to the constructor and not the container itself.

The container can also work out constructor arguments from type hints. This is autowiring, and it can be switched on per service with autowire: true. A type-hinted interface needs an alias that points to a service. Core registers such aliases, for example Drupal\Core\Session\AccountInterface for current_user. The AutowireTrait gives a class a create() method that autowires. ControllerBase uses it, and FormBase does since Drupal 11.4. The trait's documentation warns that it uses reflection and can be slow for classes created many times. Classes in a module's Hook namespace are registered as autowired services, as explained under hooks.

How the service container passes services to a constructorThe System module's services.yml file defines the service system.timezone_resolver with the class Drupal\system\TimeZoneResolver and the arguments @current_user and @config.factory. The service container reads the definition, creates or reuses those two services, and passes them to the TimeZoneResolver constructor in the listed order: AccountInterface $current_user first, ConfigFactoryInterface $config_factory second.system.services.yml (System module)system.timezone_resolver:class: Drupal\system\TimeZoneResolverarguments: ['@current_user', '@config.factory']the service container reads the definitionService container@current_userCreates or reuses an AccountProxy@config.factoryCreates or reuses a ConfigFactory1st argument2nd argumentTimeZoneResolver::__construct()AccountInterface$current_userConfigFactoryInterface$config_factoryA controller, form or plugin that is not a service gets its services through a static create() method.
The container reads the arguments in system.services.yml and passes those services to the TimeZoneResolver constructor in the same order.

Example

The service definition lists what the class needs:

services:
          my_module.greeter:
            class: Drupal\my_module\Greeter
            arguments: ['@current_user', '@datetime.time']

The constructor receives those two services in the same order:

use Drupal\Component\Datetime\TimeInterface;
        use Drupal\Core\Session\AccountInterface;

        class Greeter {

          public function __construct(
            protected AccountInterface $currentUser,
            protected TimeInterface $time,
          ) {}

        }

Common mistakes

  • Passing the whole container to the constructor. The interface documentation asks for the specific services only. Passing the container hides what the class depends on.
  • Creating a service with new. Core's container topic says to always get a service through the container, so that a class another module swapped in is the one you get.
  • Getting the order wrong. The container passes the arguments in the order of the arguments list, so the constructor must match it.

For marketing and content teams

You never edit this directly, but the term explains why a developer can swap a behavior on your site without changing every class that uses it. A custom module can replace a core service class, and every part of the site that asks for that service gets the new one. When someone says a feature "is a service", they mean it sits in this container.

Sources

Questions about dependency injection in Drupal

With dependency injection the class receives its services through the constructor. With \Drupal::service() the class reaches out to the container itself, which is called service location. Core prefers injection because it makes tests easier and the dependencies visible in the constructor.

When injection is not possible, such as in a procedural function in a .module file or a static method. Core's documentation describes the \Drupal class as the fallback for code that has no access to the container through a constructor or a create() method.

Related terms

All Drupal terms from A to Z