diff --git a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php
index c33e8615b254e..bfe9787f7c9bd 100644
--- a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php
+++ b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php
@@ -24,6 +24,7 @@ class TaggedIteratorArgument extends IteratorArgument
private ?string $defaultPriorityMethod;
private bool $needsIndexes;
private array $exclude;
+ private bool $excludeSelf = true;
/**
* @param string $tag The name of the tag identifying the target services
@@ -32,8 +33,9 @@ class TaggedIteratorArgument extends IteratorArgument
* @param bool $needsIndexes Whether indexes are required and should be generated when computing the map
* @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute
* @param array $exclude Services to exclude from the iterator
+ * @param bool $excludeSelf Whether to automatically exclude the referencing service from the iterator
*/
- public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = [])
+ public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null, array $exclude = [], bool $excludeSelf = true)
{
parent::__construct([]);
@@ -47,6 +49,7 @@ public function __construct(string $tag, string $indexAttribute = null, string $
$this->needsIndexes = $needsIndexes;
$this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Priority' : null);
$this->exclude = $exclude;
+ $this->excludeSelf = $excludeSelf;
}
public function getTag()
@@ -78,4 +81,9 @@ public function getExclude(): array
{
return $this->exclude;
}
+
+ public function excludeSelf(): bool
+ {
+ return $this->excludeSelf;
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php
index 5898a6afe0e81..fb33fb572942b 100644
--- a/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php
+++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedIterator.php
@@ -20,6 +20,7 @@ public function __construct(
public ?string $defaultIndexMethod = null,
public ?string $defaultPriorityMethod = null,
public string|array $exclude = [],
+ public bool $excludeSelf = true,
) {
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php
index b706a6388bf0d..f05ae53bc4284 100644
--- a/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php
+++ b/src/Symfony/Component/DependencyInjection/Attribute/TaggedLocator.php
@@ -20,6 +20,7 @@ public function __construct(
public ?string $defaultIndexMethod = null,
public ?string $defaultPriorityMethod = null,
public string|array $exclude = [],
+ public bool $excludeSelf = true,
) {
}
}
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index 81dec56e681f3..df045b56f05c8 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -24,6 +24,8 @@ CHANGELOG
* Deprecate using numeric parameter names
* Add support for tagged iterators/locators `exclude` option to the xml and yaml loaders/dumpers
* Allow injecting `string $env` into php config closures
+ * Add `excludeSelf` parameter to `TaggedIteratorArgument` with default value to `true`
+ to control whether the referencing service should be automatically excluded from the iterator
6.1
---
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
index 66a175d76c267..ac94cd7ae5b24 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
@@ -88,11 +88,11 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
}
if ($value instanceof TaggedIterator) {
- return new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, false, $value->defaultPriorityMethod, (array) $value->exclude);
+ return new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, false, $value->defaultPriorityMethod, (array) $value->exclude, $value->excludeSelf);
}
if ($value instanceof TaggedLocator) {
- return new ServiceLocatorArgument(new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, true, $value->defaultPriorityMethod, (array) $value->exclude));
+ return new ServiceLocatorArgument(new TaggedIteratorArgument($value->tag, $value->indexAttribute, $value->defaultIndexMethod, true, $value->defaultPriorityMethod, (array) $value->exclude, $value->excludeSelf));
}
if ($value instanceof MapDecorated) {
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
index 309bf63118d4e..2ddcaa0c08d8c 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
@@ -37,9 +37,8 @@ trait PriorityTaggedServiceTrait
*
* @return Reference[]
*/
- private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container): array
+ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagName, ContainerBuilder $container, array $exclude = []): array
{
- $exclude = [];
$indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null;
if ($tagName instanceof TaggedIteratorArgument) {
@@ -47,7 +46,7 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
$needsIndexes = $tagName->needsIndexes();
$defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority';
- $exclude = $tagName->getExclude();
+ $exclude = array_merge($exclude, $tagName->getExclude());
$tagName = $tagName->getTag();
}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php
index 1fca5ebaa5f8a..469d001b51fea 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php
@@ -28,7 +28,12 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
return parent::processValue($value, $isRoot);
}
- $value->setValues($this->findAndSortTaggedServices($value, $this->container));
+ $exclude = $value->getExclude();
+ if ($value->excludeSelf()) {
+ $exclude[] = $this->currentId;
+ }
+
+ $value->setValues($this->findAndSortTaggedServices($value, $this->container, $exclude));
return $value;
}
diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
index 7c0ea3261300b..4acbfe56a9aa3 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
@@ -550,7 +550,7 @@ 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);
+ $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->getAttribute('exclude-self') ?: true);
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 ad61c14437d1a..a9e35fdad654c 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
@@ -824,11 +824,11 @@ private function resolveServices(mixed $value, string $file, bool $isParameter =
$forLocator = 'tagged_locator' === $value->getTag();
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'])) {
+ 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 = 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);
} elseif (\is_string($argument) && $argument) {
$argument = new TaggedIteratorArgument($argument, null, null, $forLocator);
} else {
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 20e97866788b6..83e430a859445 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
@@ -302,6 +302,7 @@
+
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php
index ba979be80bc04..4da06e889b715 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php
@@ -462,7 +462,7 @@ public static function getSubscribedServices(): array
'autowired' => new ServiceClosureArgument(new Reference('service.id')),
'autowired.nullable' => new ServiceClosureArgument(new Reference('service.id', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
'autowired.parameter' => new ServiceClosureArgument('foobar'),
- 'map.decorated' => new ServiceClosureArgument(new Reference('.service_locator.oZHAdom.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
+ 'map.decorated' => new ServiceClosureArgument(new Reference('.service_locator.LnJLtj2.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'someTarget', [new Target('someTarget')])),
];
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php
index a62a585c6ef0c..7e2fa2f7ddda1 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTaggedIteratorArgumentPassTest.php
@@ -54,4 +54,36 @@ public function testProcessWithIndexes()
$expected->setValues(['1' => new TypedReference('service_a', 'stdClass'), '2' => new TypedReference('service_b', 'stdClass')]);
$this->assertEquals($expected, $properties['foos']);
}
+
+ public function testProcesWithAutoExcludeReferencingService()
+ {
+ $container = new ContainerBuilder();
+ $container->register('service_a', 'stdClass')->addTag('foo', ['key' => '1']);
+ $container->register('service_b', 'stdClass')->addTag('foo', ['key' => '2']);
+ $container->register('service_c', 'stdClass')->addTag('foo', ['key' => '3'])->setProperty('foos', new TaggedIteratorArgument('foo', 'key'));
+
+ (new ResolveTaggedIteratorArgumentPass())->process($container);
+
+ $properties = $container->getDefinition('service_c')->getProperties();
+
+ $expected = new TaggedIteratorArgument('foo', 'key');
+ $expected->setValues(['1' => new TypedReference('service_a', 'stdClass'), '2' => new TypedReference('service_b', 'stdClass')]);
+ $this->assertEquals($expected, $properties['foos']);
+ }
+
+ public function testProcesWithoutAutoExcludeReferencingService()
+ {
+ $container = new ContainerBuilder();
+ $container->register('service_a', 'stdClass')->addTag('foo', ['key' => '1']);
+ $container->register('service_b', 'stdClass')->addTag('foo', ['key' => '2']);
+ $container->register('service_c', 'stdClass')->addTag('foo', ['key' => '3'])->setProperty('foos', new TaggedIteratorArgument(tag: 'foo', indexAttribute: 'key', excludeSelf: false));
+
+ (new ResolveTaggedIteratorArgumentPass())->process($container);
+
+ $properties = $container->getDefinition('service_c')->getProperties();
+
+ $expected = new TaggedIteratorArgument(tag: 'foo', indexAttribute: 'key', excludeSelf: false);
+ $expected->setValues(['1' => new TypedReference('service_a', 'stdClass'), '2' => new TypedReference('service_b', 'stdClass'), '3' => new TypedReference('service_c', 'stdClass')]);
+ $this->assertEquals($expected, $properties['foos']);
+ }
}