1

In a basic Controller class, I tried to load node data using the code from this accepted answer. I.e:

$nid = 1;
$node_storage = $this->entityTypeManager()->getStorage('node');
$node = $node_storage->load($nid);

However, this answer does not have sufficient detail to help me. Just putting it inside my very simple controller class, I get a WSOD with this error:

Error: Call to undefined method Drupal\mymodule\Controller\MyModuleController::entityTypeManager() in …

Following the suggestions below the answer by @apaderno, I understood that I I need to declare ControllerBase as the parent of my Controller class.

It now looks like this:

namespace Drupal\mymodule\Controller;

use Drupal\Core\Controller\ControllerBase;

class MyController extends ControllerBase {

  public function mymodule() {
      $nid = 1;
      $node_storage = $this->entityTypeManager()->getStorage('node');
      $entity = $node_storage->load($nid);
      dpm($entity, 'entity');
  }
}

This is still not a working controller. It still produces a WSOD with this error:

Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException: The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned null. Did you forget to add a return statement somewhere in your controller?

But the entity now gets loaded 😊, and the exception is something I know how to fix.

I guess my problem is that I am a fairly inexperienced Drupal 10 developer, and don't know all the things about the Drupal 10 programming environment that it seems that most other Drupal developers take for granted that one already knows.

6
  • 1
    Does this answer your question? Dependency injection to load node data
    – leymannx
    May 20, 2023 at 18:33
  • @leymannx No it does not. It is linked on the first line on my question, and while I try to use exactly that code, there is too little detail to help me. I'll try to expand the question to make the problem clearer. May 20, 2023 at 18:59
  • 1
    It is possible the controller class file is in the wrong place or has the wrong name.
    – mona lisa
    May 20, 2023 at 22:03
  • @cilefen It could be, but that should be a change that happened after adding ControllerBase as parent class, or Drupal could not first complain that Drupal\mymodule\Controller\MyModuleController::entityTypeManager() is an undefined method (which means Drupal found the Drupal\mymodule\Controller\MyModuleController class).
    – apaderno
    May 21, 2023 at 7:54
  • Controllers have to return a Response object or a render array. You need to use xdebug if you want to step through and debug during development. Devel won't cut it here. You're getting that error because dpm doesn't halt execution for code inspection like xdebug would.
    – Kevin
    May 21, 2023 at 13:48

1 Answer 1

2

entityTypeManager() is a method defined from the ControllerBase class, even in Drupal 10. ControllerBase::$entityTypeManager is one of its properties, but I would use ControllerBase::entityTypeManager() because it initializes that property, if it was not already initialized.

protected function entityTypeManager() {
  if (!isset($this->entityTypeManager)) {
    $this->entityTypeManager = $this->container()->get('entity_type.manager');
  }
  return $this->entityTypeManager;
}

If that is not one of the parent classes for the controller, I would do as the documentation for that class says (if the controller class does not contain trivial code) and use the ContainerInjectionInterface to implement the create() method necessary to inject the required dependencies.

For reference, this is the suggestion given in the ControllerBase documentation. (Emphasis is mine.)

Controllers that use this base class have access to a number of utility methods and to the Container, which can greatly reduce boilerplate dependency handling code. However, it also makes the class considerably more difficult to unit test. Therefore this base class should only be used by controller classes that contain only trivial glue code. Controllers that contain sufficiently complex logic that it's worth testing should not use this base class but use ContainerInjectionInterface instead, or even better be refactored to be trivial glue code.

If you want to have available a entityTypeManager() method without extending that class, I would use code similar to the following one.

use Drupal\Core\Entity\EntityTypeManager;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;

class Controller implements ContainerInjectionInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManager
   */
  protected $entityTypeManager;

  /**
   * Constructs a new Controller object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
   *   The entity type manager.
   */
  public function __construct(EntityTypeManager $entityTypeManager) {
    $this->entityTypeManager = $entityTypeManager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container->get('entity_type.manager');
  }

  /**
   * Retrieves the entity type manager.
   *
   * @return \Drupal\Core\Entity\EntityTypeManager
   *   The entity type manager.
   */
  public function entityTypeManager() {
    return $this->entityTypeManager;
  }

}

Otherwise, to simply use the ControllerBase class, I would take the BookController code as example.

use Drupal\book\BookExport;
use Drupal\book\BookManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Link;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Controller routines for book routes.
 */
class BookController extends ControllerBase {

  /**
   * Constructs a BookController object.
   *
   * @param \Drupal\book\BookManagerInterface $bookManager
   *   The book manager.
   * @param \Drupal\book\BookExport $bookExport
   *   The book export service.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer.
   */
  public function __construct(BookManagerInterface $bookManager, BookExport $bookExport, RendererInterface $renderer) {
    $this->bookManager = $bookManager;
    $this->bookExport = $bookExport;
    $this->renderer = $renderer;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('book.manager'),
      $container->get('book.export'),
      $container->get('renderer'));
  }

  /**
   * Prints a listing of all books.
   *
   * @return array
   *   A render array representing the listing of all books content.
   */
  public function bookRender() {
    $book_list = [];
    foreach ($this->bookManager->getAllBooks() as $book) {
      $book_list[] = Link::fromTextAndUrl($book['title'], $book['url']);
    }
    return [
      '#theme' => 'item_list',
      '#items' => $book_list,
      '#cache' => [
        'tags' => $this->entityTypeManager()->getDefinition('node')->getListCacheTags(),
      ],
    ];
  }

}

BookController adds its own dependencies, but it can still use $this->entityTypeManager() to get the entity type manager.

As for the exception thrown in line 135, this is the code that causes that exception.

$entity_types = $this->getEntityTypes();
$parameter_definitions = $route->getOption('parameters') ?: [];

$result = FALSE;

if (is_array($controller)) {
  [$instance, $method] = $controller;
  $reflection = new \ReflectionMethod($instance, $method); // Line 135
}
else {
  $reflection = new \ReflectionFunction($controller);
}

For some reason, PHP does not found the \Drupal\mymodule\Controller\MyController class, for example because the class namespace is wrong. (There could be other reasons for which PHP autoload cannot load that class, but I could not recall them at the moment.)

12
  • Thanks for answering! My controller do not extend any parent classes. I would appreciate it if you could expand your answer a bit to spell out how to use ContainerInjectionInterface to get access to the method in my class. May 20, 2023 at 18:12
  • If you want to use a entityTypeManager() method without using the ControllerBase class, I can show the code I would use, yes.
    – apaderno
    May 20, 2023 at 18:15
  • Thanks for the help. I want to have access to the entityTypeManager() method inside my Controller. I am too inexperienced to have an opinion about whether to do it with or without the ControllerBase class. I only want to see some code that implements the recommended way to do this. May 20, 2023 at 18:20
  • @FreeRadical In controllers $this->entityTypeManager is already there. Simply scan the core/ directory for living examples.
    – leymannx
    May 20, 2023 at 18:31
  • @leymannx Yes. There are also controllers that inject their own dependencies and still use $this->entityTypeManager(), like BookController.
    – apaderno
    May 20, 2023 at 18:40

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.