Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The Service Container & Autowiring

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

We found out that KnpMarkdownBundle allows us to control some of the features of the markdown parser by using this knp_markdown.parser.service key:

knp_markdown:
parser:
service: markdown.parser.light

We used their documentation to learn that there were a few valid values for this service key.

But what is this? What does markdown.parser.light mean? Is it just a string that someone invented when they were designing the config for this bundle?

Not exactly: in this case, markdown.parser.light happens to be the id of a service in the container.

Hello Service Container

Let's... back up. We know that there are many useful objects - called services - floating around in Symfony. What I haven't told you yet is that, behind the scenes, Symfony puts all of these services inside something called the "service container". You can think of the service container as basically an associative array of services, where each object has a unique id.

How can we see a list of all of the services in the container and their IDs? Just run debug:autowiring, right? Actually, not quite. Find your terminal and run a new command called:

php bin/console debug:container

And wow! This is the full list of all the services inside the service container! On the left is the service's id or "key" - like filesystem and on the right is the type of object you would get if you asked for this service. The filesystem service is an instance of Symfony\Component\Filesystem\Filesystem.

You can see that this is a really long list. But the truth is that you will probably only ever use a very small number of these. Most of these are low-level service objects that help other more important services do their work.

Not all Services are Autowireable

Because of this, many of these services cannot be accessed via autowiring. What I mean is, for most of these services, there is no type-hint that you could use in a controller to fetch that service. So how would you access a service if it can't be autowired? Don't worry about that yet, we'll talk about how later.

The point is: not all services can be autowired but the most useful ones can. To get that, shorter, list of autowireable services, we run:

php bin/console debug:autowiring

This is not the full list of services, but it's usually all you'll need to use.

How Autowiring Works

While we're looking at this list, I want to talk about how autowiring works. We know that we can use the type-hint on the left to get the service with the id on the right. For example, we can use this AdapterInterface type-hint to fetch some service called cache.app.

Cool. But... how does that work? How does Symfony know that the AdapterInterface should give us that exact service? When Symfony sees an argument type-hinted with Symfony\Component\Cache\Adapter\AdapterInterface, does... it loop over every service in the container and look for one that implements that interface?

Fortunately, no. The way autowiring works is so much simpler. When Symfony sees an argument type-hinted with Symfony\Component\Cache\Adapter\AdapterInterface, to figure out which service to pass, it does one simple thing: it looks for a service in the container with this exact id. Yes, there is a service in the container whose id is literally this long interface name.

Let me show you. Once again, run:

php bin/console debug:container

Most of the service ids have snake-case names: lower case letters and periods. But if you scroll up to the top, there are also some services whose ids are class or interface names. And... yea! Here's a service whose id is: Symfony\Component\Cache\Adapter\AdapterInterface! On the right, it says that it's an alias to cache.app.

Ok, so there are two important things. First, when you type-hint an argument with Symfony\Component\Cache\Adapter\AdapterInterface, Symfony figures out which service to pass to you by looking for a service in the container with that exact id. If it finds it, it uses it. If it doesn't, you get an error. Second, some services - like this one - aren't real services: they're aliases to another service. If you ask for the AdapterInterface service, Symfony will actually give you the cache.app service. It's kind of like a symlink.

This is primarily how the autowiring system works. Bundles add services to the container and typically they use this snake-case naming scheme, which means the services can't be autowired. Then, to add autowiring support for the most important services, they register an alias from the class or interface to that service.

If this went a little over your head... don't sweat it. The most important thing is this: autowiring isn't magic. When you add a type-hint to autowire a service, Symfony simply looks for a service in the container with that id. If it finds one, life is good. If not... error!

Next, let's use our bundle-config skills to figure out how to control where Symfony stores the cache... which we know means: let's control how the cache service behaves.

Leave a comment!

4
Login or Register to join the conversation
Cheshire Avatar
Cheshire Avatar Cheshire | posted 4 months ago

I am having a problem with dependency injection in Symfony 5.4 and I have no clue what the problem is....

I am trying to inject entity manager into a constraint validator. It begins like this:

class NoPreviousFreeTrialConstraintValidator extends ConstraintValidator
{
    private $entityManager;

    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

And I have the following under services: in my services.yaml file:

App\Validator\NoPreviousFreeTrialConstraintValidator
    arguments:
        - "@doctrine.orm.entity_manager"

However, the constructor is always called with zero arguments when I try to use it. debug:autowiring says there's no autowirable classes or services for that class. What could be going wrong?

Reply

Hi

Have you tried to use EntityManagerInterface as a wireable class? also if you have autoconfigure and autowire enabled then you don't need to specify configuration for this class.

Cheers!

Reply
triemli Avatar
triemli Avatar triemli | posted 2 years ago

I would like to aks about peerfomance of autowiring (auto:true). Is it load whole the folder or only when you real use a class? It it make sense to disable autowiring on entities folder with 30-40 classes? Thanks for great tutorials!

Reply

Hey triemli

That's a good question. When you enable autowiring, what it does is to tell Symfony to inject the correct services into your class based on the type-hint or bind-ed variables. This may have a performance impact because it'll have to look up the metadata and other things BUT this happens only once, when the container is compiled, after that, this process is very fast. So, in other words all the heavy work for setting up the container gets cached and dumped into a final file which will be used for the subsequent requests.
I strongly recommend you to enable autowiring and autoconfigure, those features makes working with Symfony a delight.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "sentry/sentry-symfony": "^4.0", // 4.0.3
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.15", // v1.23.0
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}
userVoice