For the first time I am trying to architect a reasonably complex Drupal 8 module, and not sure of best practice for the controller.

  • I need to route paths to a controller.
  • I intend the controller to be thin (very little logic, doesn't need to testable)
  • Heavy lifting will be via service class/es configured and defined by the module.

Extending ControllerBase should be the right path for a thin controller, however when I look in Core at the moment I notice a number of controllers that both extend ControllerBase and implement ContainerInjectionInterface. I would only implement ContainerInjectionInterface to be able to inject a different container for testing, not just selection of service in the create method, but Core is still in flux and I am not sure exactly why they are doing this.

I feel that I should extend ControllerBase, add a __construct that fetches the container and pulls in the non-standard service/s that I require via $this->container()->get() and indeed that does work. I also don't need the create method required to implement ContainerInjectionInterface.

Am I correct? I know I will need to go through a few iterations of getting the services right but want to get THIS bit right from the start and understand why I am doing it.

Thanks in advance.

  • 1
    In this context, I have more context with the Symfony components rather than Drupal. With that said, I don't understand why you'd create a special container solution with __construct rather than just implement ContainerInjectionInterface.
    – Letharion
    Nov 14, 2013 at 16:10
  • Because with only extending controllerBase I can access the container provided in my __construct function to get the instance of my service (a bunch of standard services are automatically available but not my non-standard one). Nov 14, 2013 at 22:27
  • Sorry editing on a tablet, this is extension of above comment. If I implement ContainerInjection interface I have to add a create method to pull desired services from an injected controller, AND a construct method to receive them as parameters, rather long winded. Nov 14, 2013 at 22:38
  • ControllerBase::container() is being removed shortly. Do not rely on it. Nov 21, 2013 at 22:22

1 Answer 1


ContainerInjectionInterface is used as a part of a static factory method pattern.

This way, a class's constructor can specify all of the services it needs, and its factory method, in this case create(), can satisfy the constructor by retrieving services from the container.

The instantiated controller should never have its own copy of the container, if at all avoidable.

A brief example:


namespace Drupal\book\Controller;

use Drupal\book\BookManager;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class BookController extends ControllerBase implements ContainerInjectionInterface {
  protected $bookManager;

  public function __construct(BookManager $bookManager) {
    $this->bookManager = $bookManager;

  public static function create(ContainerInterface $container) {
    return new static(

  public function bookRender() {
    $book_list = array();
    foreach ($this->bookManager->getAllBooks() as $book) {
      $book_list[] = l($book['title'], $book['href'], $book['options']);
    return array(
      '#theme' => 'item_list',
      '#items' => $book_list,

This would be declared as a controller in a routing file like this:

_content: \Drupal\book\Controller\BookController::bookRender

  • 1
    Removing container from ControllerBase that is food for thought, feels like many contrib devs will start with this class and then get stuck when they need to access non-standard services. ControllerBase was touted as lighter way to make a controller but begins to look like a dead end. Nov 26, 2013 at 14:18
  • 1
    You would use ContainerInjectionInterface for any non-standard services. ControllerBase is just to get you started with the common services. Nov 28, 2013 at 5:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.