Dependency injection in Drupal: services and examples
Ala Batayneh
September 3, 2025
Updated on:
July 2, 2026
Dependency Injection (DI) is a fundamental design pattern in object-oriented programming. Instead of having a class create its own dependencies, DI supplies them from the outside. This approach keeps classes loosely coupled and easier to maintain and test.
By separating classes from the responsibility of creating their dependencies, we promotes flexibility, scalability, and cleaner architecture.
In this article, we'll explore the concept of Dependency Injection, walk through its implementation, highlight its benefits, and share real-world examples, applications, and best practices.
Dependency Injection (DI) is a design pattern where a class receives its dependencies from outside rather than creating them itself, which keeps code loosely coupled and testable. In Drupal, DI works through the service container: services are defined in YAML and injected into classes such as forms, blocks, plugins, and controllers, typically via a constructor and a static create() method.
What is Dependency Injection?
Dependency Injection is an application of the Inversion of Control Principle, so rather than instantiating dependencies within the class, they are injected through constructors, setters, or interfaces. Dependency Injection guiding rule states that high-level modules should not depend on low-level modules, but both should depend on abstractions.
In Drupal, Dependency Injection is made possible through service containers; a central registry that manages services (e.g., sending emails or caching data). These services are defined in YAML files, and can be injected into classes such as controllers, forms, blocks, or plugins.
Before Drupal 8, developers often relied on procedural code or static methods, which led to tightly-coupled hard-to-test systems. Dependency Injection allows implementations to be swapped, and makes unit testing much easier.
What are service containers in Drupal?
Service containers are the heart of Drupal's DI system, often referred to as the Dependency Injection Container (DIC), it is responsible for creating services and wiring them together.
When Drupal bootstraps, it compiles all the containers from service definitions provided by Drupal and the contributed modules.
Services in Drupal are defined in a module's *.service.yaml file. For example, a basic service definition would look like:
Here, my_module.example_service is the service ID. The class specifies the PHP class to instantiate, and the arguments list the dependencies to inject (prefixed with @ for other services). This structure allows Drupal to resolve injection dependencies recursively. And to optimize performance, containers support lazy loading, where services are only instantiated when first requested.
It's possible to use \Drupal::getContainer() to access the container programmatically, but this is generally considered a bad practice.
How do you implement Dependency Injection in Drupal components?
Dependency Injection is implemented in Drupal components, forms, blocks, and controllers, by injecting the services a class needs rather than calling them statically, usually through a constructor and a static create() method that reads from the container.
How do you inject a service into a form?
One common use case is injecting services into forms. Forms often need access to entities or configurations (and sometimes other services). So instead of using static class like \Drupal::entityTypeManager(), services should be injected instead.
For a custom form class extending FormBase:
namespace Drupal\my_module\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyForm extends FormBase {
protected $entityTypeManager;
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')
);
}
// Form methods here...
}
Here, EntityTypeManagerInterface is injected via the constructor, and the create() method fetches it from the container. This patterns allows instantiating forms for testing without a full Drupal environment.
How do you inject a service into a block or plugin?
Similarly, for blocks (or plugins), DI is applied in the plugin class. Blocks might need services like current user for example.
namespace Drupal\my_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyBlock extends BlockBase {
protected $currentUser;
public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->currentUser = $current_user;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_user')
);
}
// build method here...
}
This kind of injection allows the block to access the current user without static calls.
How do you inject dependencies into a controller?
Additionally, controllers also benefit from DI. In routing YAML file, controller class can be specified with the injected dependencies. For instance:
What are the benefits of Dependency Injection in Drupal?
Adopting DI patterns enhanced modularity by swapping or extending services without altering dependent classes. It also improves testability by allowing injecting mock data, so unit tests can be written in full isolation. It also enhances performance and encourages better code organization. Furthermore, the use of lazy services minimizes overhead.
Dependency Injection enhances maintainability and reduces spaghetti code, which eventually makes the whole application less prone to errors and easier to debug.
How do tags, decorators, and altering services work?
For more advanced scenarios, Drupal's DI support service tags. Tags categorize services for collection by other services. For example, the event_subscriber tag registers classes as event listeners automatically:
Decorators allow wrapping existing services to modify behavior without changing the original, this is particularly useful for extending core services.
Modules can implement hook_service_alter() or use service provider classes to override definitions, this is called Altering Services.
For dynamic services, factories can create instances based on runtime conditions - pretty powerful stuff.
In plugins, DI is integrated via the plugin manager which often injects dependencies into plugin instances.
What are Dependency Injection best practices in Drupal?
Use injections over static calls: Avoid using \Drupal::service() or \Drupal::currentUser() in classes.
Use meaningful services IDs and specify interfaces in arguments.
Inject interfaces rather than concrete classes to allow substitutions.
For config-dependent services, inject config.factory and load configs dynamically.
Don't inject the whole containers, inject only what is needed to avoid overhead.
For classes not under full container control, use factory methods and implement create() as mentioned earlier.
Always clear the cache after updating service files.
Dependency injection is a modern powerful patterns that transforms development process, fostering clean, extensible code, whether you're building forms, blocks, or custom services, Dependency Injection will elevate codebase overall quality.
Dependency injection in Drupal is a design pattern where a class receives its dependencies from the service container instead of creating them itself. Services are defined in YAML and injected into classes such as forms, blocks, plugins, and controllers. This keeps code loosely coupled, easier to test, and follows the Inversion of Control principle.
You inject a service into a Drupal form or block by adding it as a constructor argument and returning it from a static create() method that pulls the service from the container. For example, a form injects entity_type.manager, and a block injects current_user, so the class uses the service instead of a static call like \Drupal::entityTypeManager().
A service container in Drupal, also called the Dependency Injection Container (DIC), is the central registry responsible for creating services and wiring them together. When Drupal bootstraps, it compiles the container from service definitions provided by core and contributed modules. It supports lazy loading, so services are only instantiated when they are first requested.
You override or alter an existing Drupal service using service decorators, which wrap a service to modify its behavior without changing the original, or by implementing a service provider or hook_service_alter() to change service definitions. This is known as altering services and is useful for extending core services. Service tags, such as event_subscriber, register classes for automatic collection.