In Symfony applications, services and aliases are public by default. This means
that when you have the container at hand, you can easily get services, like in
controllers that extend the Symfony base controller
($this->get('app.my_service')
) or when injecting the service_container
into your classes ($container->get('app.my_service')
).
As easy as this seams to be, using the container directly is not considered a good practice because it hides the dependencies of your classes, making them coupled to external configuration, thus harder to test, harder to review, etc.
Starting with Symfony 3.3, we added new dependency injection primitives that had the potential to completely replace the cases where injecting the service container was required (e.g. to achieve laziness, or break some circular references).
Slowly but steadily, we're now ready to move away from having any need to
inject the container in user-land classes. That's why in Symfony 3.4 services
and aliases will be private by default. This means that even if you manage to
have the container, you will not be able to get()
them anymore by default.
Instead, you should use regular dependency injection.
If you use autowiring in your application, you won't have to make many changes because you are already injecting the services instead of getting them via their public ID. That works for regular classes, but also for controllers and commands, since they are services themselves by default now.
Alternatively, you can update your application to mark services as public. You'll then need to be explicit about it. If you use YAML to configure services:
1 2 3 4 5 6 7
services:
# this makes public all the services defined in this file
_defaults: { public: true }
# you can also make public individual services
App\Manager\UserManager:
public: true
If you use XML to configure services:
1 2 3 4 5 6 7
<services>
<!-- this makes public all the services defined in this file -->
<defaults public="true" />
<!-- you can also make public individual services -->
<service id="App\Manager\UserManager" public="true"></service>
</services>
If you use PHP, add the ->setPublic(true)
call to the appropriate service
definition.
This change will also impact the third-party bundles used in your projects. If your favorite bundle uses the container internally, please send them pull requests to fix that: starting with 3.4, there is almost never a reason to play with the container anymore.
In Symfony core we've already done that and we made all services and aliases private, except a few selected ones, that are required at bootstrap time. In fact, bootstrapping is the last and only legitimate use case for using the container directly.
So, should we deprecate the possibility to inject the service_container
entirely alongside with ContainerAware*
? That's a possibility that the
community might consider when preparing Symfony 5.
Shouldn't this be considered as BC break? Please correct me if I am wrong, but wouldn't a lot of apps using 3rd party bundles will break after updating symfony to 3.4 ?
@Mantas no. We have a BC layer in place preventing the services marked as private implicitly from being removed or inlined, so that they are still there in the final container. And access to non-inlined private services through
get
is deprecated since 3.2, but it still works in 3.4.External bundles would break on 4.0 if they don't take this change into account (and they actually rely on services being public)
@Christophe Coevoet that's a very smart BC layer. Anyway, making services private per default is very good, no business code should never rely on the container as a dependency.
To me the feeling is that we all know whats wrong but we don't talk about it, we try to find all kind of workaround solutions in order to fix the "inheritance" from the "RAILS" design - if you built on symfony 1 you know what I'm talking about.
Any good(experienced) developer knows that this is not application architecture, this is a framework architecture, nevertheless 80% of all web applications will be built like this... starting to see the pattern? The reality is that we are encouraged and all tutorials (which the new devs will be reading) points to this type of systems where the "application" is not your application but a "framework coupled application"!
We can "hide" services, we can destroy the AppBundle, we can add another level of complexity in using the symfony "service container", we can do whatever you want to make it harder for people to implement "framework coupled application", but in the end if nobody in this community takes responsibility to promote a NON FRAMEWORK coupled application architecture first then we can "alter" symfony in whatever ways you want the result will be the same. Hiding services won't fix the real problem. What will fix the real problem is to let symfony do what it does better: deliver our application to the web or whatever delivery channel one might require and TOTALLY decouple our APPLICATION LOGIC from the framework... Otherwise we are being left chasing and wondering what parts of our "framework coupled application" needs to be refactored because there's a "new better way" of registering/using services... or whatever "new better way" will be created to do "X" better in symfony 4.
All that we need is to use symfony to do its job: deliver our application to the web! I shouldn't care HOW and WHERE I need to register my services, this is a DETAIL!
@Javier Eguiluz this is despicable! You are basically ignoring EVERYTHING and EVERYBODY and expect that we "Contact bundle authors and open issues in their repositories" ? Are you kidding me? REALLY ARE YOU KIDDING ME ? Have a look at https://github.com/Sylius/Sylius/blob/master/composer.json and then lets talk about contacting bundle authors.
But you don't care, we DEPEND ON YOU, you don't depend on us, we are just here to catch up with "latest visionary BC's" and contact bundle authors and open issues in their repositories" because we don't have anything else better to do...
For all the rest of us: https://www.youtube.com/watch?v=Nsjsiz2A9mg lets learn how to build applications first and then learn how to use symfony 4 to deliver our apps to the web.
@Petru the tone of your message is very aggressive. Please read it again and ask yourself if you'd like people to talk to you the same way. I guess not. Please consider you're talk to actual Human beings, despite the "comment wall" which can make it a bit abstract.
About the content of your message, I feel like you should be happy with the change described in the article: making services private by default will encourage people to not use the container directly, which exactly means "less coupling with the framework".
And yes, in open-source software, it's plain common an legitimate to ask people to contribute to the code they decided to use. That's just how things work in our industry, and I really encourage you to do it also.
@Nicolas Grekas my tone is not aggressive, its just emergent frustration and consternation based on facts. I worry because a few years ago we were instructed to use the service container anywhere, anyhow, it didn't matter: http://symfony.com/doc/2.7/service_container.html I quote: "The container is the heart of Symfony: it allows you to standardize and centralize the way objects are constructed. It makes your life easier, is super fast, and emphasizes an architecture that promotes reusable and decoupled code. It's also a big reason that Symfony is so fast and extensible!"
It was advertised that: "configuring and using the service container is easy. [...] you'll be comfortable creating your own objects via the container and customizing objects from any third-party bundle. You'll begin writing code that is more reusable, testable and decoupled, simply because the service container makes writing good code so easy."
Now I'm looking at: http://symfony.com/doc/3.4/service_container.html and trying to figure out just why are we making these changes?
Wasn't possible back then to "writing code that is more reusable, testable and decoupled, simply because the service container makes writing good code so easy" ?
What happened in between ? What problem are we trying to fix ? What are we missing in symfony that required making the services private by default ?
Please show us the COMPLETE picture and tell us for how many years this "new" picture will be considered as "good practice". The simple statement that we are presented "This is not considered a good practice and we're slowly moving away from it" is not enough since IT WAS considered a GOOD PRACTICE just a few years ago.
Please find the words and time to present a strong motivation behind such change. The details of the change(technical side) is not important... really. What is import is that you and the other core contributors (which I respect and praise whenever the chance) tell us WHY.
Kind regards, Petru.
@Petru Cojocar, your tone was definitely aggressive. And "despicable" was far too dramatic. This change is deserving of neither hatred nor contempt.
Backwards compatibility can't be expected to be kept forever. 4.0 is has breaking changes so external bundle developers will have to update their bundles for 4.0 anyway.
@Petru, about this comment:
I'm sorry if the blog post was confusing, but that idea doesn't change. Your application still uses the service container everywhere and we strongly recommend to always use the service container.
The only difference is that previously we didn't care if you accessed the container directly in a controller/command (via "$this->get(...)") and now we say that's wrong and it's better to inject the services you need directly (in the command constructor or the controller action method).
@Keri Henare I agree I was "far too dramatic", that is because now I realize that it was a rant... @Javier Eguiluz I completely understand the outcomes of using the Container "anywhere" and "everywhere" - from experience this approach doesnt lead to clean code, because there's nothing that stops you using the ContainerAwareTrait in any of your classes which directly couples the entire container (thus the entire symfony framework) to your class - been there, done that - NOT OK!
But if nothing is stopping you (from a technical point of view) to use the container "anywhere" and "everywhere" then making the services private, from my point of view, only complicates things unnecessarily and we are left in the same situation, we are not fixing anything, we merely try to protect the developers from themselves and to this approach I disagree.
That was the entire idea of my "rant", lets do not have symfony try to "protect the developers from themselves", lets try to do 2 things:
If it were for me to decide (but IT'S NOT), I would completely remove the ContainerAwareTrait and the ability to directly access the service container in userland - this is the fix guys! Now how would I do that? First of all I would communicate WHY this is required and what is the alternative; then I would make sure that this "removal" of the service container from userland ONLY HAPPENS in symfony 4 so that all the bundle developers have enough time to adapt. Otherwise we are left promoting just "good practices" that have a very short half life and which doesnt drive our community to higher "professionalism" but rather to constant reevaluation of what is a good practice today vs what was a good practice last year. Lets stop trying to treat the symptoms (making services private) and address the elephant in the room (the service container should not be available in userland).
All the good wishes and lets hope that we all learn something useful from all this.
@Petru et al., good news, we're on par. The article has been updated to make it (hopefully) more clear.
@Nicolas +1
In unit tests, how do I obtain an instance of the entity manager without $container->get('doctrine') ?
How to get some service in test without forcing it public?
$this->container->get(SomeType::class);
@Marcel @Tomas you can set up a container definitions file loaded only in test environment (like services_test.xml) and alias those private services you're using in tests:
@Nicolas Grekas believe me or not, this make the migration from Symfony 3.3 to 3.4 really painful for no direct gain (from a business point of view). It's too late for that, but this kind of behavior should be kept for the next major :/
I have some questions, why make a services private?
alias it ?
@ChunpengLI : you inject it via type hinting into your Services / Controller actions / ...
See https://symfony.com/doc/current/service_container.html