Chapters
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Auto-Registering All Services
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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:
Show Lines
|
// ... lines 1 - 8 |
services: | |
_defaults: | |
Show Lines
|
// ... 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}' | |
Show Lines
|
// ... 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:
Show Lines
|
// ... lines 1 - 8 |
services: | |
Show Lines
|
// ... lines 10 - 16 |
AppBundle\: | |
Show Lines
|
// ... line 18 |
# you can exclude directories or files | |
# but if a service is unused, it's removed anyway | |
exclude: '../../src/AppBundle/{Entity,Repository}' | |
Show Lines
|
// ... 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.
21 Comments
Hey Mark!
Long time ;). Happy to hear from you. So, let's walk through. When you add this line to the config, Symfony literally starts opening each of your files in the resource
path and looking for the classes it expects inside. When it opens the src/xxx/Bundle/AppBundle/Action/GeocodeXyzRelay.php
file, it expects the class name of this class to be AppBundle\Action\GeocodeXyzRelay
. Is that class name actually something else? Is it xxx\Bundle\AppBundle\Action\GeocodeXyzRelay
? If so, change the AppBundle\:</code line in the config (the first line) to be
xxx\Bundle\AppBundle:`. Symfony uses that first key to help "form" the class name that it expects in the files it finds. So, look at what it expects the class to be, and play with it a little.
If you can't figure it out, let me know - and tell me what the actual class name is in that file and we'll work it out :).
Cheers!
Hello,
thanks for the great courses online! I tried to change public: false to public:true so i can see all the new services available, but the command ./bin/console debug:container output this error 'Cannot autowire service "AppBundle\Service\MessageGenerator": argument "$messages" of method "__construct()" is type-hinted "array", you should configure its value explicitly.'
I don't understand why changing from false to true public argument leads to this error and how I should fix this.
Cheers!
Hey Vincent,
I just downloaded the course code, went to finish/ directory, changed to "public: true" in services.yml, and ran "bin/console debug:container" without any errors. Probably cache problem? Could you try to clear the cache first and try again? Are you sure the problem in "public:true"? If you still have this issue - could you write the steps to reproduce it?
Cheers!
Hello, thanks for the time spent on my question.
I did copy the composer.json of finish/ directory so everything went up to normal. I guess there must have been some misconfiguration as I update the same directory with the next course code when i finish one. Bad practice leads to unexpected behavior.
Thank you very much!
Hey Vincent,
Glad you got it working! Yeah, sometimes we may do some required changes before starting a new course. That's might be a problem if you code along with us through the track and do all the changes in one project. But most of the time Ryan says what exactly was changed so you can look at start/ directory at least and find required changes you need to do to start the course.
Cheers!
Is it me, or last 2 videos in this serie are flickering?
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!
Thanks Victor.
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
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!
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....?
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!
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.
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/screencast/symfony-3.3/controller-di-cleanup. 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/symfony-docs/issues/7740
Cheers!
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
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
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!
Thanks for the clear explanation.
Hello guys!
You re doing a perfect job!
I have just a question. Why am I having just 5 more services when i m passing the new code. It seems to me normal because they are the Controllers that i have, but there are not in your video. Has this to do with the new version that i m having?
I am sure that there is no a performance problem just with these 5 services(i mention them below)
AppBundle\Controller\Admin\GenusAdminController
AppBundle\Controller\GenusController
AppBundle\Controller\MainController
AppBundle\Controller\SecurityController
AppBundle\Controller\UserController
Thanks in advanced for your reply,
Argy
Yo Argy,
Do you use "bin/console debug:container | wc -l" command when you have these 5 more services? Well, there's no performance issue, but probably you have a bit different content of config/services.yaml, can you make sure it matches ours? And the order of those declarations are the same like ours? Or, maybe, it was just some changes in the newest version you use that now includes all the registered controllers to the output, but I'm not sure 100%. But there's nothing to worry about.
Cheers!
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.3.0-RC1", // v3.3.0-RC1
"doctrine/orm": "^2.5", // 2.7.5
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
"symfony/swiftmailer-bundle": "^2.3", // v2.6.7
"symfony/monolog-bundle": "^3.1", // v3.2.0
"symfony/polyfill-apcu": "^1.0", // v1.23.0
"sensio/distribution-bundle": "^5.0", // v5.0.25
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
"incenteev/composer-parameter-handler": "^2.0", // v2.1.4
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"knplabs/knp-markdown-bundle": "^1.4", // 1.7.1
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
"stof/doctrine-extensions-bundle": "^1.2" // v1.3.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.7
"symfony/phpunit-bridge": "^3.0", // v3.4.47
"nelmio/alice": "^2.1", // v2.3.6
"doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
}
}
Hi Ryan,
Updating my Symfony project. Had success until I got to the point where adding this
AppBundle\:
# resource: '../../src/xxx/Bundle/AppBundle/*'
# # you can exclude directories or files
# # but if a service is unused, it's removed anyway
# exclude: '../../src/xxx/Bundle/AppBundle/{Entity,Repository}'
Now I get.
FileLoaderLoadException
Expected to find class "AppBundle\Action\GeocodeXyzRelay" in file "/var/www/projecta/src/xxx/Bundle/AppBundle/Action/GeocodeXyzRelay.php" while importing services from resource "../../src/Airnav/Bundle/AppBundle/*", but it was not found! Check the namespace prefix used with the resource in /var/www/projecta/app/config/services.yml (which is being imported from "/var/www/projecta/app/config/config.yml"