How to Get a Service in the Controller

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

There's a dino_roar.roar_generator service in the container and gosh darnit, I want to use this in my controller!

The ControllerBase Class

First, notice that RoarController is not extending anything. That's cool: your controller does not need to extend anything: Drupal doesn't care. That being said, most of the time a controller will extend a class called ControllerBase. Add it and hit tab so the use statement is added above the class:

... lines 1 - 4
use Drupal\Core\Controller\ControllerBase;
... lines 6 - 9
class RoarController extends ControllerBase
... lines 12 - 34

This has a lot of cool shortcut methods - we'll look at some soon. But more importantly, it gives us a new super-power: the ability to get services out of the container.

Override create()


An alternative to the following method is to register your controller as a service and refer to it in your routing with a your_service_name:methodName syntax (e.g. dino.roar_controller:roar). This allows you to pass other services into your controller without needing to add the create function. For more info, see Structure of Routes.

I'll use the shortcut command+n, select "Override" from the menu and override the create function that lives in the base class:

... lines 1 - 6
use Symfony\Component\DependencyInjection\ContainerInterface;
... lines 8 - 9
class RoarController extends ControllerBase
... lines 12 - 21
public static function create(ContainerInterface $container)
... lines 24 - 26
... lines 28 - 34

You don't need to use PhpStorm to override this, it's just fast and fancy. It also added the use statement for the ContainerInterface. When your controller needs to access services from the container, this is step 1.

Before we had this, Drupal instantiated our controller automatically. But now, it will call this function and expect us to create a new RoarController and return it. And hey, it passes us the $container!!! There it is, finally! The container is the most important object in Drupal... and guess what? It has only one important method on it: get(). I bet you can guess what it does.

Create a $roarGenerator variable, set it to $container->get(''); and pass it the name of the service: dino_roar.roar_generator:

... lines 1 - 21
public static function create(ContainerInterface $container)
$roarGenerator = $container->get('dino_roar.roar_generator');
... lines 25 - 26
... lines 28 - 36

Behind the scenes, Drupal will instantiate that object and give it to us. To create the RoarController, return new static(); and pass it $roarGenerator:

... lines 1 - 21
public static function create(ContainerInterface $container)
$roarGenerator = $container->get('dino_roar.roar_generator');
return new static($roarGenerator);
... lines 28 - 36

This may look weird, but stay with me. The new static part says:

Create a new instance of RoarController and return it, please".

Again, manners are good for performance in D8.

Controller __construct() Method

Next, create a constructor: public function __construct().When we instantiate the controller, we're choosing to pass it $roarGenerator. So add that as an argument:

... lines 1 - 9
class RoarController extends ControllerBase
... lines 12 - 16
public function __construct(RoarGenerator $roarGenerator)
... line 19
... lines 21 - 34

I'll even type-hint it with RoarGenerator to be super cool. Type-hinting is optional, but it makes us best friends.

Finally, create a private $roarGenerator property and set it with $this->roarGenerator = $roarGenerator;:

... lines 1 - 9
class RoarController extends ControllerBase
... lines 12 - 14
private $roarGenerator;
public function __construct(RoarGenerator $roarGenerator)
$this->roarGenerator = $roarGenerator;
... lines 21 - 34

Ok, this was a big step. As soon as we added the create() function, it was now our job to create a new RoarController. And of course, we can pass it whatever it needs to its constructor - like objects or configuration. That's really handy since we have access to the $container and can fetch out any service and pass it to the new controller object.

In the __construct function, we don't use the RoarGenerator yet: we just set it on a property. That saves it for use later. Then, 5, 10, 20 or 100 miliseconds later when Drupal finally calls the roar() function, we know that the roarGenerator property holds a RoarGenerator object.

Delete the new RoarGenerator line and instead use $this->roarGenerator directly:

... lines 1 - 28
public function roar($count)
$roar = $this->roarGenerator->getRoar($count);
return new Response($roar);
... lines 35 - 36

Woh. Moment of truth: go back to the browser, change the URL and hit enter to reload the page. OMG! It still works!

It's Dependency Injection!

It is ok if this was confusing for you. This - by the way - is called dependency injection. Buzzword! Actually, it's kind of a hard application of dependency injection. I'll show you a simpler and more common example in a second. But once you wrap your head around this pattern, you will be unstoppable.

Why did I put my Service in the Container?

Why did we go to all this trouble? After all, this only saved us one line in the controller: the new RoarGenerator() line.

Two reasons, big reasons. First, I keep telling you the container is like an array of all the useful objects in the system. Ok, that's kind of a lie. It's more like an array of potential objects. The container doesn't instantiate a service until and unless someone asks for it. So, until we actually hit the line that asks for the dino_roar.roar_generator service, your app doesn't use the memory or CPUs needed to create that.

For something big like Drupal, it means you can have a ton of services without slowing down your app. If you don't use a service, it's not created.

And if ten places in your code ask for the dino_roar.roar_generator service, it gives each of them the same one object. That's awesome: you might need a RoarGenerator in 50 places but you don't want to waste memory creating 50 objects. The container takes care of that for us: it only creates one object.

The second big benefit of registering a service in the container isn't obvious yet, but I'll show that next. It deals with constructor arguments.

Fetch another Service: a Logger

Now that we have this pattern with the create function and __construct, we're dangerous! We can grab any service from the container!

Go to the terminal and run container:debug and grep for log:

drupal container:debug | grep log

Interesting: there's a service called logger.factory that can be used to, um ya know, log stuff. Let's see if we can log the ROOOOOAR message from the controller.

In RoarController add $loggerFactory = $container->get('logger.factory'); and pass that as the second constructor argument when creating RoarController:

... lines 1 - 27
public static function create(ContainerInterface $container)
$roarGenerator = $container->get('dino_roar.roar_generator');
$loggerFactory = $container->get('logger.factory');
return new static($roarGenerator, $loggerFactory);
... lines 35 - 45

Type-Hinting Core Services

The container:debug command tells us that this is an instance of LoggerChannelFactory. Use that as the type-hint. In the autocomplete, it suggests LoggerChannelFactory and LoggerChannelFactoryInterface. That's pretty common. Often, a class will implement an interface with a similar name. Interfaces are a bit more hipster, and in practice, you can type-hint the original class name or the interface if you want to look super cool in front of co-workers.

Call the argument $loggerFactory. I'll use a PhpStorm shortcut called initialize fields to add that property and set it:

... lines 1 - 5
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
... lines 7 - 10
class RoarController extends ControllerBase
... lines 13 - 19
private $loggerFactory;
public function __construct(RoarGenerator $roarGenerator, LoggerChannelFactoryInterface $loggerFactory)
$this->roarGenerator = $roarGenerator;
$this->loggerFactory = $loggerFactory;
... lines 27 - 43

If you want to dive into PhpStorm shortcuts, you should: we have a full tutorial on it.

Now in the roar() function, use that property! Add $this->loggerFactory->get(''): this returns one specific channel - there's one called default. Finish with ->debug() and pass it $roar:

... lines 1 - 35
public function roar($count)
$roar = $this->roarGenerator->getRoar($count);
... lines 41 - 42
... lines 44 - 45

Congrats: we're now using our first service from the container.

Refresh to try it! To check the logs, head to a page that has the main menu, click "Reports", then go into "Recent Log Messages." There it is!

Not only did we add a service to the container, but we also used an existing one in the controller. Considering how many services exist, that makes you very dangerous.

Oh, and if this seemed like a lot of work to you, you're in luck! The Drupal Console has many code generation commands to help you build routes, controllers, services and more.

Leave a comment!

  • 2020-04-22 seancamden

    Thanks Vladimir! That was, indeed, the thing I was missing.

  • 2020-04-22 Vladimir Sadicov

    Hey seancamden

    Looks like you miss use statement for LoggerChannelFactoryInterface. I updated code block on this page, so check this out, there is correct use statement ate the top.

    TIP. You can always check complete file code with show all lines button on the code block header, and of course write a comment here if we are missing something ;)


  • 2020-04-21 seancamden

    Adding the logger gives me this error:

    TypeError: Argument 2 passed to Drupal\dino_roar\Controller\RoarController::__construct() must be an instance of Drupal\dino_roar\Controller\LoggerChannelFactoryInterface, instance of Drupal\Core\Logger\LoggerChannelFactory given, called in /home/bscdev/new_html/web/modules/custom/dino_roar/src/Controller/RoarController.php on line 35 in Drupal\dino_roar\Controller\RoarController->__construct() (line 16 of modules/custom/dino_roar/src/Controller/RoarController.php).

    It seems to want Drupal\dino_roar\Controller\LoggerChannelFactoryInterface

    But I seem to be giving it Drupal\Core\Logger\LoggerChannelFactory and I'm not sure why.

  • 2020-01-24 T. Wagner

    Great explanation. Thanks! and nice "fake code" too.

  • 2020-01-23 weaverryan

    Hey T. Wagner!

    Great question :). This is a total "Drupalism". Basically, when the controller system was added to Drupal, here was the thought process:

    A) By default, Drupal will instantiate the controller class automatically. But, when it does this, no constructor arguments will be passed to the constructor... because how would Drupal know what to pass you?

    B) Having the controller class instantiated automatically with no constructor args is fine for many people. But to *allow* people to pass constructor args who want to, Drupal needed to "invent" some way of doing this. The way they decided to do it was: (A) check if the controller class implements a ContainerInjectionInterface (which ControllerBase does) - this interface requires that create() method. And (B) if the controller class *does* implement it, call that method to instantiate the controller object instead of instantiating it manually.

    A better explanation might be in code :). Here is some "fake code" that is more-or-less what Drupal does in its core:

    use Drupal\Core\DependencyInjection\ContainerInjectionInterface;

    // just some fake code to get the controller class
    $controllerClass = $route->getController();

    if (is_subclass_of($controllerClass, ContainerInjectionInterface)) {
    // call the create() method and allow IT to instantiate the controller
    $controllerObject = $controllerClass::create($container);
    } else {
    // otherwise, just create it with no arguments
    $controllerObject = new $controllerClass();

    I hope that helps. This is total, dark, hidden logic, which is why I love to explain it. You're 100% correct to wonder "who calls this method?"


  • 2020-01-21 T. Wagner

    Should mention how the Create method is invoked??? It isn't a magic method, so... who called it?

  • 2017-02-17 weaverryan

    Note added! Thank you!

  • 2017-02-16 Robert Slootjes

    You can find it at under "_controller".

    Personally I even think the article should be rewritten to show this as the primary method and then show the create() as an alternative method but that's up to you of course :)

  • 2017-02-16 weaverryan

    Hey Robert!

    I agree! I think we should at least add a note about this. Is your change to the Drupal 8 docs already live? It would be a great place to link to in that note.


  • 2017-02-15 Robert Slootjes

    I've also just added it to the Drupal 8 docs on routing so it should be easier to find from now on. I would suggest also mentioning it in the code examples here as the create() method seems really crazy to me as I don't want any binding with my framework but the create() does require the container itself.

  • 2017-02-15 Victor Bocharsky

    Hey Robert,

    Yeah, `controller_service_name:action` notation is a little-known fact. Thanks for sharing it!


  • 2017-02-15 Robert Slootjes

    It's good to know that you can also use the "controller_service_name:action" in your routing.yml where "controller_service_name" is a service defined in your service.yml. This way you don't have to implement the create() method anymore.

  • 2017-01-10 weaverryan

    Yo Matt!

    I don't think I talk about it here enough (or at all), but my opinion is almost always that you should feel ok avoiding create() (or create-like functions, there are a few other times when Drupal has a thing like this) and use \Drupal::getContainer(). BUT, the key is: *only* do this when you're in these classes that force this pattern on you - e.g. controllers. AND, of course, ideally, put as much logic into services, so that your controllers stay very "skinny". For me, places like your controller *should* have access to the container - I was a bit surprised when Drupal decided to make developers jump through this hoop.

    So, tl;dr YES - but only use \Drupal when you're in a Controller (or the other few spots with this pattern).


  • 2017-01-09 Matt Holbrook-Bull

    hi Ryan (again.. not stalking honest).

    is there a reason why we shouldnt use?


    seems a lot simpler than using create?

  • 2016-06-18 weaverryan

    Hey Kieran!

    The static create() thing is tricky, and it's a total Drupal-ism - it's how they chose to give you access to the Container in controllers. I actually find it fairly difficult to explain - it's a very odd - and edge-case version of dependency injection :). But, that makes it all the more interesting!

    But, first, you probably don't exactly see $container->get('...') in the roar() method in other tutorials, because - in Drupal 8 - you don't have access to the $container object in a random controller function. This is a "problem" in general with OO code - unless you "cheat" and use global/static variables, you simply don't have access to an object from within another object unless it is somehow passed to you as an argument (either as an argument to your controller or an argument to the __construct() function of your object).

    The most important thing for testable and predictable code (because using global variables are very not predictable) is that you don't use global/static variables. If we take that as a truth ("I will not use static/globals"), everything that follows is a collection of strategies to get access to the objects/configuration that we need. *Usually*, we choose to pass our "dependencies" through the __construct() method. But technically speaking, that is just one strategy.

    Most of the time in Symfony/Drupal, *we* are in control of *how* are objects are instantiated. For example, if you have a RoarGenerator class (like the one in this tutorial), we can control what arguments are passed to its __construct() method via the YAML file. This allows us to not use globals/static, but still get access to whatever we need (yay!).

    But controllers are a special case for DI, because it is the *one* place in Drupal or Symfony where you do *not* control *how* your object is instantiated (the reasons for why that is true are not too important). Somewhere deep and dark, Drupal/Symfony creates your Controller object for you, and it passes *no* arguments to __construct(). Oh no! This puts us in a bad place: how can we get access to outside objects if we cannot control the arguments to our __construct() method? Basically, we can't! So, Drupal added a second option. They said, if your Controller class has a create() method, then instead of creating your Controller object with new __construct args (i.e. new RoarController()) - it will call the create() method, pass you the Container, and allow *you* to choose your __construct arguments. In short, this is a one-off strategy for allowing you to control how your Controller is instantiated. All other objects are controlled in this way via the YAML file.

    Phew! Now, I don't know if that helped explain things or confused further :). And thanks for the note about the learning design - your point is well-understood. I like to downplay very technical terms to help shake the fear that sometimes becomes attached to ideas that are fundamentally *not* complex. However, I see your point :).


  • 2016-06-17 Kieran Mathieson

    Why make a static create() method? In other tutorials, lines like...

    $roarGenerator = $container->get('dino_roar.roar_generator');

    ... would be in the roar() method of RoarController.

    I'm guessing it has to do with testing? create() centralizes instantiation of services used in all the methods of RoarController (assuming programmers follow the standard of using create()). Makes it easier to identify the stub services (stub isn't the right name, forget the correct name) needed for testing. Just guessing, since I don't know what I'm talking about.

    BTW, from a learning design PoV, I like the fact that you address emotional issues in this series, such as the noob fear induced by terms like dependency injection container. Can be tricky, though. It's easy for students to infer that anyone using the term dependency injection container is intentionally begin snooty. The inference will often be incorrect; that's just the way devs talk to each other. The inference can complicate the social dynamics of a dev team.


  • 2015-12-15 weaverryan

    I saw an issue that they were going to add this, I'm glad they did. It was actually added to 8.1, not 8.0 (it looks like they added it to 8.0 for a moment, then realized that adding a new feature breaks semver - great decision).

    Anyways, for others reading this, yes! In 8.1, you'll be able to use $this->getLogger('main') to get the "main" channel logger from a controller that extends ControllerBase.

    Thanks for the note!