Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@
class Autoconfigure
{
/**
* @param array<array-key, array<array-key, mixed>>|string[]|null $tags The tags to add to the service
* @param array<array<mixed>>|null $calls The calls to be made when instantiating the service
* @param array<string, mixed>|null $bind The bindings to declare for the service
* @param bool|string|null $lazy Whether the service is lazy-loaded
* @param bool|null $public Whether to declare the service as public
* @param bool|null $shared Whether to declare the service as shared
* @param bool|null $autowire Whether to declare the service as autowired
* @param array<string, mixed>|null $properties The properties to define when creating the service
* @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
* @param string|null $constructor The public static method to use to instantiate the service
* @param array<array<mixed>>|string[]|null $tags The tags to add to the service
* @param array<array<mixed>>|null $calls The calls to be made when instantiating the service
* @param array<string, mixed>|null $bind The bindings to declare for the service
* @param bool|string|null $lazy Whether the service is lazy-loaded
* @param bool|null $public Whether to declare the service as public
* @param bool|null $shared Whether to declare the service as shared
* @param bool|null $autowire Whether to declare the service as autowired
* @param array<string, mixed>|null $properties The properties to define when creating the service
* @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
* @param string|null $constructor The public static method to use to instantiate the service
* @param array<array<mixed>>|string[]|null $resourceTags The resource tags to add to the service
*/
public function __construct(
public ?array $tags = null,
Expand All @@ -42,6 +43,7 @@ public function __construct(
public ?array $properties = null,
public array|string|null $configurator = null,
public ?string $constructor = null,
public ?array $resourceTags = null,
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Attribute;

/**
* @author Nicolas Grekas <p@tchwork.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class AutoconfigureResourceTag extends Autoconfigure
{
/**
* @param string|null $name The resource tag name to add
* @param array<mixed> $attributes The attributes to attach to the resource tag
*/
public function __construct(?string $name = null, array $attributes = [])
{
parent::__construct(
resourceTags: [
[$name ?? 0 => $attributes],
]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
class AutoconfigureTag extends Autoconfigure
{
/**
* @param string|null $name The tag name to add
* @param array<array-key, mixed> $attributes The tag attributes to attach to the tag
* @param string|null $name The tag name to add
* @param array<mixed> $attributes The attributes to attach to the tag
*/
public function __construct(?string $name = null, array $attributes = [])
{
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
7.4
---

* Allow adding resource tags using any config format
* Allow `#[AsAlias]` to be extended
* Add argument `$target` to `ContainerBuilder::registerAliasForArgument()`
* Deprecate registering a service without a class when its id is a non-existing FQCN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,17 @@ private static function registerForAutoconfiguration(ContainerBuilder $container
self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) {
$attribute = (array) $attribute->newInstance();

foreach ($attribute['tags'] ?? [] as $i => $tag) {
if (\is_array($tag) && [0] === array_keys($tag)) {
$attribute['tags'][$i] = [$class->name => $tag[0]];
foreach (['tags', 'resourceTags'] as $type) {
foreach ($attribute[$type] ?? [] as $i => $tag) {
if (\is_array($tag) && [0] === array_keys($tag)) {
$attribute[$type][$i] = [$class->name => $tag[0]];
}
}
}
if (isset($attribute['resourceTags'])) {
$attribute['resource_tags'] = $attribute['resourceTags'];
}
unset($attribute['resourceTags']);

$parseDefinitions->invoke(
$yamlLoader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ final public function tag(string $name, array $attributes = []): static
return $this;
}

/**
* Adds a resource tag for this definition.
*
* @return $this
*
* @throws InvalidArgumentException when an invalid tag name or attribute is provided
*/
final public function resourceTag(string $name, array $attributes = []): static
{
if ('' === $name) {
throw new InvalidArgumentException('The resource tag name in "_defaults" must be a non-empty string.');
}

$this->validateAttributes($name, $attributes);

$this->definition->addResourceTag($name, $attributes);

return $this;
}

/**
* Defines an instanceof-conditional to be applied to following service definitions.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ final public function tag(string $name, array $attributes = []): static
return $this;
}

/**
* Adds a resource tag for this definition.
*
* @return $this
*/
final public function resourceTag(string $name, array $attributes = []): static
{
if ('' === $name) {
throw new InvalidArgumentException(\sprintf('The resource tag name for service "%s" must be a non-empty string.', $this->id));
}

$this->validateAttributes($name, $attributes);

$this->definition->addResourceTag($name, $attributes);

return $this;
}

private function validateAttributes(string $tag, array $attributes, array $path = []): void
{
foreach ($attributes as $name => $value) {
Expand Down
37 changes: 20 additions & 17 deletions src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -346,28 +346,31 @@ private function parseDefinition(\DOMElement $service, string $file, Definition
);
}

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

foreach ($tags as $tag) {
$tagNameComesFromAttribute = $tag->childElementCount || '' === $tag->nodeValue;
if ('' === $tagName = $tagNameComesFromAttribute ? $tag->getAttribute('name') : $tag->nodeValue) {
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $service->getAttribute('id'), $file));
}
$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));
foreach ($tag->attributes as $name => $node) {
if ($tagNameComesFromAttribute && 'name' === $name) {
continue;
}

$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));
foreach ($tag->attributes as $name => $node) {
if ($tagNameComesFromAttribute && 'name' === $name) {
continue;
if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
$parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
}
// keep not normalized key
$parameters[$name] = XmlUtils::phpize($node->nodeValue);
}

if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
$parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
}
// keep not normalized key
$parameters[$name] = XmlUtils::phpize($node->nodeValue);
match ($type) {
'tag' => $definition->addTag($tagName, $parameters),
'resource-tag' => $definition->addResourceTag($tagName, $parameters),
};
}

$definition->addTag($tagName, $parameters);
}

$definition->setTags(array_merge_recursive($definition->getTags(), $defaults->getTags()));
Expand Down
71 changes: 42 additions & 29 deletions src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class YamlFileLoader extends FileLoader
'configurator' => 'configurator',
'calls' => 'calls',
'tags' => 'tags',
'resource_tags' => 'resource_tags',
'decorates' => 'decorates',
'decoration_inner_name' => 'decoration_inner_name',
'decoration_priority' => 'decoration_priority',
Expand All @@ -83,6 +84,7 @@ class YamlFileLoader extends FileLoader
'configurator' => 'configurator',
'calls' => 'calls',
'tags' => 'tags',
'resource_tags' => 'resource_tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
'bind' => 'bind',
Expand All @@ -97,6 +99,7 @@ class YamlFileLoader extends FileLoader
'configurator' => 'configurator',
'calls' => 'calls',
'tags' => 'tags',
'resource_tags' => 'resource_tags',
'autowire' => 'autowire',
'bind' => 'bind',
'constructor' => 'constructor',
Expand All @@ -105,6 +108,7 @@ class YamlFileLoader extends FileLoader
private const DEFAULTS_KEYWORDS = [
'public' => 'public',
'tags' => 'tags',
'resource_tags' => 'resource_tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
'bind' => 'bind',
Expand Down Expand Up @@ -281,9 +285,13 @@ private function parseDefaults(array &$content, string $file): array
}
}

if (isset($defaults['tags'])) {
if (!\is_array($tags = $defaults['tags'])) {
throw new InvalidArgumentException(\sprintf('Parameter "tags" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file));
foreach (['tags', 'resource_tags'] as $type) {
if (!isset($defaults[$type])) {
continue;
}

if (!\is_array($tags = $defaults[$type])) {
throw new InvalidArgumentException(\sprintf('Parameter "%s" in "_defaults" must be an array in "%s". Check your YAML syntax.', $type, $file));
}

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

$tags = $service['tags'] ?? [];
if (!\is_array($tags)) {
throw new InvalidArgumentException(\sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
}

if (isset($defaults['tags'])) {
$tags = array_merge($tags, $defaults['tags']);
}
foreach (['tags', 'resource_tags'] as $type) {
$tags = $service[$type] ?? [];
if (!\is_array($tags)) {
throw new InvalidArgumentException(\sprintf('Parameter "%s" must be an array for service "%s" in "%s". Check your YAML syntax.', $type, $id, $file));
}

foreach ($tags as $tag) {
if (!\is_array($tag)) {
$tag = ['name' => $tag];
if (isset($defaults[$type])) {
$tags = array_merge($tags, $defaults[$type]);
}

if (1 === \count($tag) && \is_array(current($tag))) {
$name = key($tag);
$tag = current($tag);
} else {
if (!isset($tag['name'])) {
throw new InvalidArgumentException(\sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file));
foreach ($tags as $tag) {
if (!\is_array($tag)) {
$tag = ['name' => $tag];
}
$name = $tag['name'];
unset($tag['name']);
}

if (!\is_string($name) || '' === $name) {
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file));
}
if (1 === \count($tag) && \is_array(current($tag))) {
$name = key($tag);
$tag = current($tag);
} else {
if (!isset($tag['name'])) {
throw new InvalidArgumentException(\sprintf('A "%s" entry is missing a "name" key for service "%s" in "%s".', $type, $id, $file));
}
$name = $tag['name'];
unset($tag['name']);
}

if (!\is_string($name) || '' === $name) {
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file));
}

$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);
$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);

$definition->addTag($name, $tag);
match ($type) {
'tags' => $definition->addTag($name, $tag),
'resource_tags' => $definition->addResourceTag($name, $tag),
};
}
}

if (null !== $decorates = $service['decorates'] ?? null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="resource-tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
Expand Down Expand Up @@ -197,6 +198,7 @@
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="resource-tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
"properties": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"calls": { "$ref": "#/$defs/calls" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"decorates": { "type": "string" },
"decoration_inner_name": { "type": "string" },
"decoration_priority": { "type": "integer" },
Expand Down Expand Up @@ -285,6 +286,7 @@
"configurator": { "$ref": "#/$defs/callable" },
"calls": { "$ref": "#/$defs/calls" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"autowire": { "type": "boolean" },
"autoconfigure": { "type": "boolean" },
"bind": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
Expand Down Expand Up @@ -322,6 +324,7 @@
"properties": {
"public": { "type": "boolean" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"autowire": { "type": "boolean" },
"autoconfigure": { "type": "boolean" },
"bind": {
Expand All @@ -342,6 +345,7 @@
"configurator": { "$ref": "#/$defs/callable" },
"calls": { "$ref": "#/$defs/calls" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"autowire": { "type": "boolean" },
"bind": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"constructor": { "type": "string" }
Expand Down
Loading
Loading