I am working from this tutorial and I want to load a node entity in a Controller class.

The following construct (copied almost verbatim):

$entity = \Drupal::entityTypeManager()->getStorage('node')->load(1);

works as expected.

The same tutorial says:

Avoid using the static Entity::load() method in object oriented code. Instead use dependency injection to inject the entity type manager and load the entity with $this->entityTypeManager->getStorage($entity_type)->load($entity_id). This ensures that the code is properly decoupled and can be unit tested.

Based on that, I tried the following variant (i.e. without parenthesises after entityTypeManager), which is an almost verbatim copy of what is suggested:

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

However, this produces the following error:

Error: Call to a member function getStorage() on null in Drupal\mymodule\Controller\MyModuleController->mymethod() (…).

Edit: After reading the answer by apaderno, I've changed my Controller class to look like this:

use Drupal\Core\Controller\ControllerBase;

class MyController extends ControllerBase {

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

}

This works!

Edit 2: However. I was too rash in assuming that the line I first copied from the tutorial was wrong. The comment by Berdir (posted 21 May 2023) sets the record straight.

Calling the method, using entityTypeManager() is not really using DI. It's a handy shorthand because ControllerBase makes this method available to thin Controllers without actually using DI. To have DI, one needs to go through the steps outlined by Jaypan.

DI is a lot more complex than I first thought, and the comments below from both Jaypan and apaderno has been very illuminating for me.

Comments

jaypan’s picture

You didn't show where you are using this code. There are two possibilities - you're using it statically, in a hook or a static class method, or you're using it in a non-static OOP method.

If you are in a hook or a static method, $this does not exist, so you will use:

$node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);

However if you are working from within a non-static method of a class, $this will be available, and you will need dependency injection.

Dependency Injection

Any function declared in a class without the static keyword will have access to the $this object, and can use dependency injection. Setting up dependency injection requires three things:

  1. Declaring the dependency, so it can be prepared and injected in the class when the class is instantiated into memory
  2. Declaring a property, where the dependency will be stored for access from within the class using $this
  3. Storing the injected dependency in the class property, when the class is instantiated into memory

1) Declare the dependency

Declaring dependencies is done in one of two ways, either in the [MODULE].services.yml file, for classes that are services, or in the create() method, for classes that are not services. In this example, the dependency to be injected is the entity_type.manager service, and one of the following methods will be used:

1A) Declaring a dependency for a service:

This is done in the arguments of the[MODULE].services.yml file:

services:
  example_service:
    class: Drupal\example\Service\ExampleServiceInterface
    arguments:
      - '@entity_type.manager`

The Drupal registry needs to be cleared after declaring services, as they are cached and will only be picked up after a cache clear.

1B) Declaring a dependency for a class that is not a service

This is done in the create() method of the class:

use Symfony\Component\DependencyInjection\ContainerInterface;

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

2) Create a property to hold the injected dependency

Next, a property is declared that will store the injected dependency:

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

This sets up $this->entityTypeManagerProperty for the class. However, at this point, the value of the property is NULL, as it has only been declared, with no value yet assigned to it. That will happen in step 3.

As an aside, I would usually call the property $entityTypeManager, however for the purposes of clarity for this tutorial, I am using $entityTypeManagerProperty to visibly differentiate from the injected dependency which I will call $entityTypeManagerInjected in the next step. In a normal class, I would name both the property and the injected dependency $entityTypeManager. This makes for clarity and consistency day-to-day coding, but makes it harder to differentiate what the differences are when learning, which is why I've used these names in this example.

3) Store the injected dependency in the class property when the class is loaded

Finally, the injected dependency is set as the value of the property, when the class is loaded. This is done in the __construct() method of the class, which is a "magic" method called every time this class is instantiated into memory (aka NOT called statically);

/**
 * Constructs a [CLASS NAME] instance.
 *
 * @property \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManagerInjected
 *   The injected entity type manager.
 */
public function __construct(EntityTypeManagerInterface $entityTypeManagerInjected) {
  // Store the injected dependency in the property defined earlier.
  $this->entityTypeManagerProperty = $entityTypeManagerInjected;
}

Using the injected dependency

Now that the dependency has been injected $this->entityTypeManagerProperty contains the injected dependency, and can be used in any non-static method in the class as follows:

$node = $this->entityTypeManagerProperty->getStorage('node')->load($nid);
  1. A declaration of the dependency, so it can be retrieved when the class is loaded
  2. A property to store the injected dependency, so that that the dependency can be called from $this
  3. Storing the injected dependency in the property when the class is loaded

Contact me to contract me for D7 -> D10/11 migrations.

gisle’s picture

Sorry about not providing context about where I am using this code. It is used in a very simple Controller class (i.e. inside a non-static method inside a class). I have expanded the OP to show what this class looks like.

- gisle

avpaderno’s picture

The Call to a member function getStorage() on null in Drupal\mymodule\Controller\MyModuleController->mymethod() error is correct, since the code is using a property ($this->entityTypeManager) that has not been yet set. ControllerBase::entityTypeManager() sets ControllerBase::$entityTypeManager to the correct value.

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

The correct way to get the entity type manager is calling the method, not accessing the property.

For procedural code, or for static methods, the following line loads the node whose ID is 1.

$entity = \Drupal::entityTypeManager()->getStorage('node')->load(1);

For a non-static method implemented in a class that extends ControllerBase, the line to use is the following one.

$entity = $this->entityTypeManager()->getStorage('node')->load(1);
gisle’s picture

However, it looks like the tutorial I've been following is wrong on how to get theentityTypeManager injected.

Edit: I was rash to say that the tutorial was wrong – striking out that misinformation from this comment.

I agree with what apaderno says in the comment below, that the

page does not give details sufficient to understand the example

- gisle

avpaderno’s picture

Either that documentation page does not give details sufficient to understand the example code, or the example code is not much detailed.
$this->entityTypeManager->getStorage($entity_type)->create() would create problems whenever that line is used in a class that extends ControllerBase or in a class that does not have parent classes, if that class does not initialize $this->entityTypeManager correctly. In the first case, PHP would complain about calling a method for a variable that contains NULL; in the second case, PHP would probably complain about a not existing property.

fkelly12054@gmail.com’s picture

Your post (@Gisle) and the tutorial and comments in this thread are very timely, at least for me.  I'm a "trainee" maintainer for the contrib Juicebox module (and believe me I would happily leave the work to more experienced Drupal programmers if such could be found) and have been beating my head about one remaining PHPCS warning re. Dependency Injection.  I've been trying to model my solution around other contrib modules that avoid the dependency injection warning as well as one of the core modules that uses the same service I'm trying to use but avoids the warnings.  The thread where this is discusssed is:

https://www.drupal.org/project/juicebox/issues/3359786

I'm burned out from hacking at this for an hour this morning but will return to this Forum thread to see if there is a solution in it for my situation.  Anyone who has the patience to contribute insights to the thread linked above is welcome to do so meanwhile.  

There was also a long Forum thread over in Slack re. dependency injection that didn't yield any obvious solutions to my problem.  

I'm sure that using dependency injection is the preferred technical approach but it sure seems a long way around just including some code that would generate an "absolute string" version of a url.  That said, it has to be done, I know.

jaypan’s picture

I'm sure that using dependency injection is the preferred technical approach but it sure seems a long way around just including some code that would generate an "absolute string" version of a url.  That said, it has to be done, I know.

It actually doesn't "have" to be done, in that you can call the services statically. But using dependency injection accomplishes three tasks:

1) Follows a proper OOP paradigm

2) Prevents dependency loops

3) Allows for unit testing of code

Dependency injection is 'cleaner', but not necessarily required.

Contact me to contract me for D7 -> D10/11 migrations.

fkelly12054@gmail.com’s picture

Yes, the current non-dependency injection code works just fine.  It just generates a PHPCS warning that I would like to eliminate going forward.  Personally, I would also like to improve my skills so I can do it the right way.  

But coming into the code for the Juicebox module "cold" and not knowing all the ins and outs of Drupal it's hard to know:

-  which use statements to use  ... various modules that "use" the file_url_generator service seem to use different combinations of use statements

-  how to set up the construct and other statements that seem to be needed PRIOR to actually injecting the service.

jaypan’s picture

I think I outlined those things in my first post in this thread.

Contact me to contract me for D7 -> D10/11 migrations.

fkelly12054@gmail.com’s picture

Your first post was about the clearest thing I've seen on dependency injection.  It will take me a few times through and some practice to digest it.  I'm also looking at:

https://symfony.com/doc/current/service_container.html

and subsequent articles in that documentation.  

I don't fully understand why programs such as:

core/modules/image/src/Entity/ImageStyle.php

don't have dependency injection problems when the JuiceboxFormatter.php program does.  That just means I have to work though the code and documentation step by step a few more times.

If someone can point out exactly where and how ImageStyle.php makes it possible for lines 252 and 253:

/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');

to inject the file_url_generator service properly maybe I can copy that approach.  And learn from it.

Thanks

fkelly12054@gmail.com’s picture

Just in case this helps someone else.

In the contrib Juicebox module I am working on a couple of community members and the senior maintainer have more or less resolved the dependency injection problems.  The solution involved modifying the module's services.yml file.  This in turn involved modifying the arguments in the module services file.  After completing this, the maintainer wrote a juicebox_post_update_dependency_injection_cache_clear()

program to be invoked when Juicebox was updated via composer.  Fine except if you are developing and updating your code manually via, say PHPstorm and running something like Wampserver.  

After much gnashing of teeth, what I found out was that Drupal caches the a services (arguments) in a table named cache_container.   A cache clear from within Drupal nor a Drush cr doesn't clean out this table.  So your construct statement is likely to fail by referencing an obsolete service (argument).  

In the bowels of the Internet I found a solution:

from drush run drush ev  "drupal_flush_all_caches();"

then do a normal drush cr.  

normal cache clears, flushing browser cache and even a standalone drush cr are not sufficient.  

I was in a situation where the code worked fine on my hosted production server which I had updated by the "normal" composer require route but not on my local Wampserver where I had just updated the code directly in PHPstorm.  Same code:  different results because of caching.