diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index 408ed3c40d..9c950c75ed 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -275,9 +275,6 @@ class XmlDumper extends Dumper $element->setAttribute($keyAttribute, $key); } - if ($value instanceof ServiceClosureArgument) { - $value = $value->getValues()[0]; - } if (\is_array($tag = $value)) { $element->setAttribute('type', 'collection'); $this->convertParameters($value, $type, $element, 'key'); @@ -301,8 +298,12 @@ class XmlDumper extends Dumper } elseif ($value instanceof ServiceLocatorArgument) { $element->setAttribute('type', 'service_locator'); $this->convertParameters($value->getValues(), $type, $element, 'key'); - } elseif ($value instanceof Reference) { + } elseif ($value instanceof Reference || $value instanceof ServiceClosureArgument) { $element->setAttribute('type', 'service'); + if ($value instanceof ServiceClosureArgument) { + $element->setAttribute('type', 'service_closure'); + $value = $value->getValues()[0]; + } $element->setAttribute('id', (string) $value); $behavior = $value->getInvalidBehavior(); if (ContainerInterface::NULL_ON_INVALID_REFERENCE == $behavior) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index c590526b6d..cf40ec0bf4 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -254,6 +254,8 @@ class YamlDumper extends Dumper { if ($value instanceof ServiceClosureArgument) { $value = $value->getValues()[0]; + + return new TaggedValue('service_closure', $this->getServiceCall((string) $value, $value)); } if ($value instanceof ArgumentInterface) { $tag = $value; diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index a5b42f8835..5dfe97355f 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -494,6 +495,13 @@ class XmlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file)); } break; + case 'service_closure': + if ('' === $arg->getAttribute('id')) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service_closure" has no or empty "id" attribute in "%s".', $name, $file)); + } + + $arguments[$key] = new ServiceClosureArgument(new Reference($arg->getAttribute('id'), $invalidBehavior)); + break; case 'service_locator': $arg = $this->getArgumentsAsPhp($arg, $name, $file); try { diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 4fe4839179..bf7ad8b721 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -797,6 +798,15 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file)); } } + if ('service_closure' === $value->getTag()) { + $argument = $this->resolveServices($argument, $file, $isParameter); + + if (!$argument instanceof Reference) { + throw new InvalidArgumentException(sprintf('"!service_closure" tag only accepts service references in "%s".', $file)); + } + + return new ServiceClosureArgument($argument); + } if ('service_locator' === $value->getTag()) { if (!\is_array($argument)) { throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file)); 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 03988686ba..3d98eafc37 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 @@ -291,6 +291,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php index f05a1c5ee3..83b4cf63d2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -252,6 +253,17 @@ class XmlDumperTest extends TestCase $this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_with_tagged_arguments.xml', $dumper->dump()); } + public function testServiceClosure() + { + $container = new ContainerBuilder(); + $container->register('foo', 'Foo') + ->addArgument(new ServiceClosureArgument(new Reference('bar', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))) + ; + + $dumper = new XmlDumper($container); + $this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_with_service_closure.xml', $dumper->dump()); + } + public function testDumpAbstractServices() { $container = include self::$fixturesPath.'/containers/container_abstract.php'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php index 3f4d5bc1fb..793002eebb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -119,6 +120,17 @@ class YamlDumperTest extends TestCase $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_with_tagged_argument.yml', $dumper->dump()); } + public function testServiceClosure() + { + $container = new ContainerBuilder(); + $container->register('foo', 'Foo') + ->addArgument(new ServiceClosureArgument(new Reference('bar', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))) + ; + + $dumper = new YamlDumper($container); + $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_with_service_closure.yml', $dumper->dump()); + } + public function testDumpServiceWithAbstractArgument() { $container = new ContainerBuilder(); @@ -130,6 +142,7 @@ class YamlDumperTest extends TestCase $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_with_abstract_argument.yml', $dumper->dump()); } + private function assertEqualYamlStructure(string $expected, string $yaml, string $message = '') { $parser = new Parser(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_closure.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_closure.xml new file mode 100644 index 0000000000..a202b33249 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_closure.xml @@ -0,0 +1,15 @@ + + + + + + + + + The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it. + + + The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it. + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_closure.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_closure.yml new file mode 100644 index 0000000000..98e1996d72 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_closure.yml @@ -0,0 +1,21 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + foo: + class: Foo + arguments: [!service_closure '@?bar'] + Psr\Container\ContainerInterface: + alias: service_container + deprecated: + package: symfony/dependency-injection + version: 5.1 + message: The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it. + Symfony\Component\DependencyInjection\ContainerInterface: + alias: service_container + deprecated: + package: symfony/dependency-injection + version: 5.1 + message: The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it. diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 8714905f71..8a5be20d18 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -23,6 +23,7 @@ use Symfony\Component\Config\Util\Exception\XmlParsingException; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; @@ -381,6 +382,15 @@ class XmlFileLoaderTest extends TestCase $this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('foo_tagged_locator')->getArgument(0)); } + public function testParseServiceClosure() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services_with_service_closure.xml'); + + $this->assertEquals(new ServiceClosureArgument(new Reference('bar', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), $container->getDefinition('foo')->getArgument(0)); + } + public function testParseTagsWithoutNameThrowsException() { $this->expectException(InvalidArgumentException::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 8591b9f128..bbb77e9923 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\GlobResource; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; @@ -375,6 +376,15 @@ class YamlFileLoaderTest extends TestCase $this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('bar_service_tagged_locator')->getArgument(0)); } + public function testParseServiceClosure() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services_with_service_closure.yml'); + + $this->assertEquals(new ServiceClosureArgument(new Reference('bar', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), $container->getDefinition('foo')->getArgument(0)); + } + public function testNameOnlyTagsAreAllowedAsString() { $container = new ContainerBuilder();