Constructor Injection (ContainerInjectionInterface)
When to Use
When injecting services into controllers, forms, or any class that implements ContainerInjectionInterface — the standard Drupal dependency injection pattern.
Steps
-
Implement ContainerInjectionInterface
namespace Drupal\my_module\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Database\Connection; use Psr\Log\LoggerInterface; class MyController extends ControllerBase implements ContainerInjectionInterface { public function __construct( protected Connection $database, protected LoggerInterface $logger, ) {} public static function create(ContainerInterface $container) { return new static( $container->get('database'), $container->get('logger.channel.my_module'), ); } public function content() { $this->logger->info('Controller accessed'); return ['#markup' => 'Hello']; } } -
Use AutowireTrait for automatic create() (Drupal 10.2+)
namespace Drupal\my_module\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\AutowireTrait; use Drupal\Core\Database\Connection; use Psr\Log\LoggerInterface; class MyController extends ControllerBase { use AutowireTrait; // Provides create() automatically public function __construct( protected Connection $database, protected LoggerInterface $logger, ) {} public function content() { return ['#markup' => 'Hello']; } }
Decision Points
| At this step... | If... | Then... |
|---|---|---|
| Choosing injection method | Class is a controller or form | Implement ContainerInjectionInterface |
| Choosing injection method | Class is a plugin | Use ContainerFactoryPluginInterface (see Plugin Injection) |
| Writing create() method | You have many dependencies | Use AutowireTrait to reduce boilerplate |
| Writing create() method | You want explicit clarity | Manual create() shows exactly what's injected |
Pattern
Form Example:
namespace Drupal\my_module\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
class MyForm extends FormBase implements ContainerInjectionInterface {
public function __construct(
protected ConfigFactoryInterface $configFactory,
) {}
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
);
}
public function getFormId() {
return 'my_module_my_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->configFactory->get('my_module.settings');
// ...
}
}
Reference: /core/lib/Drupal/Core/DependencyInjection/ContainerInjectionInterface.php, /core/modules/system/src/Controller/SystemController.php (example)
Common Mistakes
- Not calling parent constructor — If extending ControllerBase/FormBase with own __construct, parent may have protected properties you break
- Type hinting Container instead of ContainerInterface — Use the interface for testability
- Static property assignment in create() — create() must return a NEW instance, not modify static state
- Putting logic in __construct — Keep constructors simple; only assign dependencies to properties
- Not using AutowireTrait when available — Why write boilerplate when AutowireTrait generates create() for you?