Skip to content

[DependencyInjection] Introduce a flag to enable setter autowiring #19631

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
wants to merge 3 commits into from

Conversation

dunglas
Copy link
Member

@dunglas dunglas commented Aug 16, 2016

Q A
Branch? master
Bug fix? maybe
New feature? yes
BC breaks? no (feature not released at this time)
Deprecations? no
Tests pass? yes
Fixed tickets n/a
License MIT
Doc PR todo

This PR follows a discussion with @Ocramius: https://twitter.com/Ocramius/status/754630452045029376

It makes setter injection opt-in.

ping @weaverryan

@Ocramius
Copy link
Contributor

Looks good to me! 👍

@ro0NL
Copy link
Contributor

ro0NL commented Aug 16, 2016

What about allowing a collection of setters to autowire;
setSettersAutowired(['setBar', 'setBaz']); // but not setFoo()?

@dunglas
Copy link
Member Author

dunglas commented Aug 16, 2016

@ro0NL I don't think there is much benefit compared to the existing

        calls:
            - [setMailer, ['@app.mailer']]

@Ocramius
Copy link
Contributor

Ocramius commented Aug 16, 2016

To clarify on why this change is needed, enabling auto-wiring without this flag could cause a BC break. Given following example service:

class Foo
{
    private $baz;
    public function __construct(Bar $bar)
    {
        $this->baz = $bar;
    }

    public function setBaz(Taz $taz)
    {
        $this->baz = $taz;
    }
}

Turning auto-wiring on in earlier DependencyInjection versions calls __construct.

Current master calls __construct and then setBaz (the risk is injecting a different instance here).

This PR reverts the behavior to just __construct, while setBaz is to be enabled by the consumer.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 16, 2016

@dunglas guess so, it's considerable though. There will be situations you want to ideally autowire 9/10 methods and face a all or nothing choice: go with autowiring or configure 9 services. We fully lose the autowiring benefit then.. which can be a, pragmatic, pain :-)

@Ocramius
Copy link
Contributor

which can be a, pragmatic, pain :-)

It's still out of the 80/20 use-case, from the looks of it.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 16, 2016

Agree :)

edit: consider

calls:
  - [setBar, [@arg]]
  - setFoo # autowired

@ro0NL
Copy link
Contributor

ro0NL commented Aug 16, 2016

Yeah, i think i like

autowire: true
[autowire_]calls:
   - setBar
   - setFoo

better than

setters_autowired: true

Or setters_autowired: true should be an equivalent of calls: <all_setters>

@nicolas-grekas
Copy link
Member

👍

1 similar comment
@fabpot
Copy link
Member

fabpot commented Aug 16, 2016

👍

@chalasr
Copy link
Member

chalasr commented Aug 16, 2016

Should we consider this for inheritance? At least make children inherit the flag and maybe allow them to change it? Actually doing it afterwards for the autowire flag in #19643.

👍

@weaverryan
Copy link
Member

I'm worried about usability. If you want full autowiring, it'll now be:

autowire: true
autowire_setters: true

That's a lot of work for something that's purely for rapid development. But, we need to keep BC also.

What about this suggestion, which includes part of @ro0NL's idea:

# A) deprecated, but would only autowire the constructor, allows BC
autowire: true

# B) would autowire everything, including setters
autowiring: true

# C) or, use an array to opt into only certain methods
autowiring: [__construct]

This is also - accidentally - more flexible.

@chalasr
Copy link
Member

chalasr commented Aug 17, 2016

Even more flexible, IMHO having to list the methods to autowire would indeed reduce the benefit of using this feature rather than fully fill calls, as pointed out by @dunglas.

The proposed flag sounds nice, keeping autowire managing only constructors per default which stay logic to me.

Otherwise, what about a choice?

autowiring: constructor
autowiring: setters
autowiring: [constructor, setters]

(Depreciating the current flag)

@Ocramius
Copy link
Contributor

@weaverryan regarding usability, this is a reminder that setter injection is generally harmful, and while it's a nice-to-have, it's usually to be discouraged.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 17, 2016

this is a reminder that setter injection is generally harmful

This, imo, explains why it should be verbose. autowire_setters: true #magic happens

edit: i rather be explicit in what methods are called (autowired or not). The shortcut (true) will be the nice-to-have imo.

@dunglas
Copy link
Member Author

dunglas commented Aug 17, 2016

I also agree with @Ocramius, we should discourage setter injection (it should be opt-in).

I added initial support for setter autowiring (#17608) to ease the creation of Symfony controllers that don't depend of the container (#18193). Such classes will basically be automatically registered (e.g.: DunglasActionBundle), the user will not have to care about enabling setters autowiring by itself.

Developpers wanting to always enable setters autowiring in other contexts can create a userland a compiler pass to do it.

@weaverryan
Copy link
Member

this is a reminder that setter injection is generally harmful

Is it harmful? The user is already opting into magic. I'm not saying this is wrong, but I want to challenge the statement. Is there some evidence we can look at to support / contest this?

I added initial support for setter autowiring (#17608) to ease the creation of Symfony controllers that don't depend of the container

I'm interested in setter autowiring because of the potential to have traits with setters and helper methods - e.g. RouterTrait with setRouter and generateUrl methods.

My proposal (#19631 (comment)) offers BC, ease-of-use and control... but assumes the best default mode for autowiring is constructors and setters.

@Ocramius
Copy link
Contributor

Ocramius commented Aug 17, 2016

Is it harmful?

Yes, due to basic design of service dependency immutability.

The user is already opting into magic.

The comment about setter injection can be taken completely out of the scope of this PR or of the dependency injection component.

@dunglas
Copy link
Member Author

dunglas commented Aug 17, 2016

I'm interested in setter autowiring because of the potential to have traits with setters and helper methods - e.g. RouterTrait with setRouter and generateUrl methods.

Yes, it's basically the same use case. But for such cases, a compiler pass can automatically enable setters autowiring (for instance if the service implements a given interface).

@weaverryan
Copy link
Member

Ok, I'm getting out-voted, and the arguments are sound.

👍 for the current approach

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

Just to clarify; this will call any defined set..() method, after you configured autowire_settings: true, right/?. I can live with it, no problem, but I hope you understand my concern why that's tricky and bad DX.

IMO, it's the same trickiness what we currently try to avoid.

@dunglas
Copy link
Member Author

dunglas commented Aug 18, 2016

@ro0NL any applicable setter. See #17608 and tests for details.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

I see. How does this solve optional dependencies? I don't want setFoo(Bar $bar = null) to be called... im stuck?

Point is: you need to know at all time, whenever you define a setter, on whatever level in the class-chain, it's gonna be called (if enabled). It's tricky, period. :)

edit: i understand it's somewhat the current behavior, hence i can live with this approach. Just saying ;-)

@dunglas
Copy link
Member Author

dunglas commented Aug 18, 2016

What do you think about the following config, it should satisfy everybody's needs and is backward compatible:

autowire: true # constructor autowiring
autowire: [__construct, setFoo, setBar] # autowire whitelisted methods only
autowire: '*' # autowire constructor + every setters (following existing rules for setters autowiring)

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

What about

autowire: true
# enables constructor autowiring by default, if disabled no autowiring whatsoever happens

autowire_calls: [autowireThis, andThat]
# additional method calls to be autowired

autowire_setters: true
# adds all setters to autowire_calls

This looks intuitive.. but is limiting in a way, you can only autowire method calls when the constructor is autowired.

@nicolas-grekas
Copy link
Member

I prefer @dunglas proposal because the various configurations are mutually exclusive, but having several keys allows setting several of them at once, and from a DX perspective requires remembering 3 keys.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

Okay then.. let's do it :) Thanks for letting me clarify my point.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

@dunglas just thought of using bitwise flags, on php level it's pretty verbose;

$definition->setAutowired(Definition::CONSTRUCTOR|Definition::SETTERS);
// true = Definition::CONSTRUCTOR
// array = whitelist
if ($definition->getAutowired() & Definition::SETTERS) { }

For yaml Definition::FULL could work (CONSTRUCTOR|SETTERS), ie;

autowire: !php/const:Definition::FULL

or allow the * shortcut anyway.

@dunglas
Copy link
Member Author

dunglas commented Aug 18, 2016

autowire: !php/const:Definition::FULL is a no go for me.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

Understand, hence i proposed allowing the * shortcut. It's more about the PHP structure for this. The * can be allowed for file loaders, or directly in Definition::setAutowired.

@dunglas
Copy link
Member Author

dunglas commented Aug 18, 2016

My proposal is more flexible and easier to learn (though)

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

Easier to learn, i guess so ;-) my proposal allows for adding Definition::SOME_FUTURE_GROUP_OF_METHODS, which will never happen.. but if you wanna talk about flexibility; it's their).

Anyway, lets move on. true|array|"*" is 👍

@stof
Copy link
Member

stof commented Aug 18, 2016

@ro0NL your proposal shows the wrong Yaml snippet. It would be like this:

autowire: !php/const:Symfony\Component\DependencyInjection\Definition::FULL

@ro0NL
Copy link
Contributor

ro0NL commented Aug 18, 2016

Left that out intentionally 👼 i know it's a no-go in some ways (too verbose i guess), but im looking at this from the php side first.

@nicolas-grekas
Copy link
Member

Status: needs work

@chalasr
Copy link
Member

chalasr commented Sep 26, 2016

As said, it would be a BC break to don't get this merged for 3.2.
@dunglas Are you fine or do you want some help?

@dunglas
Copy link
Member Author

dunglas commented Sep 26, 2016

I'll do it for the 3.2 release. If it's not ready, we'll revert the introduction of setter autowiring (and reintroduce it in 3.3) but I'll find the time to finish this PR.

@dunglas
Copy link
Member Author

dunglas commented Oct 5, 2016

I opened a new PR implementing #19631 (comment): #20167

@dunglas dunglas closed this Oct 5, 2016
fabpot added a commit that referenced this pull request Dec 13, 2016
…configurable (dunglas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[DependencyInjection] Make method (setter) autowiring configurable

| Q | A |
| --- | --- |
| Branch? | master |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | maybe? |
| Tests pass? | yes |
| Fixed tickets | #19631 |
| License | MIT |
| Doc PR | symfony/symfony-docs#7041 |

Follow up of #19631. Implements #19631 (comment):

Edit: the last supported format:

``` yaml
services:
    foo:
        class: Foo
        autowire: ['__construct', 'set*'] # Autowire constructor and all setters
        autowire: true # Converted by loaders in `autowire: ['__construct']` for BC
        autowire: ['foo', 'bar'] # Autowire only `foo` and `bar` methods
```

Outdated:

``` yaml
autowire: true # constructor autowiring
autowire: [__construct, setFoo, setBar] # autowire whitelisted methods only
autowire: '*' # autowire constructor + every setters (following existing rules for setters autowiring)
```
- [x] Allow to specify the list of methods in the XML loader
- [x] Add tests for the YAML loader

Commits
-------

6dd53c7 [DependencyInjection] Introduce method injection for autowiring
symfony-splitter pushed a commit to symfony/dependency-injection that referenced this pull request Dec 13, 2016
…configurable (dunglas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[DependencyInjection] Make method (setter) autowiring configurable

| Q | A |
| --- | --- |
| Branch? | master |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | maybe? |
| Tests pass? | yes |
| Fixed tickets | #19631 |
| License | MIT |
| Doc PR | symfony/symfony-docs#7041 |

Follow up of #19631. Implements symfony/symfony#19631 (comment):

Edit: the last supported format:

``` yaml
services:
    foo:
        class: Foo
        autowire: ['__construct', 'set*'] # Autowire constructor and all setters
        autowire: true # Converted by loaders in `autowire: ['__construct']` for BC
        autowire: ['foo', 'bar'] # Autowire only `foo` and `bar` methods
```

Outdated:

``` yaml
autowire: true # constructor autowiring
autowire: [__construct, setFoo, setBar] # autowire whitelisted methods only
autowire: '*' # autowire constructor + every setters (following existing rules for setters autowiring)
```
- [x] Allow to specify the list of methods in the XML loader
- [x] Add tests for the YAML loader

Commits
-------

6dd53c7 [DependencyInjection] Introduce method injection for autowiring
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