Dependency Injection - how to access services in controllers
Jo Fitzgerald
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();
Additional 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 implementsContainerInjectionInterface
)
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.