Skip to content

Make as many services private as possible #24104

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

Merged
merged 1 commit into from
Sep 13, 2017

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Sep 5, 2017

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

List of services that will be kept public:

Remaining public aliases

cache.app_clearer <- for the cache:pool:clear command to clear app pools
$commandId <- for non-lazy commands which are get() at runtime
router <- for the base controller
templating <- for the base controller

Remaining public services

test.client <- for WebTestCase
security.password_encoder <- for use in ContainerAware classes
security.authentication_utils <- for use in ContainerAware classes
filesystem <- for use in ContainerAware classes
kernel <- for use in ContainerAware classes
translator <- for use in ContainerAware classes
validator <- for use in ContainerAware classes
cache_clearer <- for the cache:clear command
cache_warmer <- required to bootstrap the kernel
cache.app <- for userland only
cache.global_clearer <- for the cache:pool:clear command to clear all pools
cache.system <- for userland only
data_collector.dump <- required to have dump() work very early when booting the kernel
event_dispatcher <- required to wire console apps
form.factory <- for the base controller
http_kernel <- required to bootstrap the kernel
profiler <- used in tests
request_stack <- for the base controller
routing.loader <- used by routing
security.authorization_checker <- for the base controller
security.csrf.token_manager <- for the base controller
security.token_storage <- for the base controller
serializer <- for the base controller
session <- for the base controller
state_machine.abstract <- userland state machines
workflow.abstract <- userland workflows
twig <- for the base controller
twig.controller.exception <- controllers referenced by routing
twig.controller.preview_error <- controllers referenced by routing
var_dumper.cloner <- required to have dump() work very early when booting the kernel
web_profiler.controller.exception <- controllers referenced by routing
web_profiler.controller.profiler <- controllers referenced by routing
web_profiler.controller.router <- controllers referenced by routing

security.acl.dbal.connection
security.acl.provider
security.encoder_factory
security.firewall
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is an event subscriber, could be made private to me

cache.app_clearer
$commandId
debug.templating.engine.php
profiler.request_matcher
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be made private to me, only injected

security.password_encoder
session.handler
session.storage
session.storage.filesystem
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


annotation_reader
cache.app_clearer
$commandId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must remain public, this is for non-lazy commands which are get() at runtime

annotation_reader
cache.app_clearer
$commandId
debug.templating.engine.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the debug one does not need to be public IMO. You should never get it by its debug.* id (the templating.engine.php should stay public though)

router
security.access.decision_manager
security.acl.cache
security.acl.dbal.connection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not need to be public (if you need the DBAL connection yourselves dynamically, you should get it using the public ids given in DoctrineBundle)

templating
templating.loader
translator
twig.loader
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even this one should become private IMO. There is no reason to access it dynamically at runtime IMO.

cache.system
cache_warmer <- required to bootstrap the kernel
data_collector.cache
data_collector.dump
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do collectors actually need to be public ? AFAIK, they are needed only by the profiler, and so could become private


### Remaining public services

cache.adapter.* <- pools are private by default when created by config so not a pb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest adapters should become private in 4.0

security.csrf.token_manager
security.firewall
security.token_storage
security.validator.user_password
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should become private, like other constraint validators

serializer
session
state_machine.abstract <- state machines created by config are public
workflow.abstract <- workflows created by config are public
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we make them private instead ? The documented way to get them is to use the registry AFAIK, not to get the service from the container

translator
twig
twig.controller.exception
twig.controller.preview_error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are required to be public due to the routing referencing them, right ?

<argument type="service" id="request_stack" />
</service>

<service id="assets.path_package" class="Symfony\Component\Asset\PathPackage" abstract="true" public="true">
<service id="assets.path_package" class="Symfony\Component\Asset\PathPackage" abstract="true">
<tag name="container.private" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the place where we actually need to apply this tag is in child services

@nicolas-grekas nicolas-grekas force-pushed the privates branch 2 times, most recently from ae67484 to 0d55972 Compare September 7, 2017 18:45
@nicolas-grekas
Copy link
Member Author

@chalasr @stof thanks for the first round of review, comments addressed.

In order to allow deprecating public aliases, I changed the implementation to not rely on a tag anymore. Instead, I added Alias/Definition::isPrivate(). The PR is ready for a second round review. It only needs tests now.

@nicolas-grekas nicolas-grekas force-pushed the privates branch 2 times, most recently from 4f8c41d to 89e8e28 Compare September 8, 2017 06:14
Copy link
Contributor

@ro0NL ro0NL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Fixed rather elegant 👍 is setPrivate an internal detail now? should it?

serializer <- for the base controller
session <- for the base controller
state_machine.abstract <- state machines created by config are public
workflow.abstract <- workflows created by config are public
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lyrixx @Nyholm should we plan to make these services private in 4.0 and tell ppl that injecting workflow.registry is the way to go?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. The registry is only here for convenience in twig.
Users should inject the right workflow in other services. Adding an extra layer will make the core harder to understand and to test. It's exactly like doctrine. People should inject the right repository, not the whole Doctrine Manager.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, then we can still make them private, isn't it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lyrixx if you want them to inject it in the service, it can be private. Public is here for dynamic get.

But if they are meant to be the main services of the workflow component, they should indeed stay public for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because we need to access it from a controller or a command.
Not everyone are using controller/command as a service.

translation.extractor
translation.loader
translation.reader
translation.writer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nyholm do you think we should plan making private these translation.*/translator services in 4.0?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

translator must stay public. Otherwise, people using ContainerAware classes cannot use the translator layer anymore

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, all theses services (above) could be private. except the security.authentication_utils (for use in ContainerAware classes)

@nicolas-grekas
Copy link
Member Author

@ro0NL setPrivate is OK for eg bundles to me so they can do the same as we do here.

@ro0NL
Copy link
Contributor

ro0NL commented Sep 8, 2017

Guessed so. But are we sure about setPublic & setPrivate doing different things, which is here to stay for the long run. This requires documentation, but i think it needs a better name...

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Sep 8, 2017

setPublic & setPrivate doing different things

they don't need to do different things in 4.0, that's the solution.

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Sep 8, 2017

And in fact, they already don't do different things already - except for internal details.

@ro0NL
Copy link
Contributor

ro0NL commented Sep 8, 2017

Yeah but setPublic(false) wont enable isPrivate() === true.. right? That's unexpected.

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Sep 8, 2017

but setPublic(false) wont enable isPrivate() === true.. right

locally, that's correct - making it so would just discard any benefits of introducing it.
But the ResolveChildDefinitionsPass turns isPrivate() to setPublic(false) in the end, so semantically, they're the same.
The special behavior related to isPrivate() is 3.4-only and tailored to the purpose of making currently public services private in 4.0.
For this reason also, setPrivate() cannot be set via a (yaml/xml) loader - only a DI ext can. So only "advanced" users can use it.

@stof
Copy link
Member

stof commented Sep 8, 2017

I think we need to improve the documentation about the difference between setPublic(false) and setPrivate(true) in the phpdoc (the phpdoc will be the place where advanced users will learn about it IMO, rather than learning it in symfony-docs, but the current phpdoc does not allow to understand the difference and why they should use one rather than the other)

@nicolas-grekas
Copy link
Member Author

failures are false positives
list of remaining public services added to the PR's description and removed from patch
ready for vote+merge

@javiereguiluz
Copy link
Member

Very nice work! Imagining myself as someone new to Symfony, I would have issues/doubts with these services:

  1. Everything related to "cache clear": it looks like something internal to Symfony which I shouldn't care about

cache.app_clearer alias
cache_clearer <- for the cache:clear command
cache.global_clearer <- for the cache:pool:clear command to clear all pools

  1. Controllers: all these services

twig.controller.*
web_profiler.controller.*

@chalasr
Copy link
Member

chalasr commented Sep 9, 2017

@javiereguiluz We still have some places where we are forced to get() public services from the container internally, mostly places where we fetch unpredictable services which cannot be grouped by scope, like controllers which can be any service (no required tag), similar problem for those related to cache (have a look at CacheClearCommand/CachePoolClearCommand).
The services you listed must remain public.

@ro0NL
Copy link
Contributor

ro0NL commented Sep 9, 2017

command related services can be made private in 4.0 :)

@mvrhov
Copy link

mvrhov commented Sep 9, 2017

If the controllers are the only big area which cannot be made private.. Maybe the controller tag should be introduced to make this possible?

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Sep 9, 2017

There is already a tag to flag services as controllers (controller.service_arguments).
BUT it works the other way around: the routing contains references to services.
So that the correct logic to do that would be to parse the routing config to then flag controllers as such.
That's not something we have right now, and I'm personally no planning to work on it at all: controllers as public services are just fine, there is nothing to "fix" here IMHO.

@mvrhov
Copy link

mvrhov commented Sep 9, 2017

Just to complete this. But controller.service_arguments doesn't work for the controllers that are actually registered as services. And I find it weird that I'd have to tag with that when using "actions" for controllers e.g one class per action with __invoke and all dependencies injected via constructor.

@nicolas-grekas
Copy link
Member Author

@mvrhov I don't know anymore what you're talking about :) You don't have to use that tag at all, that's what I said. You just have to make your controllers public.

@javiereguiluz
Copy link
Member

@nicolas-grekas the thing that controllers must be public is more confusing than it looks. I can understand public services: you may inject this somewhere, so make it public. OK. But I don't inject or need or use or do anything with controllers. It's "a Symfony thing" and that's why I expect them to not be public.

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Sep 9, 2017

OK, I've no solution for that - and I don't think there is anything wrong in fact.

A service really needs to be public for one and only one reason: bootstrapping.
That's because the app must start with something.
There are at least three common bootstrapping contexts:

  • HTTP : here the entry point is routing thus controllers
  • CLI: the entry point is the Application thus commands
  • tests: the entry point is whatever you use in the body of your test cases

all other uses are the bad sort of service locator.

@nicolas-grekas
Copy link
Member Author

ping @symfony/deciders, one vote missing :)

@nicolas-grekas nicolas-grekas merged commit 1936491 into symfony:3.4 Sep 13, 2017
nicolas-grekas added a commit that referenced this pull request Sep 13, 2017
…kas)

This PR was merged into the 3.4 branch.

Discussion
----------

Make as many services private as possible

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

- [x] review the list of currently public services and validate which one should be turned private (noted as such with the `container.private` tag in this PR)
- [x] rebase on top of #24103
- [x] implement the behavior described in #23822 (comment)
- [x] add tests

List of services that will be kept public:

### Remaining public aliases

cache.app_clearer <- for the cache:pool:clear command to clear app pools
$commandId <- for non-lazy commands which are get() at runtime
router <- for the base controller
templating <- for the base controller

### Remaining public services

test.client <- for WebTestCase
security.password_encoder <- for use in ContainerAware classes
security.authentication_utils <- for use in ContainerAware classes
filesystem <- for use in ContainerAware classes
kernel <- for use in ContainerAware classes
translator <- for use in ContainerAware classes
validator <- for use in ContainerAware classes
cache_clearer <- for the cache:clear command
cache_warmer <- required to bootstrap the kernel
cache.app <- for userland only
cache.global_clearer <- for the cache:pool:clear command to clear all pools
cache.system <- for userland only
data_collector.dump <- required to have dump() work very early when booting the kernel
event_dispatcher <- required to wire console apps
form.factory <- for the base controller
http_kernel <- required to bootstrap the kernel
profiler <- used in tests
request_stack <- for the base controller
routing.loader <- used by routing
security.authorization_checker <- for the base controller
security.csrf.token_manager <- for the base controller
security.token_storage <- for the base controller
serializer <- for the base controller
session <- for the base controller
state_machine.abstract <- userland state machines
workflow.abstract <- userland workflows
twig <- for the base controller
twig.controller.exception <- controllers referenced by routing
twig.controller.preview_error <- controllers referenced by routing
var_dumper.cloner <- required to have dump() work very early when booting the kernel
web_profiler.controller.exception <- controllers referenced by routing
web_profiler.controller.profiler <- controllers referenced by routing
web_profiler.controller.router <- controllers referenced by routing

Commits
-------

1936491 Make as many services private as possible
@nicolas-grekas nicolas-grekas deleted the privates branch September 13, 2017 15:05
nicolas-grekas added a commit that referenced this pull request 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
This was referenced Oct 18, 2017
@kbond
Copy link
Member

kbond commented Oct 25, 2017

@nicolas-grekas After upgrading to 3.4, I'm getting this deprecation warning:

User Deprecated: The "routing.loader" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.

This is the culprit:

$this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']);

@Tobion
Copy link
Contributor

Tobion commented Oct 25, 2017

The routing.loader should be public. So that is strange.

@kbond
Copy link
Member

kbond commented Oct 25, 2017

Yeah, I discovered a bundle is at fault... Please ignore...

leofeyer pushed a commit to contao/core-bundle that referenced this pull request Mar 2, 2018
…tainer (see #1383).

Description
-----------

I searched the Contao source code for instances of `getContainer()->get('...');` occurrences, filtered out `contao` prefixed services and "uniquified" the resulting array.

```
assets.packages
database_connection
doctrine.dbal.default_connection
event_dispatcher
filesystem
fragment.handler
lexik_maintenance.driver.factory
monolog.logger.contao
request_stack
router
security.authentication_utils
security.authorization_checker
security.firewall.map
security.logout_url_generator
security.token_storage
session
swiftmailer.mailer
translator
```

After intersecting this array to the one found in this ticket (future public services) [this ticket](symfony/symfony#24104), we end up with:

```
assets.packages
database_connection
doctrine.dbal.default_connection
fragment.handler
lexik_maintenance.driver.factory
monolog.logger.contao
security.firewall.map
security.logout_url_generator
swiftmailer.mailer
```

I added those to our `MakeServicesPublicPass` and made sure it will also find aliases instead of definitions only.

Unfortunately I'm not sure if I really caught all direct service references. Do you have any other ideas?

Commits
-------

d6df8a8 Make all services public that we are retrieving directly from the container.
579c113 CS Fix
a93083a Test all services in the unit test.
christian-kolb pushed a commit to christian-kolb/contao that referenced this pull request Oct 11, 2018
…the container (see contao#1383).

Description
-----------

I searched the Contao source code for instances of `getContainer()->get('...');` occurrences, filtered out `contao` prefixed services and "uniquified" the resulting array.

```
assets.packages
database_connection
doctrine.dbal.default_connection
event_dispatcher
filesystem
fragment.handler
lexik_maintenance.driver.factory
monolog.logger.contao
request_stack
router
security.authentication_utils
security.authorization_checker
security.firewall.map
security.logout_url_generator
security.token_storage
session
swiftmailer.mailer
translator
```

After intersecting this array to the one found in this ticket (future public services) [this ticket](symfony/symfony#24104), we end up with:

```
assets.packages
database_connection
doctrine.dbal.default_connection
fragment.handler
lexik_maintenance.driver.factory
monolog.logger.contao
security.firewall.map
security.logout_url_generator
swiftmailer.mailer
```

I added those to our `MakeServicesPublicPass` and made sure it will also find aliases instead of definitions only.

Unfortunately I'm not sure if I really caught all direct service references. Do you have any other ideas?

Commits
-------

d6df8a8a Make all services public that we are retrieving directly from the container.
579c1138 CS Fix
a93083a2 Test all services in the unit test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.