Skip to main content

drupal10, when do I need to implement __construct in custom controllerBase

DrupalVIP Support

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:

  1. When you need to inject custom services or services not readily available via ControllerBase methods: ControllerBase provides convenient getter methods for common services like config(), formBuilder(), entityTypeManager(), t(), etc. If your controller needs to interact with a service that isn't directly exposed by ControllerBase (e.g., a custom service you've defined, or another core service like logger.factory), you must explicitly inject it.

  2. How Dependency Injection works in Drupal controllers (the recommended way):

    • Implement ContainerInjectionInterface: Your custom controller class should implement \Symfony\Component\DependencyInjection\ContainerInjectionInterface. While ControllerBase doesn'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. Inside create(), you request the services you need from the $container object 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.

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.

 

<?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!'),
    ];
  }

}