-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Add support for "wither" methods - for greater immutable services #30212
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
Conversation
33bd906
to
0f5a1c8
Compare
src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php
Outdated
Show resolved
Hide resolved
The third parameter does not feel as explicit as it should. On the other hand is the following format a bit overkill: services:
MyService:
with:
- [withLogger, '@logger'] Did you think about some other formats as well? |
Its ok to access private members as public ones? Sounds weird to me. Its ok this approach in this context? |
0f5a1c8
to
82a2d2f
Compare
None that clicked - that's the most simple I could think of - like for you, "with" doesn't click. I'm good with the 3rd arg - withers already start with "with" and that's enough maybe.
of course it is, that's pretty common for this and similar cases. |
src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
Show resolved
Hide resolved
src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
Outdated
Show resolved
Hide resolved
services:
MyService:
calls:
- [withLogger, '@logger', true] It does not look like this works. The second array element needs to be an array of arguments ( So it needs to be written in the following way which is also alot more readable: services:
MyService:
calls:
-
method: withLogger
arguments: ['@logger']
use_result: true I would go so far that we should think about deprecating the yaml syntax with array list in favour of named properties (#13892) |
4ce5f60
to
1e41685
Compare
Thanks @Tobion and everyone, comments addressed, PR ready.
that's too far for me :) but that can be discussed in a RFC if you want to. |
Shouldn't there be a check that the "wither" method actually returns an instance of the same class ? What prevents people from returning something else ? |
That's not the job of a DI container, eg we don't check you won't inject a logger where an event dispatcher is expected. That's a job for the language runtime, or a separate compiler pass / inspection process. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this locally and works very well.
@rvanlaak Registering withers separately from other calls would have a big drawback: if you have both |
@@ -136,15 +136,27 @@ protected function processValue($value, $isRoot = false) | |||
} | |||
$this->lazy = false; | |||
|
|||
// Any calls before a "wither" are part of the constructor-instantiation graph |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't any call to a non-wither performed before a wither call also part of it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some tests covering mixing wither calls with non-wither calls (in different orders) to make sure this works right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would argue that non-wither calls should happen after wither calls, on the off chance that some calls register themselves with other services and the object hashes differ and causes unintended problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I would say that mixing things are an edge case indeed. But the current configuration syntax supports it, and so we should deal with it properly.
The code currently already respects the order of method calls, be them withers or no, when instantiating the service. Only this place is missing that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't any call to a non-wither performed before a wither call also part of it ?
sure, that'swhat I mean by "any calls" - regular setters or withers
non-wither calls should happen after wither calls
why not, but not something I would make a "must"
src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither.php
Outdated
Show resolved
Hide resolved
bb5c76d
to
4c6ad1e
Compare
@stof thanks for the review, issue addressed. Statuts: needs review |
eeb92bf
to
12787bf
Compare
friendly ping @symfony/deciders |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. Not a big fan of use-result
though. use-returned-value
would be more explicit IMHO, but perhaps a bit verbose. What about use-returned
? Or anyone with a better idea?
use-result? use-returned-value? use-returned? |
12787bf
to
f455d1b
Compare
PR and description updated to use |
Thank you @nicolas-grekas. |
…mutable services (nicolas-grekas) This PR was merged into the 4.3-dev branch. Discussion ---------- [DI] Add support for "wither" methods - for greater immutable services | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | symfony/symfony-docs#10991 Let's say we want to define an immutable service while still using traits for composing its optional features. A nice way to do so without hitting [the downsides of setters](https://symfony.com/doc/current/service_container/injection_types.html#setter-injection) is to use withers. Here would be an example: ```php class MyService { use LoggerAwareTrait; } trait LoggerAwareTrait { private $logger; /** * @required * @return static */ public function withLogger(LoggerInterface $logger) { $new = clone $this; $new->logger = $logger; return $new; } } $service = new MyService(); $service = $service->withLogger($logger); ``` As you can see, this nicely solves the setter issues. BUT how do you make the service container create such a service? Right now, you need to resort to complex gymnastic using the "factory" setting - manageable for only one wither, but definitely not when more are involved and not compatible with autowiring. So here we are: this PR allows configuring such services seamlessly. Using explicit configuration, it adds a 3rd parameter to method calls configuration: after the method name and its parameters, you can pass `true` and done, you just declared a wither: ```yaml services: MyService: calls: - [withLogger, ['@logger'], true] ``` In XML, you could use the new `returns-clone` attribute on the `<call>` tag. And when using autowiring, the code looks for the `@return static` annotation and turns the flag on if found. There is only one limitation: unlike services with regular setters, services with withers cannot be part of circular loops that involve calls to wither methods (unless they're declared lazy of course). Commits ------- f455d1b [DI] Add support for "wither" methods - for greater immutable services
This PR was merged into the 4.3 branch. Discussion ---------- [DIC] Static injection Hi everyone, As discussed with @nicolas-grekas, the DIC is planned to be capable of ensuring services immutability (using "wither" calls), the implementation can be found here: - symfony/symfony#30212 Commits ------- a598bc0 feat(DI): static injection
Let's say we want to define an immutable service while still using traits for composing its optional features. A nice way to do so without hitting the downsides of setters is to use withers. Here would be an example:
As you can see, this nicely solves the setter issues.
BUT how do you make the service container create such a service? Right now, you need to resort to complex gymnastic using the "factory" setting - manageable for only one wither, but definitely not when more are involved and not compatible with autowiring.
So here we are: this PR allows configuring such services seamlessly.
Using explicit configuration, it adds a 3rd parameter to method calls configuration: after the method name and its parameters, you can pass
true
and done, you just declared a wither:In XML, you could use the new
returns-clone
attribute on the<call>
tag.And when using autowiring, the code looks for the
@return static
annotation and turns the flag on if found.There is only one limitation: unlike services with regular setters, services with withers cannot be part of circular loops that involve calls to wither methods (unless they're declared lazy of course).