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 29, 2022
1 parent e9870a4 commit 49f0d8a
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 10 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 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([]);

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function __construct(
public ?string $defaultIndexMethod = null,
public ?string $defaultPriorityMethod = null,
public string|array $exclude = [],
public bool $excludeSelf = true,
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function __construct(
public ?string $defaultIndexMethod = null,
public ?string $defaultPriorityMethod = null,
public string|array $exclude = [],
public bool $excludeSelf = true,
) {
}
}
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 @@ -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) {
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 49f0d8a

Please sign in to comment.