Author image
Developer

Dependency Injection - how to access services in controllers

If you are trying to get to grips with Dependency Injection in Drupal 8 then here is a walk-through of how I applied it in one of my Drupal 8 test projects.

I have a project I have been using to investigate Drupal 8 since alpha10 which has been invaluable in my learning process. But as a result, some of my code is over 2 years old and written when I barely had a grasp of Drupal 8 concepts.

In the past week a very old @todo jumped out to me:

// @todo Find a way to get a service within a controller.

I realised that the solution to the problem is dependency injection. These are the steps I took in making the change to my controller.

Starting Point

Don't judge me, but here is how I had been accessing services within my controller:

$foo_bar = \Drupal::service('foo.bar');
$data = $foo_bar->get_data();

From a procedural point of view this was the sensible way to do it, but this was before I grasped dependency injection. Rather than accessing the static service it should be a part of the controller and accessed like this:

$data = $this->fooBar->getData();

Addtional Functions

The service is injected into the controller within the constructor. Until now I had no need of a constructor so added the following:

public function __construct(FooBar $foo_bar) {
  $this->fooBar = $foo_bar;
}

This needs to be called (with the correct parameter(s)) from the create() function, so I also added:

public static function create(ContainerInterface $container) {
  return new static(
    $container->get('foo.bar')
  );
}

First Gotcha

Don't forget to add the required use statements for any classes you reference, e.g.:

use Drupal\MyCustomModule\FooBar;
use Symfony\Component\DependencyInjection\ContainerInterface;

I highly recommend using PhpStorm as your IDE; it can add a use statement simply by pressing Alt + Enter.

Second Gotcha

Now I must admit, this one caught me out (and not just on my first attempt); for the controller to be called correctly it must either:

  • implement ContainerInjectionInterface
  • extend ControllerBase (because that class implements ContainerInjectionInterface)

e.g.

class MyCustomController implements ContainerInjectionInterface {

There are pros and cons to both approaches; extending the ControllerBase provides access to some handy functions including currentUser(), config() and state() amongst others. The downside is that these additions add complexity that make it harder to unit test. The ControllerBase page is a good point of reference when making this decision.

That's all folks!

And there you have it; I can now call the FooBar service as a property of MyCustomController rather than statically.