diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
index c968a54b298de..02a501bd2c34c 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
@@ -264,9 +264,6 @@ private function convertParameters(array $parameters, string $type, \DOMElement
$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');
@@ -290,8 +287,12 @@ private function convertParameters(array $parameters, string $type, \DOMElement
} 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 b57c8b955d152..c055a686128c2 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
@@ -234,6 +234,8 @@ private function dumpValue($value)
{
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 4b10acd9c53d8..7a92cc3b35b1c 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
@@ -15,6 +15,7 @@
use Symfony\Component\DependencyInjection\Alias;
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;
@@ -507,6 +508,13 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file
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 e9b09cc2eb67d..310aa351f2550 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
@@ -15,6 +15,7 @@
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;
@@ -745,6 +746,15 @@ private function resolveServices($value, string $file, bool $isParameter = false
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 208f5118ec7b3..65466e9768daf 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
@@ -267,6 +267,7 @@
+
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php
index ec68d8b4ea2d8..dda18a306207e 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php
@@ -13,6 +13,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\FileLocator;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -230,6 +231,17 @@ public function testTaggedArguments()
$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 b7876152541ce..b359f668d7758 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php
@@ -13,6 +13,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\FileLocator;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -117,6 +118,17 @@ public function testTaggedArguments()
$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());
+ }
+
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 0000000000000..3b49c1dba74ba
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_service_closure.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
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 0000000000000..a20a1c5877e75
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_service_closure.yml
@@ -0,0 +1,15 @@
+
+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
+ public: false
+ Symfony\Component\DependencyInjection\ContainerInterface:
+ alias: service_container
+ public: false
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
index aa0d08ed184dd..80d3393010602 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
@@ -21,6 +21,7 @@
use Symfony\Component\Config\Util\Exception\XmlParsingException;
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;
@@ -376,6 +377,15 @@ public function testParseTaggedArgumentsWithIndexBy()
$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 b20302cf977ba..f367bf3452384 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
@@ -20,6 +20,7 @@
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;
@@ -364,6 +365,15 @@ public function testTaggedArgumentsWithIndex()
}
}
+ 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();
diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json
index deb1b2711d04b..685f9275f596f 100644
--- a/src/Symfony/Component/DependencyInjection/composer.json
+++ b/src/Symfony/Component/DependencyInjection/composer.json
@@ -21,7 +21,7 @@
"symfony/service-contracts": "^1.1.6|^2"
},
"require-dev": {
- "symfony/yaml": "^3.4|^4.0|^5.0",
+ "symfony/yaml": "^4.4|^5.0",
"symfony/config": "^4.3",
"symfony/expression-language": "^3.4|^4.0|^5.0"
},