Buy

Auto-Registering All Services

Go back to the Symfony Standard Edition's services.yml file for Symfony 3.3. These two sections are the most fundamental change to the Symfony 3.3 service configuration. Copy the first section, then find our services.yml and, after _defaults, paste:

57 lines app/config/services.yml
... lines 1 - 8
services:
_defaults:
... lines 11 - 14
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Repository}'
... lines 22 - 57

Woh.

This auto-registers each class in src/AppBundle as a service. There are a few important things to know. First, the id for each service is the full class name, just like we've been doing with our services. And second, thanks to _defaults, these new services are autowired and autoconfigured. And that means... in a lot of cases, you won't need to manually register your services at all anymore. Nope, as soon as you put a class in src/AppBundle, Symfony will autowire and autoconfigure it. That means you can start using it with zero config. And as we'll see soon, if the service can't be autowired, you'll get a clear error with details on what to do.

Auto-registering ALL Classes as Services? Are you Insane?

Now... I bet I know what you're thinking:

Ryan, are you completely insane!? You can't auto-register everything in src/AppBundle as a service!? Some classes - like DataFixtures! - are simply not meant to be services! You've gone mad sir!

Ok, this seems like a fair argument... but actually, it's not. What if I told you that the total number of services in the container before and after adding this section is the same. Yep! To prove it, comment out this auto-registration code. Then, go to your terminal, open a new tab, and run:

php bin/console debug:container | wc -l

This will basically count the number of services returned. Ok - 262! Uncomment that code. Now, I'm registering all classes from src/AppBundle as services. Try the command again:

php bin/console debug:container | wc -l

It's the same! How is that possible?

Remember, all of these new services are private. And that's very important. It means that none of the services can be referenced via $container->get(). And Symfony's container is so incredible that - right before it dumps the cached container, it finds all private services that have not been referenced, and removes them from the container. This means that even though it looks like we're registering every class inside of src/AppBundle as a service, that's actually not true!

A better way to think of it is this: each class in src/AppBundle is available to be used as a service. This means we can reference it as argument in services.yml or type-hint its class in a constructor so that it's autowired. But if you do not reference one of these classes, that service is automatically removed.

Excluding some Paths

You've probably also noticed this exclude key:

57 lines app/config/services.yml
... lines 1 - 8
services:
... lines 10 - 16
AppBundle\:
... line 18
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Repository}'
... lines 22 - 57

Actually, for the reasons we just discussed... this isn't that important. You can exclude certain files or directories if you want. But most of the time, that's not needed: if you don't reference a class, it's removed from the container for you.

However, if you do have entire directories that should not be auto-registered, adding it here is nice. It'll give you a slight performance boost in the dev environment because Symfony won't need to watch those files for changes. And for a subtle technical reason, the Entity directory must be excluded. We also excluded the Respository directory. You actually can register these as services... but you need to configure them manually to use a factory. Basically, auto-registering and autowiring doesn't work, so we might as well ignore them.

Overriding Auto-Registered Services

Phew! So the idea is that we start by auto-registering each class as a service with these 3 lines. Then, if you do need to add some more configuration - like a tag, or an argument that can't be autowired, you can do that! Just override the auto-registered service below: use the class name as the key, then do whatever you need. Symfony automates as much configuration as possible so that you only need to fill in the rest.

Leave a comment!

  • 2017-07-31 Marc Sanders

    Thanks for the clear explanation.

  • 2017-07-31 weaverryan

    Yo Marc Sanders!

    Yep, there is some extra overhead in the dev (or test) environment with the new settings (even when the container is not being rebuilt on that request). It's exactly as you said - it has extra checks to see if files have changed or if constructor arguments have changed. Hopefully it's not too noticeable :). The container rebuild requests will also be a bit slower, but those should only happen when they need to. What I mean is, if you change some code in the middle of a class, it should not trigger a container rebuild. But if you change the constructor, it should re-trigger a rebuild. It's not 100% perfect (i.e. sometimes it will rebuild the container when you make a change that should not have caused a rebuild), but I hope we'll work out those edge cases over time.

    Oh, also, this is part of the reason for the "exclude" tag. If you know you have some directories that are definitely not services, you can exclude those in services.yml. This will give you a minor (depending on how many you exclude) performance boost in the dev environment.

    > I've noticed that even when a service is not public, it can sometimes be retrieved by referencing $container->get('AppBundle\ServiceName').

    Ah, very good eye! Indeed, sometimes you can fetch a private service in this way - it relates to whether or not that service is used by multiple services (when it is, the container is built in such a way that it's accessible). But, this is not intended. Iirc, starting in Symfony 3.4 (could be 3.3, but I don't think it is), you will receive a deprecation warning when you try this, and in Symfony 4 it won't work (private services will never be accessible in this way).

    Thanks for the great questions!

  • 2017-07-31 Marc Sanders

    And the final 2 questions for now.

    1: I've noticed that even when a service is not public, it can sometimes be retrieved by referencing $container->get('AppBundle\ServiceName').

    Is this supposed to happen. I would expect an error in this case...

    2: When would you choose to make a service public and when private? In my project I have a couple of services that are used all over the place. In order to prevent replacing all these references I've opted to make these public and the rest private.

    Cheers

  • 2017-07-31 Marc Sanders

    Thanks a lot. I'll refactor the code then.

    Maybe one last question? When I change a class and reload a page, total load time is about 3 seconds (I am not worried about the first load..). Reloading a page after the first load is about 100 ms slower in the test environment with the new configurations settings then before.

    I think this is probably caused by the extra work that has to be done checking if changes were made to any of the classes that are autoconfigured and autowired. What do you think?

    Best wishes

  • 2017-07-31 weaverryan

    Hey Marc!

    Ah, you've found the critical part :). 180 controllers! Wow, that's amazing! :)

    So, these abstract and instanceof stuff are "abstract" services (a super uncommon, low-level type of service) that are created by the container to help with the autoconfigure stuff. For controllers, if autoconfigure is true, then we automatically add the controller.service_arguments tag for you, if your controller extends either Controller or AbstractController. This tag is also applied manually in services.yml just in case. These abstract services *are* actually removed from the container - so they're not adding extra bloat. Why do they show up here? I actually found this same issue and checked into it briefly. If I remember correctly, it's either an easy fix (we just need to hide abstract services from this list) or a subtle ordering issue internally of when services are removed versus when this command is run. But in either case, these should not be in your final container. If you have any doubts, you can open the dumped container - var/cache/dev/appDevDebugProjectContainer.php - and search inside to verify they are not there.

    Also, if you want, you can set autoconfigure: false under your controller import - and half... or all of these will go away. As I mentioned, these services help support the autoconfigure / instanceof functionality in Symfony 3.3... and it's not actually needed for controllers, because the tag is specified manually right in services.yml (we do both to be be doubly sure that you have the tag). Btw, that tag is responsible for your ability to autowire services as arguments to your controller.

    > Sometimes a controller needs to have a lot of services injected that are not all used for every request. Would it be better to make those services public and retrieve them from the container when the are necessary or would you recommend keeping them private and adding them all to the controller action or would you use a service locator?

    GREAT question. As I can tell your already know, if you DI all those services through __construct, you're paying a penalty to instantiate some that you might not use. That's part of the reason why we created the argument autowiring for controllers: https://knpuniversity.com/s.... If you use this method, not only is it more convenient... but services will only be instantiated that are needed for the *one* controller that was called. If you have some situation where even *one* controller method has a lot of conditional code so that you're injecting more than you need, I would (A) first refactor that code to a service and then (B) if it really *is* a performance problem, use a service locator inside that service. The new service locator stuff in 3.3 is really cool - though I don't know if we have service subscribers documented yet! https://github.com/symfony/...

    Cheers!

  • 2017-07-31 Marc Sanders

    Hi Ryan,

    Thanks for your reply. Public is on false. Only the controller are made public. I guess I have around 180 controllers, services and listeners in total. I ran debug:container and each file under controllers gets 3 entries in this list,e.g.

    - abstract.instanceof.Master\AdminBundle\Controller\Master\User\UserEditController
    - instanceof.Symfony\Bundle\FrameworkBundle\Controller\Controller.0.Master\AdminBundle\Controller\Master\User\UserEditController
    - instanceof.Symfony\Bundle\FrameworkBundle\Controller\Controller.0.Master\AdminBundle\Controller\User\UserEdit\UserEditController

    So this would amount to around 540 entries when debug:container is run.

    When I run debug:container --types the list returns 385, about the same as the number of services before the migration.

    One other question. Sometimes a controller needs to have a lot of services injected that are not all used for every request. Would it be better to make those services public and retrieve them from the container when the are necessary or would you recommend keeping them private and adding them all to the controller action or would you use a service locator?

    Thanks.

  • 2017-07-31 weaverryan

    Hiya Marc!

    Apologies for my slow reply! So, great question. Let me answer in reverse :). This would not have a major impact on performance, though it would have a minor impact on performance. But first, more importantly, this should not be happening. So, we need to find out *what* all these new services are? First, make sure you have the public: false under _defaults. I'm guessing you do, but that is the first thing that could cause a problem (as all of the classes would be registered as public services and then not removed if they're unused). Also, make sure you've gone through the process of renaming all of your existing service id's to their class names (and using the legacy_aliases trick if needed).

    If everything seems ok to you (i.e. you're not missing the public: false), if you could get a big list of the debug:container dump before and after, that might help. And how big is your project? Even if you forgot to set public: false, it looks like you're getting 450 *new* services... which would mean that your src directory has that many classes. Is your project huge, or not too big? It's interesting!

    Cheers!

  • 2017-07-27 Marc Sanders

    After changing my config.yml to the new format and making all the controllers public, I have a total of 835 services. When the controllers are private I have a total of 382 services. Will this have a major impact on performance? Or should I reverse all the changes to the old situation when I had 382 public services....?

  • 2017-06-12 Victor Bocharsky

    Hey Ali,

    Do you register any services which implement "Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface" interface in your application? I suppose it's coming from Symfony Core, and it means that there's nothing to do on your side. It will be fixed by Symfony in future releases, probably only in 4.0. So you can just ignore those deprecated messages from the core and focus on ones that related to *your* application, i.e. to your custom code.

    Cheers!

  • 2017-06-10 Ali Niaki

    Hey guys, thank you for being updated!

    I'm using symfony 3.3 but I'm getting this:

    Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won't be supported in version 4.0. You should rename (or alias) the "session.attribute_bag" service to "Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface" instead.

    but I can't register it in service.yml becuase it's an Iterface. shouldn't symfony auto register that? what should I do to fix this?
    :D

  • 2017-06-08 Zorpen

    Thanks Victor.

  • 2017-06-08 Victor Bocharsky

    Hey Zorpen,

    Try to reload the page with Cmd + R / Ctrl + R, wait until the page is completely loaded, and play the video again - it should fix the problem. I also found this problem before by myself and these simple steps helped me. Not sure about where the problem is come from though, it's difficult to reproduce.

    Cheers!

  • 2017-06-08 Zorpen

    Is it me, or last 2 videos in this serie are flickering?