Skip to content

Commit ee95683

Browse files
[DependencyInjection] Allow adding resource tags using any config formats
1 parent 329f0cf commit ee95683

21 files changed

+294
-82
lines changed

src/Symfony/Component/DependencyInjection/Attribute/Autoconfigure.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@
2020
class Autoconfigure
2121
{
2222
/**
23-
* @param array<array-key, array<array-key, mixed>>|string[]|null $tags The tags to add to the service
24-
* @param array<array<mixed>>|null $calls The calls to be made when instantiating the service
25-
* @param array<string, mixed>|null $bind The bindings to declare for the service
26-
* @param bool|string|null $lazy Whether the service is lazy-loaded
27-
* @param bool|null $public Whether to declare the service as public
28-
* @param bool|null $shared Whether to declare the service as shared
29-
* @param bool|null $autowire Whether to declare the service as autowired
30-
* @param array<string, mixed>|null $properties The properties to define when creating the service
31-
* @param array{string, string}|string|null $configurator A PHP function, reference or an array containing a class/reference and a method to call after the service is fully initialized
32-
* @param string|null $constructor The public static method to use to instantiate the service
23+
* @param array<array<mixed>>|string[]|null $tags The tags to add to the service
24+
* @param array<array<mixed>>|null $calls The calls to be made when instantiating the service
25+
* @param array<string, mixed>|null $bind The bindings to declare for the service
26+
* @param bool|string|null $lazy Whether the service is lazy-loaded
27+
* @param bool|null $public Whether to declare the service as public
28+
* @param bool|null $shared Whether to declare the service as shared
29+
* @param bool|null $autowire Whether to declare the service as autowired
30+
* @param array<string, mixed>|null $properties The properties to define when creating the service
31+
* @param array{string, string}|string|null $configurator A PHP function, reference or an array containing a class/reference and a method to call after the service is fully initialized
32+
* @param string|null $constructor The public static method to use to instantiate the service
33+
* @param array<array<mixed>>|string[]|null $resourceTags The resource tags to add to the service
3334
*/
3435
public function __construct(
3536
public ?array $tags = null,
@@ -42,6 +43,7 @@ public function __construct(
4243
public ?array $properties = null,
4344
public array|string|null $configurator = null,
4445
public ?string $constructor = null,
46+
public ?array $resourceTags = null,
4547
) {
4648
}
4749
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Attribute;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*/
17+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
18+
class AutoconfigureResourceTag extends Autoconfigure
19+
{
20+
/**
21+
* @param string|null $name The resource tag name to add
22+
* @param array<mixed> $attributes The attributes to attach to the resource tag
23+
*/
24+
public function __construct(?string $name = null, array $attributes = [])
25+
{
26+
parent::__construct(
27+
resourceTags: [
28+
[$name ?? 0 => $attributes],
29+
]
30+
);
31+
}
32+
}

src/Symfony/Component/DependencyInjection/Attribute/AutoconfigureTag.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
class AutoconfigureTag extends Autoconfigure
2121
{
2222
/**
23-
* @param string|null $name The tag name to add
24-
* @param array<array-key, mixed> $attributes The tag attributes to attach to the tag
23+
* @param string|null $name The tag name to add
24+
* @param array<mixed> $attributes The attributes to attach to the tag
2525
*/
2626
public function __construct(?string $name = null, array $attributes = [])
2727
{

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
7.4
55
---
66

7+
* Allow adding resource tags using any config format
78
* Allow `#[AsAlias]` to be extended
89
* Add argument `$target` to `ContainerBuilder::registerAliasForArgument()`
910
* Deprecate registering a service without a class when its id is a non-existing FQCN

src/Symfony/Component/DependencyInjection/Compiler/RegisterAutoconfigureAttributesPass.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,17 @@ private static function registerForAutoconfiguration(ContainerBuilder $container
7272
self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) {
7373
$attribute = (array) $attribute->newInstance();
7474

75-
foreach ($attribute['tags'] ?? [] as $i => $tag) {
76-
if (\is_array($tag) && [0] === array_keys($tag)) {
77-
$attribute['tags'][$i] = [$class->name => $tag[0]];
75+
foreach (['tags', 'resourceTags'] as $type) {
76+
foreach ($attribute[$type] ?? [] as $i => $tag) {
77+
if (\is_array($tag) && [0] === array_keys($tag)) {
78+
$attribute[$type][$i] = [$class->name => $tag[0]];
79+
}
7880
}
7981
}
82+
if (isset($attribute['resourceTags'])) {
83+
$attribute['resource_tags'] = $attribute['resourceTags'];
84+
}
85+
unset($attribute['resourceTags']);
8086

8187
$parseDefinitions->invoke(
8288
$yamlLoader,

src/Symfony/Component/DependencyInjection/Loader/Configurator/DefaultsConfigurator.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ final public function tag(string $name, array $attributes = []): static
5454
return $this;
5555
}
5656

57+
/**
58+
* Adds a resource tag for this definition.
59+
*
60+
* @return $this
61+
*
62+
* @throws InvalidArgumentException when an invalid tag name or attribute is provided
63+
*/
64+
final public function resourceTag(string $name, array $attributes = []): static
65+
{
66+
if ('' === $name) {
67+
throw new InvalidArgumentException('The resource tag name in "_defaults" must be a non-empty string.');
68+
}
69+
70+
$this->validateAttributes($name, $attributes);
71+
72+
$this->definition->addResourceTag($name, $attributes);
73+
74+
return $this;
75+
}
76+
5777
/**
5878
* Defines an instanceof-conditional to be applied to following service definitions.
5979
*/

src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/TagTrait.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,24 @@ final public function tag(string $name, array $attributes = []): static
3333
return $this;
3434
}
3535

36+
/**
37+
* Adds a resource tag for this definition.
38+
*
39+
* @return $this
40+
*/
41+
final public function resourceTag(string $name, array $attributes = []): static
42+
{
43+
if ('' === $name) {
44+
throw new InvalidArgumentException(\sprintf('The resource tag name for service "%s" must be a non-empty string.', $this->id));
45+
}
46+
47+
$this->validateAttributes($name, $attributes);
48+
49+
$this->definition->addResourceTag($name, $attributes);
50+
51+
return $this;
52+
}
53+
3654
private function validateAttributes(string $tag, array $attributes, array $path = []): void
3755
{
3856
foreach ($attributes as $name => $value) {

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -346,28 +346,31 @@ private function parseDefinition(\DOMElement $service, string $file, Definition
346346
);
347347
}
348348

349-
$tags = $this->getChildren($service, 'tag');
349+
foreach (['tag', 'resource-tag'] as $type) {
350+
foreach ($this->getChildren($service, $type) as $tag) {
351+
$tagNameComesFromAttribute = $tag->childElementCount || '' === $tag->nodeValue;
352+
if ('' === $tagName = $tagNameComesFromAttribute ? $tag->getAttribute('name') : $tag->nodeValue) {
353+
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $service->getAttribute('id'), $file));
354+
}
350355

351-
foreach ($tags as $tag) {
352-
$tagNameComesFromAttribute = $tag->childElementCount || '' === $tag->nodeValue;
353-
if ('' === $tagName = $tagNameComesFromAttribute ? $tag->getAttribute('name') : $tag->nodeValue) {
354-
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $service->getAttribute('id'), $file));
355-
}
356+
$parameters = $this->getTagAttributes($tag, \sprintf('The attribute name of tag "%s" for service "%s" in %s must be a non-empty string.', $tagName, $service->getAttribute('id'), $file));
357+
foreach ($tag->attributes as $name => $node) {
358+
if ($tagNameComesFromAttribute && 'name' === $name) {
359+
continue;
360+
}
356361

357-
$parameters = $this->getTagAttributes($tag, \sprintf('The attribute name of tag "%s" for service "%s" in %s must be a non-empty string.', $tagName, $service->getAttribute('id'), $file));
358-
foreach ($tag->attributes as $name => $node) {
359-
if ($tagNameComesFromAttribute && 'name' === $name) {
360-
continue;
362+
if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
363+
$parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
364+
}
365+
// keep not normalized key
366+
$parameters[$name] = XmlUtils::phpize($node->nodeValue);
361367
}
362368

363-
if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
364-
$parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
365-
}
366-
// keep not normalized key
367-
$parameters[$name] = XmlUtils::phpize($node->nodeValue);
369+
match ($type) {
370+
'tag' => $definition->addTag($tagName, $parameters),
371+
'resource-tag' => $definition->addResourceTag($tagName, $parameters),
372+
};
368373
}
369-
370-
$definition->addTag($tagName, $parameters);
371374
}
372375

373376
$definition->setTags(array_merge_recursive($definition->getTags(), $defaults->getTags()));

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class YamlFileLoader extends FileLoader
5757
'configurator' => 'configurator',
5858
'calls' => 'calls',
5959
'tags' => 'tags',
60+
'resource_tags' => 'resource_tags',
6061
'decorates' => 'decorates',
6162
'decoration_inner_name' => 'decoration_inner_name',
6263
'decoration_priority' => 'decoration_priority',
@@ -83,6 +84,7 @@ class YamlFileLoader extends FileLoader
8384
'configurator' => 'configurator',
8485
'calls' => 'calls',
8586
'tags' => 'tags',
87+
'resource_tags' => 'resource_tags',
8688
'autowire' => 'autowire',
8789
'autoconfigure' => 'autoconfigure',
8890
'bind' => 'bind',
@@ -97,6 +99,7 @@ class YamlFileLoader extends FileLoader
9799
'configurator' => 'configurator',
98100
'calls' => 'calls',
99101
'tags' => 'tags',
102+
'resource_tags' => 'resource_tags',
100103
'autowire' => 'autowire',
101104
'bind' => 'bind',
102105
'constructor' => 'constructor',
@@ -105,6 +108,7 @@ class YamlFileLoader extends FileLoader
105108
private const DEFAULTS_KEYWORDS = [
106109
'public' => 'public',
107110
'tags' => 'tags',
111+
'resource_tags' => 'resource_tags',
108112
'autowire' => 'autowire',
109113
'autoconfigure' => 'autoconfigure',
110114
'bind' => 'bind',
@@ -281,9 +285,13 @@ private function parseDefaults(array &$content, string $file): array
281285
}
282286
}
283287

284-
if (isset($defaults['tags'])) {
285-
if (!\is_array($tags = $defaults['tags'])) {
286-
throw new InvalidArgumentException(\sprintf('Parameter "tags" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file));
288+
foreach (['tags', 'resource_tags'] as $type) {
289+
if (!isset($defaults[$type])) {
290+
continue;
291+
}
292+
293+
if (!\is_array($tags = $defaults[$type])) {
294+
throw new InvalidArgumentException(\sprintf('Parameter "%s" in "_defaults" must be an array in "%s". Check your YAML syntax.', $type, $file));
287295
}
288296

289297
foreach ($tags as $tag) {
@@ -296,7 +304,7 @@ private function parseDefaults(array &$content, string $file): array
296304
$tag = current($tag);
297305
} else {
298306
if (!isset($tag['name'])) {
299-
throw new InvalidArgumentException(\sprintf('A "tags" entry in "_defaults" is missing a "name" key in "%s".', $file));
307+
throw new InvalidArgumentException(\sprintf('A "%s" entry in "_defaults" is missing a "name" key in "%s".', $type, $file));
300308
}
301309
$name = $tag['name'];
302310
unset($tag['name']);
@@ -602,38 +610,43 @@ private function parseDefinition(string $id, array|string|null $service, string
602610
}
603611
}
604612

605-
$tags = $service['tags'] ?? [];
606-
if (!\is_array($tags)) {
607-
throw new InvalidArgumentException(\sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
608-
}
609-
610-
if (isset($defaults['tags'])) {
611-
$tags = array_merge($tags, $defaults['tags']);
612-
}
613+
foreach (['tags', 'resource_tags'] as $type) {
614+
$tags = $service[$type] ?? [];
615+
if (!\is_array($tags)) {
616+
throw new InvalidArgumentException(\sprintf('Parameter "%s" must be an array for service "%s" in "%s". Check your YAML syntax.', $type, $id, $file));
617+
}
613618

614-
foreach ($tags as $tag) {
615-
if (!\is_array($tag)) {
616-
$tag = ['name' => $tag];
619+
if (isset($defaults[$type])) {
620+
$tags = array_merge($tags, $defaults[$type]);
617621
}
618622

619-
if (1 === \count($tag) && \is_array(current($tag))) {
620-
$name = key($tag);
621-
$tag = current($tag);
622-
} else {
623-
if (!isset($tag['name'])) {
624-
throw new InvalidArgumentException(\sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file));
623+
foreach ($tags as $tag) {
624+
if (!\is_array($tag)) {
625+
$tag = ['name' => $tag];
625626
}
626-
$name = $tag['name'];
627-
unset($tag['name']);
628-
}
629627

630-
if (!\is_string($name) || '' === $name) {
631-
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file));
632-
}
628+
if (1 === \count($tag) && \is_array(current($tag))) {
629+
$name = key($tag);
630+
$tag = current($tag);
631+
} else {
632+
if (!isset($tag['name'])) {
633+
throw new InvalidArgumentException(\sprintf('A "%s" entry is missing a "name" key for service "%s" in "%s".', $type, $id, $file));
634+
}
635+
$name = $tag['name'];
636+
unset($tag['name']);
637+
}
638+
639+
if (!\is_string($name) || '' === $name) {
640+
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file));
641+
}
633642

634-
$this->validateAttributes(\sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, '%s', $file), $tag);
643+
$this->validateAttributes(\sprintf('A "%s" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, '%s', $type, $file), $tag);
635644

636-
$definition->addTag($name, $tag);
645+
match ($type) {
646+
'tags' => $definition->addTag($name, $tag),
647+
'resource_tags' => $definition->addResourceTag($name, $tag),
648+
};
649+
}
637650
}
638651

639652
if (null !== $decorates = $service['decorates'] ?? null) {

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@
151151
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
152152
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
153153
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
154+
<xsd:element name="resource-tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
154155
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
155156
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
156157
</xsd:choice>
@@ -197,6 +198,7 @@
197198
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
198199
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
199200
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
201+
<xsd:element name="resource-tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
200202
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
201203
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
202204
<xsd:element name="exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

0 commit comments

Comments
 (0)