Skip to content

Commit

Permalink
Add tagged_variadic to dependency injection
Browse files Browse the repository at this point in the history
  • Loading branch information
Fan2Shrek committed Apr 26, 2024
1 parent 65ccca0 commit ba029a2
Show file tree
Hide file tree
Showing 14 changed files with 310 additions and 7 deletions.
@@ -0,0 +1,12 @@
<?php

namespace Symfony\Component\DependencyInjection\Argument;

/**
* Represents a tagged variadic argument.
*
* @author Pierre Ambroise<pierre27.ambroise@gmail.com>
*/
class TaggedVariadicArgument extends TaggedIteratorArgument
{
}
4 changes: 4 additions & 0 deletions 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
---

Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Expand Up @@ -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.
Expand Down Expand Up @@ -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()) {
Expand Down
10 changes: 9 additions & 1 deletion src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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]);
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
Expand Up @@ -362,6 +362,7 @@
<xsd:enumeration value="tagged" />
<xsd:enumeration value="tagged_iterator" />
<xsd:enumeration value="tagged_locator" />
<xsd:enumeration value="tagged_variadic" />
</xsd:restriction>
</xsd:simpleType>

Expand Down
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit ba029a2

Please sign in to comment.