-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Reference tagged services in config #22200
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
you don't have a XML syntax for this |
Also no tests.. it's a proof of concept :) but will do that.. wanted to propose it first. Perhaps introduces a few things to discuss...
I think the latter is actually an important one... |
Tagged scalars won't be supported in yaml before 4.0. So you can either use a different syntax, or you can whitelist just one tag using: diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php
index e8d5faa..82112f0 100644
--- a/src/Symfony/Component/Yaml/Inline.php
+++ b/src/Symfony/Component/Yaml/Inline.php
@@ -710,8 +710,10 @@ class Inline
// Is followed by a scalar
if (!isset($value[$nextOffset]) || !in_array($value[$nextOffset], array('[', '{'), true)) {
- // Manage scalars in {@link self::evaluateScalar()}
- return;
+ if (!in_array($tag, $whitelist = array('foo'))) {
+ // Manage scalars in {@link self::evaluateScalar()}
+ return;
+ }
}
// Built-in tags
diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php
index 7b572e1..bbb1531 100644
--- a/src/Symfony/Component/Yaml/Parser.php
+++ b/src/Symfony/Component/Yaml/Parser.php
@@ -642,6 +642,8 @@ class Parser
if ('' !== $matches['tag']) {
if ('!!binary' === $matches['tag']) {
return Inline::evaluateBinaryScalar($data);
+ } elseif (in_array($matches['tag'], $whitelist = array('!foo'))) {
+ return new TaggedValue(substr($matches['tag'], 1), $data);
} elseif ('!' !== $matches['tag']) {
@trigger_error(sprintf('Using the custom tag "%s" for the value "%s" is deprecated since version 3.3. It will be replaced by an instance of %s in 4.0.', $matches['tag'], $data, TaggedValue::class), E_USER_DEPRECATED);
} |
For XML, the syntax could be: <service>
<argument type="tagged-services" tag="foo" />
</service> |
Above works currently (tagged scalar on a new line), so the syntax only improves as of 4.0. perhaps to compromise it could allow |
Little update from my side;
Open for any cosmetic changes / better naming etc :) |
@@ -57,6 +57,7 @@ public function __construct() | |||
new CheckDefinitionValidityPass(), | |||
new RegisterServiceSubscribersPass(), | |||
new ResolveNamedArgumentsPass(), | |||
new ResolveTaggedIteratorArgumentPass(), |
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.
not sure about the best order here =/
tagged_iterator: | ||
class: Bar | ||
arguments: | ||
- !tagged_iterator foo |
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.
@GuilhemN looks like the yaml dumper is dumping a deprecated format :-)
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.
Again, you have to do #22200 (comment) ;)
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.
Right.. :) however since
foo: !bar
baz
bypasses the deprecation i assumed we should dump like that in general for tagged scalars. But im not sure how its done inline =/
Could that be used to replace any existing passes? If yes, that'd be a good sign of the usefulness of it and should be done in the same PR if possible. |
Technically yes. Now the priority attribute is handled it could replace some 🤞 Will look into that. edit: from https://symfony.com/doc/current/service_container/tags.html looks like |
Furthermore if we ever allow to map a |
@@ -505,6 +506,12 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $lowercase = | |||
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file)); | |||
} | |||
break; | |||
case 'tagged-iterator': |
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 personally prefered tagged-services
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.
See #22649 (comment) how i came up with !tagged_iterator
.
A !tagged_map
could leverage keys by doing $attr['alias'] ?? $definition->getClass()
which could replace more core passes.
@nicolas-grekas would it make sense to merge That would end the iterator vs map thingy. And allows this PR to move forward with a single
I guess the problem is most rely on array API, so the auto expansion could actually help here to satisfy those and have easier migration. Also thinks like https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml#L59 can benefit i guess. |
Dunno, we could try... :) |
See https://github.com/ro0NL/symfony/commit/1360bab6154c9d29d27d42a9011a9300c8048afd for where im leaning to.. Basically i dont understand why we favor
over (simply)
What edit: if im correct it creates a new def+alias only to be inlined again afterwards.. lost me :) |
@nicolas-grekas final proposal; im leaning to roll out my own yaml tag(s) otherwise. Looking at my own usecases i could definitely use a tagged iterator as well as a tagged service locator *. We can keep leveraging iterator arg and simply dont support tagged service locators, but to me thats half the feature. Not sure if worth it then. So we could do *) Problem with tagged service locator is we cant get the full map, it's not iterable nor exposes getKeys. Which may be limiting, and therefor im not sure whats best way to introduce it in core; hence im leaning to roll out my own and keep things as is in core. |
If you know the name of the tagged services, you don't need to tag them. |
Yes, so true :) we dont need this 👍
But there's no key control, you'll just get numeric indexes. But lets forget about maps here ;-) So, as is, we can at least cover Tags like security.voter are a bit more difficult. And things like commands cannot even be done this way i guess. So it needs a per case decision. For end users it may fit many more cases though. |
I wouldn't recommend doing that :)
Let's see how it goes then |
@nicolas-grekas this is hard :) and im not sure about the best approach. I think there are 2 viable directions, given method($mixed, array $array, iterable $iterable, ...$variadic) Variant A - type based: '$mixed': !tagged-services tag # pass a lazy iterable
'$array': !tagged-services tag # pass an array
'$iterable': !tagged-services tag # pass a lazy iterable
'$variadic': !tagged-services tag # pass a service + append subsequent services as arg Variadic for numeric arg doesnt resolve subsequent arguments, as it implies subsequent args come from config (though not sure), i.e; - [mixed]
- [array]
- [iterable]
- !tagged-services tag # pass a lazy iterable
- x # pass scalar for subsequent variadic Variant B - tag based: '$mixed': !tagged-services tag # pass an array
'$array': !tagged-services tag # pass an array
'$iterable': !tagged-services tag # pass an array
'$iterable': !iterator { values: !tagged-services tag } # pass a lazy iterable
'$variadic': !variadic { values: !tagged-services tag } # pass a service + append subsequent services as arg I guess this variant allows for the same approach between numeric and named args. So im leaning to tag based, but it requires refactoring here and there. Though type based looks nice.. i think explicitness is the way to go. Basically we introduce a little DSL with |
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.
Straightforward, nice :)
<tag name="foo"/> | ||
</service> | ||
<service id="tagged_iterator" class="Bar"> | ||
<argument type="tagged" tag="foo"/> |
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.
xml syntax
tagged_iterator: | ||
class: Bar | ||
arguments: | ||
- !tagged foo |
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.
yaml syntax
@@ -774,8 +774,8 @@ private static function parseTag($value, &$i, $flags) | |||
$nextOffset += strspn($value, ' ', $nextOffset); | |||
|
|||
// Is followed by a scalar | |||
if (!isset($value[$nextOffset]) || !in_array($value[$nextOffset], array('[', '{'), true)) { | |||
// Manage scalars in {@link self::evaluateScalar()} | |||
if ((!isset($value[$nextOffset]) || !in_array($value[$nextOffset], array('[', '{'), true)) && 'tagged' !== $tag) { |
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.
opting out from a deprecation in the specific required case (!tagged foo
was a string before this, not yaml compliant so deprecated anyway)
@ro0NL could you rebase please? |
Done. edit: looking at tests. |
Status: needs work. Cant get tests to work. Need some help to both fix
|
Now green fabbot failures are false-positives. |
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.
👍
Thank you @ro0NL. |
This PR was merged into the 3.4 branch. Discussion ---------- [DI] Reference tagged services in config | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #12269 | License | MIT | Doc PR | symfony/symfony-docs#8404 This is a proof of concept to reference a sequence of tagged services. The problem bugs me for some time, and at first i thought the solution was to have some super generic compiler pass. If it could replace a lot of compilers in core.. perhaps worth it, but eventually each tag comes with it's own logic, including how to deal with tag attributes. However, writing the passes over and over again becomes tedious for the most basic usecase. So given the recent developments, this idea came to mind. ```yml services: a: class: stdClass properties: { a: true } tags: [foo] b: class: stdClass properties: { b: true } tags: [foo] c: class: stdClass properties: #stds: !tagged_services foo (see #22198) stds: !tagged_services foo ``` ``` dump(iterator_to_array($this->get('c')->stds)); ``` ``` array:2 [▼ 0 => {#5052 ▼ +"a": true } 1 => {#4667 ▼ +"b": true } ] ``` Given the _basic_ example at https://symfony.com/doc/current/service_container/tags.html, this could replace that. Any thoughts? Commits ------- 979e58f [DI] Reference tagged services in config
This PR was merged into the 4.0-dev branch. Discussion ---------- [DependencyInjection] fix tests | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | adapt tests from #22200 | License | MIT | Doc PR | Commits ------- 08deb37 [DependencyInjection] fix tests
@ro0NL @nicolas-grekas with this PR is it possible to use an alias as array index? |
Nope :) just a sequence collection. We had a long debate about that. You might infer alias/key from |
@ro0NL we from sulu (https://github.com/sulu/sulu) uses the tagged services and also aliased because we want to allow overriding this services. so this would be an awesome additional feature for us |
Well done @ro0NL! :) |
@@ -61,6 +61,7 @@ public function __construct() | |||
new AutowireRequiredMethodsPass(), | |||
new ResolveBindingsPass(), | |||
new AutowirePass(false), | |||
new ResolveTaggedIteratorArgumentPass(), |
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.
this should be before the DecoratorServicePass, so that tagging a service and then decorating it creates the reference with the original id (and so gets the decorated service rather than the inner one). It should be at the same place than the ServiceLocatorTagPass (which is here for the same reason)
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.
@stof debugged this real quick, and AFAIK for a decorated service we already inherit tags via ResolveChildDefinitionsPass
, which runs sooner.
Thus ContainerBuilder::findTaggedServiceIds()
returns expected service id's already. Am i missing something?
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.
child definitions are not about decorated services. Child definitions are about service definition inheritance (which is precisely why we renamed DecoratorDefinition to ChildDefinition as the previous name was totally misleading, especially after we implemented service decoration)
@ro0NL to clarify, can we use variadic's then? |
nope, it injects an iterator. Variadics argument would not be compatible with IteratorArgument's lazy-loading. |
Thanks for the clarification and explanation. |
…javiereguiluz) This PR was merged into the 3.4 branch. Discussion ---------- Docs for referencing tagged services in config See symfony/symfony#22200 Curious how it looks :) also a bit related to #8403 Commits ------- ed70659 Update tags.rst 2e5c87f Update tags.rst 564b5ea Update tags.rst a2fd23f Update tags.rst 000b801 Minor reword and fixes 0aaf48b Update tags.rst 71158f8 Update tags.rst 61c74da Update tags.rst da034d2 Docs for referencing tagged services in config
This is a proof of concept to reference a sequence of tagged services.
The problem bugs me for some time, and at first i thought the solution was to have some super generic compiler pass. If it could replace a lot of compilers in core.. perhaps worth it, but eventually each tag comes with it's own logic, including how to deal with tag attributes.
However, writing the passes over and over again becomes tedious for the most basic usecase. So given the recent developments, this idea came to mind.
Given the basic example at https://symfony.com/doc/current/service_container/tags.html, this could replace that.
Any thoughts?