Fetching Non-Autowireable Services

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 $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

There are many services in the container and only a small number of them can be autowired. That's by design: most services are pretty low-level and you will rarely need to use them.

But what if you do need to use one? How can we do that?

To see how, we're going to use our markdown channel logger as an example. It actually is autowireable if you use the LoggerInterface type-hint and name your argument $markdownLogger.

But back in MarkdownHelper, to go deeper, let's be complicated and change the argument's name to something else - like $mdLogger:

... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 15
public function __construct(MarkdownParserInterface $markdownParser, CacheInterface $cache, bool $isDebug, LoggerInterface $mdLogger)
{
... lines 18 - 20
$this->logger = $mdLogger;
}
... lines 23 - 37
}

Excellent! If you refresh the page now, it doesn't break, but if you open the profiler and go to the Logs section, you'll notice that this is using the app channel. That's the "main" logger channel. Because our argument name doesn't match any of the "special" names, it passes us the main logger.

So here's the big picture: I want to tell Symfony that the $mdLogger argument to MarkdownHelper should be passed the monolog.logger.markdown service. I don't want any fancy autowiring: I want to tell Symfony exactly which service to pass to this argument.

And, there are two ways to do this.

Passing Services via Bind

You might guess the first: it's with bind, which works just as well to pass services as it does to pass scalar config like parameters:

... lines 1 - 8
services:
# default configuration for services in *this* file
_defaults:
... lines 12 - 13
bind:
bool $isDebug: '%kernel.debug%'
... lines 16 - 31

First, go copy the full class name for LoggerInterface, paste that under bind and add $mdLogger to match our name. But, what value do we set this to?

If you look back at debug:autowiring, the id of the service we want to use is monolog.logger.markdown. Copy that and paste it onto our bind.

But... wait. If we stopped now, Symfony would literally pass us the string monolog.logger.markdown. That's... not helpful: we want it to pass us the service that has this id. To communicate that, prefix the service id with @:

... lines 1 - 8
services:
# default configuration for services in *this* file
_defaults:
... lines 12 - 13
bind:
... line 15
Psr\Log\LoggerInterface $mdLogger: '@monolog.logger.markdown'
... lines 17 - 32

That's a super-special syntax to tell Symfony that we're referring to a service.

Let's try this thing! Refresh, then open the Logs section of the profiler. Yes! We're back to logging through the markdown channel!

The bind key is your Swiss Army knife for configuring any argument that can't be autowired.

Adding Autowiring Aliases

But there's one other way - besides bind - that we can accomplish this. I'm mentioning it... almost more because it will help you understand how the system works: it's no better or worse than bind.

Copy the LoggerInterface bind line, delete it, move to the bottom of the file, go in four spaces so that we're directly under services and paste:

... lines 1 - 8
services:
... lines 10 - 31
Psr\Log\LoggerInterface $mdLogger: '@monolog.logger.markdown'

That will work too. But... this probably deserves some explanation.

This syntax creates a service "alias": it adds a service to the container whose id is Psr\Log\LoggerInterface $mdLogger. I know, that's a strange id, but it's totally legal. If anyone ever asks for this service, they will actually receive the monolog.logger.markdown service.

Why does that help us? I told you earlier that when autowiring sees an argument type-hinted with Psr\Log\LoggerInterface, it looks in the container for a service with that exact id. And, well... that's not entirely true. It does do that, but only after it first looks for a service whose id is the type-hint + the argument name. So yes, it looks for a service whose id is Psr\Log\LoggerInterface $mdLogger. And guess what? We just created a service with that id.

To prove I'm not shouting random information, move over, refresh, and open up the profiler. Yes! It's still using the markdown channel. The super cool thing is that, back at your terminal, run debug:autowiring log again:

php bin/console debug:autowiring log

Check it out! Our $mdLogger shows up in the list! By creating that alias, we are doing the exact same thing that MonologBundle does internally to set up the other named autowiring entries. These are all service aliases: there is a service with the id of Psr\Log\LoggerInterface $markdownLogger and it's an alias to the monolog.logger.markdown service.

Phew! I promise team, that's as deep & dark as you'll probably ever need to get with all this service autowiring business. But as a bonus, the autowiring alias stuff will be great small talk for your next Zoom party. Your virtual friends are going to love it. I know I would.

Now that we are service experts, let's look back at our controller. Because, it's a service too!

Leave a comment!

  • 2020-07-13 weaverryan

    Hey Steven!

    > can you confirm me that with the first method (_defaults, bind), it's not really a service alias (unlike the second method) but rather a "hack" ?

    Yes! You understand correctly. I wouldn't call it a hack, but you are 100% correct that it is *not* an alias. The logic internally looks like this:

    A) Symfony checks to see if an argument has been specifically configured for an argument (e.g. either via "bind" or "arguments"). And, with bind, you can be very specific that you only want to configure an argument if it has a certain name and type.

    B) If the argument has still not been configured, then it tries to autowire it, which uses the alias system.

    So, if you specified both a "bind" and an autowireable alias, the bind would win (situation A) because, to Symfony, it looks like you are specifically configuring the argument.

    I hope that helps - excellent question!

    Cheers!

  • 2020-07-11 Steven

    Hi,

    can you confirm me that with the first method (_defaults, bind), it's not really a service alias (unlike the second method) but rather a "hack" ?
    Because with the bin/console debug:autowiring there is no alias listed.

    Thanks and cheers!