Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[RFC] Making services private by default in Symfony 4? #20048

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nicolas-grekas opened this issue Sep 24, 2016 · 29 comments
Closed

[RFC] Making services private by default in Symfony 4? #20048

nicolas-grekas opened this issue Sep 24, 2016 · 29 comments
Labels
DependencyInjection Feature RFC RFC = Request For Comments (proposals about features that you want to be discussed)
Milestone

Comments

@nicolas-grekas
Copy link
Member

Just wondering, how about making services private by default in Symfony 4?
That would give us more private services optimizations by default, thus lighter containers.
The path could be as "simple" as deprecating the "public" attribute and replacing it by a "private" one.
Worth pursuing?

@nicolas-grekas nicolas-grekas changed the title Making services private by default in Symfony 4? [RFC] Making services private by default in Symfony 4? Sep 24, 2016
@GuilhemN
Copy link
Contributor

GuilhemN commented Sep 24, 2016

👍 but for that imo we have to allow using private services with any tag (create a public alias when needed for example).

@linaori
Copy link
Contributor

linaori commented Sep 25, 2016

I'd be very happy to see this, but it would be a big issue for people extending the Controller. Imo Controller as a Service is the way to go but not everyone agrees with me.

@GuilhemN
Copy link
Contributor

GuilhemN commented Sep 25, 2016

@iltar imo symfony should create its own public aliases when it needs to, instead of forcing the user to make his services public... but i'm not sure everyone agrees.

Edit: actually that looks hard to do for controllers as they are resolved at runtime...

@linaori
Copy link
Contributor

linaori commented Sep 25, 2016

@Ener-Getick the problem is that Symfony can't guess the service you're going to need if you use a Service Locator.

@GuilhemN
Copy link
Contributor

GuilhemN commented Sep 25, 2016

An alternative would be to encourage using DunglasActionBundle but again not everyone agrees...
We could limit the problem by allowing making public an entire set of services (during their import or as a header of the file).

@linaori
Copy link
Contributor

linaori commented Sep 25, 2016

You'd have to do static code analysis of the file and try to determine all locator calls. This works up to a point where calls become dynamic.

@GuilhemN
Copy link
Contributor

@iltar i meant as a header of the config files 😉

@linaori
Copy link
Contributor

linaori commented Sep 25, 2016

Ah, well then I would rather recommend making everything private by default and recommending controller as a service instead.

@hhamon
Copy link
Contributor

hhamon commented Sep 25, 2016

I would vote for but that's a major BC break and it will probably prevent people from upgrading to Symfony 4. I don't know if we can make this behavior globally configurable (enable or disable).

@GuilhemN
Copy link
Contributor

GuilhemN commented Sep 25, 2016

@hhamon that's why we use bc layers and deprecations. For apps respecting best practices, it shouldn't be that hard to upgrade.

But i'm wondering if it wouldn't make services even harder to understand for newcomers. I'm 👍 for making this globally configurable.
Maybe we should make services public by default in the standard edition and private by default in the framework to ensure bundles tests are always against the worst scenario. This would make easier to understand the framework using the standard edition but that wouldn't be very consistent.

@hhamon
Copy link
Contributor

hhamon commented Sep 25, 2016

Yes it will be harder for newcomers to understand this concept. When I do training sessions, including advanced ones, it's often rare that people know the differences between private and public services. And the consequences private service definitions have on the container compilation. So IMO, we have to be careful with this. If we decide to switch to private services by default, it has to be very very well documented. But again, I would prefer having a global configuration setting to enable or disable this behavior.

@GuilhemN
Copy link
Contributor

@hhamon for me, we should promote autowiring for newcomers (or at worst methods hiding the calls to the container such as the base Controller). It is more magic but it reduces the learning path and would allow us more easily to do this change.

@nicolas-grekas
Copy link
Member Author

It can't be a global flag: definitions can't rely on some outside context! But it could be changed locally for a whole file for sure. Having everything private by default would prevent using the dic as a service locator, which is too easy for newcomers today. They'd better learn very soon that if they do this, they must optin to the not-the-best practice.

@nicolas-grekas
Copy link
Member Author

Oh btw, we already planned to prevent fetching private services from the container in 4.0, so we already have the right explanation point for newcomers: the exception message they'll get then.

@GuilhemN
Copy link
Contributor

GuilhemN commented Sep 25, 2016

@nicolas-grekas even with bad practices, the framework is hard to understand at first. The exception thrown when using private services at runtime will of course be useful but only if the user already understands services.
The best would be to take a look at other frameworks and see how they make it easy for newcomers to get how they work.

@theofidry
Copy link
Contributor

What about libraries? If an application the issue is scoped to service locators, in libraries it's much more complex: you definitely don't want a service as private by default.

What about a switch to trigger private by default instead (that would apply to application local services as well as an library services)? If the goal is performance here, I don't think it's right to break the BC promise for it, but we should still able to make it possible for people looking for it.

@linaori
Copy link
Contributor

linaori commented Sep 26, 2016

@theofidry Usually when 3rd party bundles need services, they are being configured via the configuration/extension/compiler pass (in this sequence). At the last 2 points, you can fetch the service and make it public if needed, works well in combination with tagged services.

@theofidry
Copy link
Contributor

theofidry commented Sep 26, 2016

@iltar as actually private services are accessible as before even if they come from a third-party bundle it's not a problem, sorry for the confusion. The only place (to my understanding) where it would be a problem are service locators, but it's a concern you already shared.

So I'm all for it :)

@sh4ka
Copy link

sh4ka commented Sep 28, 2016

Would love to see this feature.

@xabbuh
Copy link
Member

xabbuh commented Sep 28, 2016

Well, if we made that steps, we would have to treat controllers that use the DI container as a service locator a bad practice. So effectively you will have to register your controllers as services (or you have to turn services into public ones again). Is that something we can agree on (in the docs we recently made the shift the other way around, i.e. not propagating controllers as services)?

@sh4ka
Copy link

sh4ka commented Sep 28, 2016

I guess private services by default do not enforce that behavior, and mark public services as such.

@linaori
Copy link
Contributor

linaori commented Sep 28, 2016

@xabbuh currently it's recommend to extend the Controller and use $this->render() for example. As a BC layer for this, https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php#L61-L67 should also call the setContainer. This means the Controller as a Service can still work for the default methods. Just have to throw a bunch of errors/warnings.

Additionally for 4.0, a bunch of controller helpers (or make the Controller a ControllerHelper with public methods instead) should be created to "ease" Controller as a Service for people. With Autowiring in place, this could still be relatively easy for people to start with.

@fabpot
Copy link
Member

fabpot commented Sep 28, 2016

@xabbuh I'm still a firm believer that controller as a service is not at all something people should do by default. Too much added complexity for little gain. We need to support the controller as a service pattern for people who like this approach and to make controllers independent of the full-stack framework (to be able to share them with other libs/frameworks built on top of the Symfony components).

@theofidry
Copy link
Contributor

@nicolas-grekas could this be configured via a parameter?

@dunglas
Copy link
Member

dunglas commented Sep 29, 2016

👍 to make services private by default.

@fabpot We can also import the code of ActionBundle in core (or include the bundle in the standard edition symfony/symfony-standard#960). It's about 200 LOC and it allows to create and use controllers as a service as easily as the standard Symfony controller while promoting better practices regarding dependency injection: https://github.com/dunglas/DunglasActionBundle#usage

IMO it's even easier for newcomers than the current controller system because it doesn't require to register services in XML or YAML.

I added this bundle in the standard edition of API Platform 2 (https://github.com/api-platform/docs/blob/master/core/operations.md#creating-custom-operations-and-controllers), and I got only positive feedback from users for now.

@linaori
Copy link
Contributor

linaori commented Sep 29, 2016

While I find the configuration for the ActionBundle a bit confusing, I really like the concept of the bundle. Maybe it cold indeed be integrated into Symfony?

@dunglas
Copy link
Member

dunglas commented Sep 29, 2016

@iltar can you open an issue about the config? What looks confusing to you? It can probably be improved.

@sstok
Copy link
Contributor

sstok commented Sep 29, 2016

The best would be to take a look at other frameworks and see how they make it easy for newcomers to get how they work.

Silex (Pimple) used to mark services as private by default, but changed this for performance reasons.

Marking services as private by default seems like a good idea, when you mark them as public it's not a bad-practice perse, it simply means you want to make them accessible elsewhere (private/internal vs public API). But most services should not be accessible by default (like 'form.registry').

Sorry for the off topic, but is it better to mark a service as lazy rather then marking them as public and use the Container for loading them??

PS. Anyone who needs compatibility can simple add a CompilerPass that marks all services as public 😄 but that's a horrible idea.

@stof
Copy link
Member

stof commented Jan 1, 2017

Silex (Pimple) used to mark services as private by default, but changed this for performance reasons.

@sstok It never did this. Pimple does not even have a concept of private services (as it does not have a compilation step to apply optimizations).
You are confusing things with the switched from non-shared to shared by default.

The performance reasons made Pimple change the way shared services are stored internally. And this change made it possible to share by default (the old way required sharing to be done on top of non-shared services, and so could not be the default). The change of default behavior was done because 99% of services are actually shared (and Symfony DI always made them shared by default)

@fabpot fabpot closed this as completed Jan 7, 2017
fabpot added a commit that referenced this issue Jan 7, 2017
…ame for "public", "tags" & "autowire" (nicolas-grekas, ogizanagi)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[DI] Add "inherit-tags" with configurable defaults + same for "public", "tags" & "autowire"

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #20048
| License       | MIT
| Doc PR        | -

Instead of making services private by default (#20048), which is going to create a big migration burden that might not be worth it, I'd like to propose the idea of changing the default for the current file.

Having a place to redefine some defaults, this can also be used to enable autowiring for a file, making it much lighter in the end.

This PR handles defaults for "public", "autowired" and "tags". Not sure the other options need a configurable default. Please comment if you think otherwise.

Short example (the feat is more interesting with a longer list of services):
```yaml
services:
    _defaults:
        public: false
        autowire: ['_construct', 'set*']

    foo:
        class: Foo
```

```xml
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <defaults public="false">
            <autowire>__construct</autowire>
            <tag name="foo"/>
        </defaults>
    </services>
</container>
```

ping @dunglas @weaverryan

Commits
-------

beec1cf [DI] Allow definitions to inherit tags from parent context
05f24d5 [DI] Add "defaults" tag to XML services configuration
7b4a18b [DI] Add "_defaults" key to Yaml services configuration
nicolas-grekas added a commit that referenced this issue Sep 19, 2017
…h BC layer (nicolas-grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[DI] Turn services and aliases private by default, with BC layer

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #20048
| License       | MIT
| Doc PR        | -

With this PR, all services and aliases are made private by default.
This is done in a BC way, thanks to the layer introduced in #24104.

We will require bundles to explicitly opt-in for "public", either using "defaults", or stating `public="true"` explicitly. Same in DI extension, where calling `->setPublic(true)` will be required in 4.0.

Commits
-------

9948b09 [DI] Turn services and aliases private by default, with BC layer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DependencyInjection Feature RFC RFC = Request For Comments (proposals about features that you want to be discussed)
Projects
None yet
Development

No branches or pull requests