Skip to content

Commit

Permalink
[DependencyInjection] Auto exclude referencing service in `TaggedIter…
Browse files Browse the repository at this point in the history
…atorArgument`
  • Loading branch information
chalasr committed Dec 23, 2022
1 parent e9870a4 commit 34a24ca
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 array $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([]);

Expand All @@ -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()
Expand Down Expand Up @@ -78,4 +81,9 @@ public function getExclude(): array
{
return $this->exclude;
}

public function excludeSelf(): bool
{
return $this->excludeSelf;
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,16 @@ 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) {
$indexAttribute = $tagName->getIndexAttribute();
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
$needsIndexes = $tagName->needsIndexes();
$defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority';
$exclude = $tagName->getExclude();
$exclude = array_merge($exclude, $tagName->getExclude());
$tagName = $tagName->getTag();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@
<xsd:attribute name="default-index-method" type="xsd:string" />
<xsd:attribute name="default-priority-method" type="xsd:string" />
<xsd:attribute name="exclude" type="xsd:string" />
<xsd:attribute name="exclude-self" type="xsd:boolean" />
</xsd:complexType>

<xsd:complexType name="call">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
}
}

0 comments on commit 34a24ca

Please sign in to comment.