Skip to content

[DI] Prevent AutowirePass from triggering irrelevant deprecations #22282

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
Apr 5, 2017

Conversation

chalasr
Copy link
Member

@chalasr chalasr commented Apr 4, 2017

Q A
Branch? 2.8
Bug fix? no (just a failing test case atm)
New feature? no
BC breaks? no
Deprecations? no
Tests pass? no
Fixed tickets n/a
License MIT
Doc PR n/a

For populating available types the AutowirePass iterates over $container->getDefinitions() trying to instantiate a reflection class for each definition.
Problem is that if one of these classes is deprecated, a notice is triggered due to the reflection, even if the service is actually never used.

Right now this only reproduces the issue with a failing test case, this bug (if we agree it's a bug) breaks the test suite of a bundle I maintain (see https://travis-ci.org/lexik/LexikJWTAuthenticationBundle/jobs/218275650#L262)

Solutions I can think about for now:

  • Skip deprecated definitions from type registering, meaning that if a service is deprecated a day, all autowired services that rely on it will suddenly be broken, also the bug would remain if the definition is not deprecated but relies on a deprecated class, not good I think
  • Register an error handler ignoring deprecations during the type registering process (AutowirePass#populateAvailableType()), works but makes my test suite say THE ERROR HANDLER HAS CHANGED. I'll push my try as a 2nd commit.

Thoughts?

@nicolas-grekas
Copy link
Member

We could register an error handler, but I'd do this only for deprecated definitions.

THE ERROR HANDLER HAS CHANGED

restoring the previous error handler properly should fix that issue.

@nicolas-grekas nicolas-grekas added this to the 2.8 milestone Apr 5, 2017
@chalasr chalasr force-pushed the autowiring-deprecated-defs branch 4 times, most recently from 333fd36 to 0dbb1eb Compare April 5, 2017 12:06
@chalasr chalasr changed the title [DI] AutowirePass triggers irrelevant deprecation notices [DI] Prevent AutowirePass from triggering irrelevant deprecations Apr 5, 2017
@@ -309,6 +319,10 @@ private function getReflectionClass($id, Definition $definition)
$reflector = false;
}

if ($deprecated) {
restore_error_handler();
Copy link
Member

Choose a reason for hiding this comment

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

this needs to happen even when the previous code throws (eg the previous error handler)

@@ -301,6 +301,16 @@ private function getReflectionClass($id, Definition $definition)
return false;
}

if ($deprecated = $definition->isDeprecated()) {
$prevErrorHandler = set_error_handler(function ($level, $message, $file, $line, $context) use (&$prevErrorHandler) {
Copy link
Member

@nicolas-grekas nicolas-grekas Apr 5, 2017

Choose a reason for hiding this comment

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

$context is deprecated in 7.2, let's not use it

return false;
}

return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line, $context) : false;
Copy link
Member

Choose a reason for hiding this comment

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

onliner:
return E_USER_DEPRECATED === $level || !$prevErrorHandler ? false : $prevErrorHandler($level, $message, $file, $line);

@chalasr
Copy link
Member Author

chalasr commented Apr 5, 2017

Comments addressed, thanks!
/cc @xabbuh this is heavily inspired from #22274, @nicolas-grekas's comments should be good for it as well (#22282 (comment)).

@chalasr chalasr force-pushed the autowiring-deprecated-defs branch 2 times, most recently from 069e2ff to 6f5b183 Compare April 5, 2017 13:50
@@ -303,9 +303,28 @@ private function getReflectionClass($id, Definition $definition)

$class = $this->container->getParameterBag()->resolveValue($class);

if ($deprecated = $definition->isDeprecated()) {
$prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
return E_USER_DEPRECATED === $level || !$prevErrorHandler ? false : $prevErrorHandler($level, $message, $file, $line);
Copy link
Member

Choose a reason for hiding this comment

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

I don't find this very readable. Can we at least add some parentheses?

Copy link
Member Author

@chalasr chalasr Apr 5, 2017

Choose a reason for hiding this comment

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

parentheses added

@chalasr chalasr force-pushed the autowiring-deprecated-defs branch from 6f5b183 to 77927f4 Compare April 5, 2017 14:41
@fabpot
Copy link
Member

fabpot commented Apr 5, 2017

Thank you @chalasr.

@fabpot fabpot merged commit 77927f4 into symfony:2.8 Apr 5, 2017
fabpot added a commit that referenced this pull request Apr 5, 2017
…cations (chalasr)

This PR was merged into the 2.8 branch.

Discussion
----------

[DI] Prevent AutowirePass from triggering irrelevant deprecations

| Q             | A
| ------------- | ---
| Branch?       | 2.8
| Bug fix?      | no (just a failing test case atm)
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | no
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

For populating available types the AutowirePass iterates over `$container->getDefinitions()` trying to instantiate a reflection class for each definition.
Problem is that if one of these classes is deprecated, a notice is triggered due to the reflection, even if the service is actually never used.

~~Right now this only reproduces the issue with a failing test case~~, this bug (if we agree it's a bug) breaks the test suite of a bundle I maintain (see https://travis-ci.org/lexik/LexikJWTAuthenticationBundle/jobs/218275650#L262)

Solutions I can think about for now:
- ~~Skip deprecated definitions from type registering, meaning that if a service is deprecated a day, all autowired services that rely on it will suddenly be broken, also the bug would remain if the definition is not deprecated but relies on a deprecated class, not good I think~~
- Register an error handler ignoring deprecations during the type registering process (`AutowirePass#populateAvailableType()`), ~~works but makes my test suite say `THE ERROR HANDLER HAS CHANGED`. I'll push my try as a 2nd commit.~~

Thoughts?

Commits
-------

77927f4 [DI] Prevent AutowirePass from triggering irrelevant deprecations
@chalasr chalasr deleted the autowiring-deprecated-defs branch April 5, 2017 16:49
fabpot added a commit that referenced this pull request Apr 5, 2017
…g reflection against all existing services (nicolas-grekas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[BC BREAK][DI] Always autowire "by id" instead of using reflection against all existing services

| Q             | A
| ------------- | ---
| Branch?       | 3.3
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | yes - compile time only
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

(patch best reviewed [ignoring whitespaces](https://github.com/symfony/symfony/pull/22295/files?w=1).)

"By-id" autowiring, as introduced in #22060 is free from all the issues that "by-type" autowiring has:
- it has no magic and requires explicit type<>id matching (*vs* using reflection on all services to cherry-pick *the* one that matches some type-hint *at that time*, which is fragile)
- it is free from any ambiguities (*vs* the Damocles' sword of breaking config just by enabling some unrelated bundle)
- it is easily introspected: just look at DI config files (*vs* inspecting the type-hierarchy of all services + their type-hints)
- ~~it is side-effect free, thus plain predictable (*vs* auto-registration of discovered types as services)~~
- it plays nice with deprecated services or classes (see #22282)
- *etc.*

Another consideration is that any "by-type" autowired configuration is either broken (because of future ambiguities) - or equivalent to a "by-id" configuration (because resolving ambiguities *means* adding explicit type<>id mappings.) For theoreticians, we could say that "by-id" autowiring is the asymptotic limit of "by-type" autowiring :-)

For all these reasons - and also because it reduces the complexity of the code base - I propose to change the behavior and only support "by-id" autowiring in 3.3. This will break some configurations relying on "by-type" autowiring. Yet the break will only happen at compile-time, which means this won't *silently* break any apps. For all core Symfony services, they will work out of the box thanks to #22098 *et al.* For the remaining services, fixing ones config should be pretty straightforward: just follow the suggestions provided by the exception messages. If they are fine to you, you'll end up with the exact same config in the end. And maybe you'll spot issues that were hidden previously.

Commits
-------

cc5e582 [BC BREAK][DI] Always autowire "by id" instead of using reflection against all existing services
This was referenced May 1, 2017
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.

5 participants