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

Skip to content

[DI] Replace wildcard-based methods autowiring by @required annotation #21763

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 2 commits into from
Mar 1, 2017

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Feb 25, 2017

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no (affects things that are only on master)
Deprecations? no
Tests pass? yes
Fixed tickets -
License MIT
Doc PR -

While playing a bit with new features in master around DI configuration, several people around me got bitten by wildcard-based autowiring. The typical example is adding autowire: [set*] in _defaults: use that on resource: ../src/Command/ PSR4-based loading and boom, setApplication and setHelperSet will now be wrongly called. You could tell me "of course, don't to that" - but being bitten so early on a master-only feature makes me really unconfident that this will be easy enough for people after the release.

If wildcard-based autowiring is removed, then I don't see anymore the need for allowing arrays as in autowire: [setFoo,getBar]. Moreover, this array syntax has a core DX issue: it's a dead end as far as the learning curve is concerned. You learn it, then when becoming a more advanced dev, someone teaches you that you'd better use another syntax: explicit wiring.

And in fact, we don't need it at all, because something else already exists: just declare a method call, but don't define its arguments. If autowire: true is set, then the AutowiringPass already fills in the holes. There is only one tweak required to make this work: don't autowire optional arguments for method calls - or that'd be a BC break. To my PoV that's even better: this makes autowiring fit a "do the minimum to make it work" strategy. A really good one to me.

But there is still an issue: wildcard-based autowiring fits a need. Namely, it allows one to define a convention (eg. 'set*'), and have all such methods that follow the convention be autowired. To me, this looks like doing it reverse (the DI config should adapt to the code, not reverse). So, to fill this need, let the declaration be in the source: just use an annotation!

This PR adds support for the @required annotation, borrowed from the Spring framework:
https://www.tutorialspoint.com/spring/spring_required_annotation.htm

Using the annotation is totally optional of course. If you do, and if autowiring is on, then it'll be autowired. If you don't, nothing changes: do manual wiring.

Even when not using autowiring, the annotation is still a nice hint for the consumer of your classes: it tells the reader that this method needs to be called for correct instantiation - thus lowering one drawback of setter injection (discoverability).

The implementation of the annotation parsing is done using a few regexp (no dep on any complex parser) - and works with inheritance, by leveraging the @inheritdoc tag (the default behavior being to not inherit anything from parent methods).

All in all, looking at the diff stats, it makes everything simpler. Good sign, isn't it?

@weaverryan
Copy link
Member

I like it! It removes 2 things I don't like:

  • I never liked the autowire key with actual args (autowire: [setFoo,getBar]). It moves more config into YAML, with limited benefit.

  • Since the set* thing has proven to be a "gotcha", we should definitely remove it: we're trying to walk a balance of added convenience, but no significant "wtf" increases!

There is only one tweak required to make this work: don't autowire optional arguments for method calls - or that'd be a BC break. To my PoV that's even better: this makes autowiring fit a "do the minimum to make it work" strategy. A really good one to me.

Yes! I agree - autowiring should do as little as possible (based on the code you've written).

And explaining autowiring should now be pretty simple!

By adding autowire: true, Symfony will read the type-hints on your __construct()
method and automatically try to inject services that match those type-hints. You can
also tell Symfony to automatically call (and autowire) other methods (e.g. setLogger())
by adding an @required annotation above the method. This also works for
getter-injected methods.

@mvrhov
Copy link

mvrhov commented Feb 26, 2017

I like the autowire feature, but I don't like this proposal. Our company has no annotations policy, so if I understand correctly this means no autowire for us.

@ro0NL
Copy link
Contributor

ro0NL commented Feb 26, 2017

just declare a method call, but don't define its arguments

Some of my first thoughts :) #19631 (comment) i think it's a good convention 👍

don't autowire optional arguments for method calls

With named arguments we could define optional ones, override required ones, and let autowiring fill in the remaining required ones.. right?

And shouldnt this be allowed by the annotation-form as well? I.e. @MethodCall({'$arg': '@ref'}) (i think i'd prefer @MethodCall over @Required).

@ro0NL
Copy link
Contributor

ro0NL commented Feb 26, 2017

I see @required is a specific built-in feature, where i was thinking of annotation support like we do for routes/validation (ie. @DI\MethodCall, etc.), my bad. Im not even sure we should go that way..

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Feb 26, 2017

so if I understand correctly this means no autowire for us.

@mvrhov I think you did not understand correctly: if you don't want to use the annotation, that won't prevent you from using autowiring. First, constructor autowiring doesn't require it. Then, for setters and getters, that will just make you declare which of them to autowire in the DI config, instead of in the code. It has to be somewhere. The annotation allows code authors to help autowiring users so that they can save these DI config lines. Only that.

With named arguments we could define optional ones, override required ones, and let autowiring fill in the remaining required ones.. right?

@ro0NL Right!

And shouldnt this be allowed by the annotation-form as well? I.e. @MethodCall({'$arg': '@ref'}) (i think i'd prefer @MethodCall over @Required)

Yes, this shouldn't be allowed, because this is moving the DI configuration right into the code - which is not what this PR does - and it a totally different topic.

I see @required is a specific built-in feature, where i was thinking of annotation support like we do for routes/validation (ie. @DI\MethodCall, etc.), my bad. Im not even sure we should go that way..

Same here, we shouldn't go that way to me, too much added complexity for no benefit at all.

@linaori
Copy link
Contributor

linaori commented Feb 26, 2017

I will see a lot of people remove @required from the docblock because:

  • It's not something used in docblocks
  • It's not in the expected annotation format used by Doctrine Annotations

@fabpot
Copy link
Member

fabpot commented Mar 1, 2017

First of all, I'm really happy that we will remove the get* and set* support in the autowire configuration. I did test them and it just does not work well in real-life projects.

That being said, having "something" to autowire dependencies in a lazy-way would be a great feature for Symfony 3.3. And we are almost there. I've migrated several projects and implemented all the new features of the container, and I have to say that this is really a joy to work with (I actually encourage anyone to try it on their projects and give feedback).

The proposed @required annotation fits a hole that would finish our container enhancement journey quite nicely. It's a very elegant solution for what we are trying to do.

If we look at the semantic of the proposed @required annotation closely, I think it's very different from the annotations we manage in FrameworkExtraBundle. It seems to me that it is more similar to the @deprecated or @final annotations.

Those annotations gather generic and purely descriptive semantic about some methods/classes, they have nothing specific about Symfony and can/could be used by any PHP code. Note that we use the semantic they convey in the Debug component, in DebugClassLoader, to give a better DX, so we have a precedent here.

I think all of this applies also to the @required annotation: it can benefit to any PHP code, and allows to declare which parts of a class are required for full instantiation, apart from the constructor. While the generalization of this annotation is not the target, it sounded like an important criteria to understand its usefulness to me. This is to be opposed to Doctrine annotations, which convey domain specific semantics, and thus wouldn't be suited here.

The fact that this annotation is not yet standardized shouldn't prevent us from adopting it (we did so for @final already).

This is also very different from previous proposals that tried to move configuration into annotations. Using the @required annotation when present is purely optional and is only triggered when you enable the autowiring.

@fabpot
Copy link
Member

fabpot commented Mar 1, 2017

👍

@fabpot
Copy link
Member

fabpot commented Mar 1, 2017

Thank you @nicolas-grekas.

@fabpot fabpot merged commit f286fcc into symfony:master Mar 1, 2017
fabpot added a commit that referenced this pull request Mar 1, 2017
…quired` annotation (nicolas-grekas)

This PR was squashed before being merged into the 3.3-dev branch (closes #21763).

Discussion
----------

[DI] Replace wildcard-based methods autowiring by `@required` annotation

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no (affects things that are only on master)
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

While playing a bit with new features in master around DI configuration, several people around me got bitten by wildcard-based autowiring. The typical example is adding `autowire: [set*]` in `_defaults`: use that on `resource: ../src/Command/` PSR4-based loading and boom, `setApplication` and `setHelperSet` will now be wrongly called. You could tell me "of course, don't to that" - but being bitten so early on a master-only feature makes me really unconfident that this will be easy enough for people after the release.

If wildcard-based autowiring is removed, then I don't see anymore the need for allowing arrays as in `autowire: [setFoo,getBar]`. Moreover, this array syntax has a core DX issue: it's a dead end as far as the learning curve is concerned. You learn it, then when becoming a more advanced dev, someone teaches you that you'd better use another syntax: explicit wiring.

And in fact, we don't need it at all, because something else already exists: just declare a method call, but don't define its arguments. If `autowire: true` is set, then the AutowiringPass already fills in the holes. There is only one tweak required to make this work: don't autowire optional arguments for method calls - or that'd be a BC break. To my PoV that's even better: this makes autowiring fit a "do the minimum to make it work" strategy. A really good one to me.

But there is still an issue: wildcard-based autowiring fits a need. Namely, it allows one to define a convention (eg. `'set*'`), and have all such methods that follow the convention be autowired. To me, this looks like doing it reverse (the DI config should adapt to the code, not reverse). So, to fill this need, let the declaration be in the source: just use an annotation!

This PR adds support for the `@required` annotation, borrowed from the Spring framework:
https://www.tutorialspoint.com/spring/spring_required_annotation.htm

Using the annotation is totally optional of course. If you do, *and if autowiring is on*, then it'll be autowired. If you don't, nothing changes: do manual wiring.

Even when not using autowiring, the annotation is still a nice hint for the consumer of your classes: it tells the reader that this method needs to be called for correct instantiation - thus lowering one drawback of setter injection (discoverability).

The implementation of the annotation parsing is done using a few regexp (no dep on any complex parser) - and works with inheritance, by leveraging the `@inheritdoc` tag (the default behavior being to *not* inherit anything from parent methods).

All in all, looking at the diff stats, it makes everything simpler. Good sign, isn't it?

Commits
-------

f286fcc [DI] Replace wildcard-based methods autowiring by `@required` annotation
9081699 Revert "minor #21315 [DI][FrameworkBundle] Show autowired methods in descriptors (ogizanagi)"
@nicolas-grekas nicolas-grekas deleted the di-required branch March 1, 2017 20:50
@dunglas
Copy link
Member

dunglas commented Mar 1, 2017

Too late but 👍 too, definitely better than set*.

@fabpot
Copy link
Member

fabpot commented Mar 1, 2017

Playing with this now, there is one catch, it does not play well with Doctrine Annotations, which wants to parse everything.

So, we need to add:

\Doctrine\Common\Annotations\AnnotationReader::addGlobalIgnoredName('required');

@fabpot
Copy link
Member

fabpot commented Mar 1, 2017

See symfony/symfony-standard#1052

public function setFoo(Foo $foo)
{
// should be called
}

/** @inheritdoc*/
Copy link
Member

Choose a reason for hiding this comment

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

It lacks a test for a child method with no dock block and a parent method marked with @required.

@kbond
Copy link
Member

kbond commented Mar 23, 2017

Does this mean setter autowiring is no longer possible with ContainerAwareTrait?

@nicolas-grekas
Copy link
Member Author

of course not, nothing changed on that side :)

@nicolas-grekas
Copy link
Member Author

oh sorry, too fast! maybe add the annotation to the trait for such cases? would you like to submit a PR?

@kbond
Copy link
Member

kbond commented Mar 23, 2017

@nicolas-grekas
Copy link
Member Author

Sure, then it's a yes, but I don't see this as a limitation. Use instanceof conditionals instead

@kbond
Copy link
Member

kbond commented Mar 24, 2017

I got it working with _instanceof but I had to have my service implement ContainerAwareInterface.

@nicolas-grekas
Copy link
Member Author

Next step could be implementing ServiceSubscriberInterface :) see #21708

@kbond
Copy link
Member

kbond commented Mar 24, 2017

Haha neat, thanks! This autowiring stuff is moving fast! :)

@fabpot fabpot mentioned this pull request 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.

9 participants