drupal10, when do I need to implement __construct in custom controllerBase
In Drupal 10, when you create a custom controller that extends ControllerBase, you need to implement __construct() primarily for Dependency Injection (DI).
Here's a breakdown of when and why:
-
When you need to inject custom services or services not readily available via
ControllerBasemethods:ControllerBaseprovides convenient getter methods for common services likeconfig(),formBuilder(),entityTypeManager(),t(), etc. If your controller needs to interact with a service that isn't directly exposed byControllerBase(e.g., a custom service you've defined, or another core service likelogger.factory), you must explicitly inject it. -
How Dependency Injection works in Drupal controllers (the recommended way):
- Implement
ContainerInjectionInterface: Your custom controller class shouldimplement \Symfony\Component\DependencyInjection\ContainerInjectionInterface. WhileControllerBasedoesn't explicitly implement this interface, it uses the same pattern. - Implement the static
create()method: This is the factory method that Drupal's service container calls to instantiate your controller. Insidecreate(), you request the services you need from the$containerobject and then pass them as arguments to your__construct()method. - Implement
__construct(): This is where you receive the injected services as arguments and assign them to protected properties of your controller class. This makes them available for use in your controller's action methods.
- Implement
Example
<?php
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Custom controller that demonstrates dependency injection.
*/
class MyCustomController extends ControllerBase implements ContainerInjectionInterface {
/**
* The logger factory service.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* Constructs a new MyCustomController object.
*
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory service.
*/
public function __construct(LoggerChannelFactoryInterface $logger_factory) {
$this->loggerFactory = $logger_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('logger.factory') // Request the 'logger.factory' service
);
}
/**
* Displays a custom page.
*/
public function myPage() {
$this->loggerFactory->get('my_module')->notice('My custom page was accessed.');
return [
'#markup' => $this->t('Welcome to my custom page with logging!'),
];
}
}
Why this pattern is preferred (Dependency Injection):
- Testability: By injecting dependencies through the constructor, your controller becomes much easier to unit test. You can pass mock objects for your dependencies during testing, without having to rely on the full Drupal environment.
- Maintainability: It clearly defines what services your controller relies on, making the code easier to understand and maintain.
- Flexibility: If a dependency changes (e.g., a different implementation of a service is needed), you only need to update the
create()method or the service definition, not the entire controller logic. - Decoupling: Your controller is not responsible for creating its dependencies, only for using them. This promotes loose coupling between components.
In summary:
You implement __construct() in your custom ControllerBase when you need to bring in any external service or object that isn't provided as a convenience method by ControllerBase itself. Always pair it with the create() static factory method for proper dependency injection in Drupal.
Code Snippet
<?php
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Custom controller that demonstrates dependency injection.
*/
class MyCustomController extends ControllerBase implements ContainerInjectionInterface {
/**
* The logger factory service.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* Constructs a new MyCustomController object.
*
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory service.
*/
public function __construct(LoggerChannelFactoryInterface $logger_factory) {
$this->loggerFactory = $logger_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('logger.factory') // Request the 'logger.factory' service
);
}
/**
* Displays a custom page.
*/
public function myPage() {
$this->loggerFactory->get('my_module')->notice('My custom page was accessed.');
return [
'#markup' => $this->t('Welcome to my custom page with logging!'),
];
}
}