Dependency Injection in PHP and Drupal 8+

One of the most common issues I see when reviewing code on projects and during interviews is a lack of adherence to coding standards and norms. The Drupal community has well defined rules around these standards. There are open source tools (such as PHP Code Sniffer, or PHPCS) that automate much of the work to find AND FIX these issues.

Coding standards are a pretty big deal on teams. If you have fifteen devs all doing “whatever they want” in regards to spacing, code comments, array syntax, (and all the other things), your code gets unreadable very very quickly. Coding standards also take into consideration things like security, the maintainability (future proofing) of your code, historical context of code changes, and more!

Why then, do people not use them?

Lack of automation is a big piece. I actually discovered recently that one of my own open source projects (the simple AWS Connector module had some issues. So, I thought I would fix them and walk through the process here.

When you run PHPCS, it spits out a useful little log of the issues, telling you both what and where they are. For instance:

FILE: src/Form/AWSConnectorForm.php
------------------------------------------------------------------------------------------------------
FOUND 0 ERRORS AND 3 WARNINGS AFFECTING 2 LINES
------------------------------------------------------------------------------------------------------
 80 | WARNING | [x] There must be no blank line following an inline comment
 80 | WARNING | [ ] There must be no blank line following an inline comment
 82 | WARNING | [ ] \Drupal calls should be avoided in classes, use dependency injection instead
------------------------------------------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
------------------------------------------------------------------------------------------------------


FILE: src/Credentials/AWSCredentialProvider.php
------------------------------------------------------------------------------------------------------
FOUND 2 ERRORS AND 2 WARNINGS AFFECTING 4 LINES
------------------------------------------------------------------------------------------------------
  19 | ERROR   | Doc comment short description must be on a single line, further text should be a
     |         | separate paragraph
  33 | WARNING | Unused bound variable $filename.
  77 | ERROR   | Doc comment short description must be on a single line, further text should be a
     |         | separate paragraph
 124 | WARNING | Unused variable $result.
------------------------------------------------------------------------------------------------------


MORE, some of these issue can actually be fixed automatically by PHPCS! I won’t walk through the simple, comment length and line breaking issues. Those should be self explanatory. However, Dependency Injection is a much more complex one, and I’ll spend some time on that.

The issue in question can be found here: https://git.drupalcode.org/project/aws_connector/blob/8.x-1.0-alpha1/src/Form/AWSConnectorForm.php#L82

\Drupal::entityTypeManager()->getViewBuilder('node')->resetCache();

In software engineeringdependency injection is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service).

The Wikipedia definition helps, but isn’t super in depth. I think that Bhavya Karia does a much better job of explaining on https://www.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it-7578c84fa88f/.

Regardless, by calling \Drupal in a class, your code is dependent on \Drupal itself. By using Dependency Injection (instead of just calling the \Drupal service directly) you are much much less likely to break your code as Drupal core evolves, you’ll have more control over the method(s) that you are injecting, and you’re following coding best practices (see above).

The end goal / result of what we are trying to do is to change:

\Drupal::entityTypeManager()->getViewBuilder('node')->resetCache();

to:

$this->entityTypeManager->getViewBuilder('node')->resetCache();

This is, unfortunately, much less straight forward than just replacing that line. The $this variable in this context represents the container that is running in Symfony. TLDR, “Your application is full of useful objects…In Symfony, these useful objects are called services and each service lives inside a very special object called the service container.” The problem? In your own custom Class, it may very well be that all the services you need (e.g. the Entity Type Manager) aren’t currently loaded. Calling \Drupal allows you to do a one time use of the service, but when you Inject the dependency, it’s there for the entire class to use (because it’s properly loaded into the container).

So, how do you load a new service into the container?

You’re going to use a constructor!

I’ll be the first to admit, I never remember the “exact” syntax for this. So for me, I Google it. In this case, I look for the thing I want to inject (Entity Type Manager). Here is a useful D.O issue that has a patch that does exactly what we want!

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

  /**
   * Constructs a \Drupal\aws_connector\Form\AWSConnectorForm object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The factory for configuration objects.
   * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManager $entity_type_manager) {
    parent::__construct($config_factory);
    $this->entityTypeManager = $entity_type_manager;
  }

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


The __contruct function is a magic method in PHP that automatically runs when the class is instantiated. Since PHP does not implicitly call parent constructors, in our case we must manually do so (since we are implementing the Core Drupal ConfigFormBase class, which itself has a constructor. We also pass the Entity Type Manager service into the constructor as:

$this->entityTypeManager

If done properly, this ensures that all methods in the class will have access to the Entity Type Manager service!

Finally, we use the create method to inject the Container and our Entity Type Manager into the class.

“Usually” Dependency injection adds a dozen (or so) lines of code to your classes by the time you properly document, add use statements, etc. Yes, it is harder than just calling \Drupal, but once you get the hang of it, it’s not bad at all!'


Related Content