diff --git a/src/Symfony/Component/DependencyInjection/Argument/TaggedVariadicArgument.php b/src/Symfony/Component/DependencyInjection/Argument/TaggedVariadicArgument.php
new file mode 100644
index 0000000000000..ff1c5990164aa
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Argument/TaggedVariadicArgument.php
@@ -0,0 +1,12 @@
+
+ */
+class TaggedVariadicArgument extends TaggedIteratorArgument
+{
+}
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index cab370e80feef..6a470e3aba970 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -1,6 +1,10 @@
CHANGELOG
=========
+7.2
+---
+ * Add `!tagged_variadic` attribute to autowire with a variadic constructor
+
7.1
---
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index adb05e76465da..128094b8f9d89 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -20,6 +20,7 @@
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass;
use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass;
use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode;
@@ -1853,7 +1854,7 @@ private function dumpValue(mixed $value, bool $interpolate = true): string
}
$code = [];
- $code[] = 'new RewindableGenerator(function () use ($container) {';
+ $code[] = ($value instanceof TaggedVariadicArgument ? '...' : '') . 'new RewindableGenerator(function () use ($container) {';
$operands = [0];
foreach ($values as $k => $v) {
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
index 6ae8d5c611906..231d18817fb37 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
@@ -23,6 +23,7 @@
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
/**
* XmlDumper dumps a service container as an XML string.
@@ -303,7 +304,13 @@ private function convertParameters(array $parameters, string $type, \DOMElement
$element->setAttribute('type', 'collection');
$this->convertParameters($value, $type, $element, 'key');
} elseif ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) {
- $element->setAttribute('type', $value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator');
+ $elementType = match (true) {
+ $value instanceof TaggedVariadicArgument => 'tagged_variadic',
+ $value instanceof TaggedIteratorArgument => 'tagged_iterator',
+ $value instanceof ServiceLocatorArgument => 'tagged_locator',
+ };
+
+ $element->setAttribute('type', $elementType);
$element->setAttribute('tag', $tag->getTag());
if (null !== $tag->getIndexAttribute()) {
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
index fac33bc6792d1..6b6b98b97530a 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
@@ -18,6 +18,7 @@
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
@@ -282,7 +283,14 @@ private function dumpValue(mixed $value): mixed
$content['exclude_self'] = false;
}
- return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator', $content);
+ $tag = match (true) {
+ $value instanceof TaggedVariadicArgument => 'tagged_variadic',
+ $value instanceof TaggedIteratorArgument => 'tagged_iterator',
+ $value instanceof ServiceLocatorArgument => 'tagged_locator',
+ default => throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_debug_type($value))),
+ };
+
+ return new TaggedValue($tag, $content);
}
if ($value instanceof IteratorArgument) {
diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
index 9eb02921feacd..9cd17a575d98f 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
@@ -19,6 +19,7 @@
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -587,8 +588,11 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file
break;
case 'tagged':
case 'tagged_iterator':
+ case 'tagged_variadic':
case 'tagged_locator':
$forLocator = 'tagged_locator' === $type;
+ $isVariadic = 'tagged_variadic' === $type;
+ $args = [];
if (!$arg->getAttribute('tag')) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file));
@@ -602,7 +606,9 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file
$excludes = [$arg->getAttribute('exclude')];
}
- $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes, !$arg->hasAttribute('exclude-self') || XmlUtils::phpize($arg->getAttribute('exclude-self')));
+ $args = [$arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes, !$arg->hasAttribute('exclude-self') || XmlUtils::phpize($arg->getAttribute('exclude-self'))];
+
+ $arguments[$key] = $isVariadic ? new TaggedVariadicArgument(...$args) : new TaggedIteratorArgument(...$args);
if ($forLocator) {
$arguments[$key] = new ServiceLocatorArgument($arguments[$key]);
diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
index fcc3fd4ca1104..4591307e410b9 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
@@ -18,6 +18,7 @@
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -851,21 +852,27 @@ private function resolveServices(mixed $value, string $file, bool $isParameter =
return new ServiceLocatorArgument($argument);
}
- if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], true)) {
+ if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator', 'tagged_variadic'], true)) {
$forLocator = 'tagged_locator' === $value->getTag();
+ $isVariadic = 'tagged_variadic' === $value->getTag();
+ $args = [];
if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude', 'exclude_self'])) {
throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "%s".', $value->getTag(), implode('", "', $diff), implode('", "', $supportedKeys)));
}
- $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null), $argument['exclude_self'] ?? true);
+ $args = [$argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null), $argument['exclude_self'] ?? true];
} elseif (\is_string($argument) && $argument) {
- $argument = new TaggedIteratorArgument($argument, null, null, $forLocator);
+ $args = [$argument, null, null, $forLocator];
} else {
throw new InvalidArgumentException(sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file));
}
+ if ($args) {
+ $argument = $isVariadic ? new TaggedVariadicArgument(...$args) : new TaggedIteratorArgument(...$args);
+ }
+
if ($forLocator) {
$argument = new ServiceLocatorArgument($argument);
}
diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
index c071e3466613c..8826808ab0a11 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
+++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
@@ -362,6 +362,7 @@
+
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
index deb1e23f2b3b1..b423ddcc4185a 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
@@ -65,6 +65,7 @@
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\VarExporter\LazyObjectInterface;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
require_once __DIR__.'/../Fixtures/includes/classes.php';
@@ -314,6 +315,35 @@ public function testDumpAsFilesWithFactoriesInlinedWithTaggedIterator()
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_inlined_factories_with_tagged_iterrator.txt', $dump);
}
+ public function testDumpAsFilesWithFactoriesInlinedWithTaggedVariadic()
+ {
+ $container = new ContainerBuilder();
+ $container
+ ->register('foo', FooClass::class)
+ ->addMethodCall('setOtherInstances', [new TaggedVariadicArgument('testVariadic')])
+ ->setShared(false)
+ ->setPublic(true);
+
+ $container
+ ->register('VariadicTest', 'VariadicTest')
+ ->addTag('testVariadic');
+
+ $container
+ ->register('stdClass', '\stdClass')
+ ->addTag('testVariadic');
+
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+ $dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'build_time' => 1714151215, 'inline_factories' => true, 'inline_class_loader' => true]), true);
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump);
+ }
+
+ $this->assertStringMatchesFormatFile(self::$fixturesPath . '/php/services9_inlined_factories_with_tagged_variadic.txt', $dump);
+ }
+
public function testDumpAsFilesWithLazyFactoriesInlined()
{
$container = new ContainerBuilder();
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php
index 548e5a18b27ec..54780f34fd815 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php
@@ -17,6 +17,7 @@
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -246,6 +247,32 @@ public function testTaggedArguments()
$this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_with_tagged_arguments.xml', $dumper->dump());
}
+ public function testTaggedVariadicArguments()
+ {
+ $taggedIterator = new TaggedVariadicArgument('variadic_tag', 'barfoo', 'foobar', false, 'getPriority');
+ $taggedIterator2 = new TaggedVariadicArgument('variadic_tag', null, null, false, null, ['baz']);
+ $taggedIterator3 = new TaggedVariadicArgument('variadic_tag', null, null, false, null, ['baz', 'qux'], false);
+
+ $container = new ContainerBuilder();
+
+ $container->register('foo', 'Foo')->addTag('variadic_tag');
+ $container->register('baz', 'Baz')->addTag('variadic_tag');
+ $container->register('qux', 'Qux')->addTag('variadic_tag');
+
+ $container->register('foo_tagged_variadic', 'Bar')
+ ->setPublic(true)
+ ->addArgument($taggedIterator);
+ $container->register('foo2_tagged_variadic', 'Bar')
+ ->setPublic(true)
+ ->addArgument($taggedIterator2);
+ $container->register('foo3_tagged_variadic', 'Bar')
+ ->setPublic(true)
+ ->addArgument($taggedIterator3);
+
+ $dumper = new XmlDumper($container);
+ $this->assertStringEqualsFile(self::$fixturesPath . '/xml/services_with_tagged_variadic_arguments.xml', $dumper->dump());
+ }
+
public function testServiceClosure()
{
$container = new ContainerBuilder();
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php
index f9ff3fff786a3..268ef4fe7f695 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php
@@ -17,6 +17,7 @@
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedVariadicArgument;
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -138,6 +139,26 @@ public function testTaggedArguments()
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_with_tagged_argument.yml', $dumper->dump());
}
+ public function testTaggedVariadic()
+ {
+ $taggedVariadic = new TaggedVariadicArgument('variadic_test', 'barfoo', 'foobar', false, 'getPriority');
+ $taggedVariadic2 = new TaggedVariadicArgument('variadic_test', null, null, false, null, ['baz']);
+ $taggedVariadic3 = new TaggedVariadicArgument('variadic_test', null, null, false, null, ['baz', 'qux'], false);
+
+ $container = new ContainerBuilder();
+
+ $container->register('foo_service', 'Foo')->addTag('foo');
+ $container->register('baz_service', 'Baz')->addTag('foo');
+ $container->register('qux_service', 'Qux')->addTag('foo');
+
+ $container->register('foo_service_tagged_variadic', 'Bar')->addArgument($taggedVariadic);
+ $container->register('foo2_service_tagged_variadic', 'Bar')->addArgument($taggedVariadic2);
+ $container->register('foo3_service_tagged_variadic', 'Bar')->addArgument($taggedVariadic3);
+
+ $dumper = new YamlDumper($container);
+ $this->assertStringEqualsFile(self::$fixturesPath . '/yaml/services_with_tagged_variadic_argument.yml', $dumper->dump());
+ }
+
public function testServiceClosure()
{
$container = new ContainerBuilder();
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories_with_tagged_variadic.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories_with_tagged_variadic.txt
new file mode 100644
index 0000000000000..cd68ee8da6c3b
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories_with_tagged_variadic.txt
@@ -0,0 +1,125 @@
+Array
+(
+ [Container%s/removed-ids.php] => true,
+ 'stdClass' => true,
+];
+
+ [Container%s/ProjectServiceContainer.php] => targetDir = \dirname($containerDir);
+ $this->services = $this->privates = [];
+ $this->methodMap = [
+ 'foo' => 'getFooService',
+ ];
+
+ $this->aliases = [];
+ }
+
+ public function compile(): void
+ {
+ throw new LogicException('You cannot compile a dumped container that was already compiled.');
+ }
+
+ public function isCompiled(): bool
+ {
+ return true;
+ }
+
+ public function getRemovedIds(): array
+ {
+ return require $this->containerDir.\DIRECTORY_SEPARATOR.'removed-ids.php';
+ }
+
+ /**
+ * Gets the public 'foo' service.
+ *
+ * @return \Symfony\Component\DependencyInjection\Tests\Dumper\FooClass
+ */
+ protected static function getFooService($container)
+ {
+ $container->factories['foo'] = function ($container) {
+ $instance = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooClass();
+
+ $instance->setOtherInstances(...new RewindableGenerator(function () use ($container) {
+ yield 0 => ($container->privates['VariadicTest'] ??= new \VariadicTest());
+ yield 1 => ($container->privates['stdClass'] ??= new \stdClass());
+ }, 2));
+
+ return $instance;
+ };
+
+ return $container->factories['foo']($container);
+ }
+}
+
+ [ProjectServiceContainer.preload.php] => = 7.4 when preloading is desired
+
+use Symfony\Component\DependencyInjection\Dumper\Preloader;
+
+if (in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
+ return;
+}
+
+require dirname(__DIR__, %d).'/vendor/autoload.php';
+(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null);
+
+$classes = [];
+$classes[] = 'VariadicTest';
+$classes[] = 'Symfony\Component\DependencyInjection\Tests\Dumper\FooClass';
+$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
+
+$preloaded = Preloader::preload($classes);
+
+ [ProjectServiceContainer.php] => '%s',
+ 'container.build_id' => '%s',
+ 'container.build_time' => %d,
+ 'container.runtime_mode' => \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? 'web=0' : 'web=1',
+], __DIR__.\DIRECTORY_SEPARATOR.'Container%s');
+
+)
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_tagged_variadic_arguments.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_tagged_variadic_arguments.xml
new file mode 100644
index 0000000000000..fb2047ef07ae6
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_tagged_variadic_arguments.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ baz
+ qux
+
+
+
+
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_tagged_variadic_argument.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_tagged_variadic_argument.yml
new file mode 100644
index 0000000000000..511586c82f73b
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_tagged_variadic_argument.yml
@@ -0,0 +1,27 @@
+
+services:
+ service_container:
+ class: Symfony\Component\DependencyInjection\ContainerInterface
+ public: true
+ synthetic: true
+ foo_service:
+ class: Foo
+ tags:
+ - foo
+ baz_service:
+ class: Baz
+ tags:
+ - foo
+ qux_service:
+ class: Qux
+ tags:
+ - foo
+ foo_service_tagged_variadic:
+ class: Bar
+ arguments: [!tagged_variadic { tag: variadic_test, index_by: barfoo, default_index_method: foobar, default_priority_method: getPriority }]
+ foo2_service_tagged_variadic:
+ class: Bar
+ arguments: [!tagged_variadic { tag: variadic_test, exclude: baz }]
+ foo3_service_tagged_variadic:
+ class: Bar
+ arguments: [!tagged_variadic { tag: variadic_test, exclude: [baz, qux], exclude_self: false }]