+ */ +trait ReferenceSetArgumentTrait +{ + private $values; + + /** + * @param Reference[] $values + */ + public function __construct(array $values) + { + $this->setValues($values); + } + + /** + * @return Reference[] The values in the set + */ + public function getValues() + { + return $this->values; + } + + /** + * @param Reference[] $values The service references to put in the set + */ + public function setValues(array $values) + { + foreach ($values as $k => $v) { + if (null !== $v && !$v instanceof Reference) { + throw new InvalidArgumentException(sprintf('A %s must hold only Reference instances, "%s" given.', __CLASS__, is_object($v) ? get_class($v) : gettype($v))); + } + } + + $this->values = $values; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php new file mode 100644 index 0000000000..fcb2b6a6b9 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; + +/** + * @author Nicolas Grekas
+ * + * @internal + */ +class ServiceLocator extends BaseServiceLocator +{ + private $factory; + private $serviceMap; + + public function __construct(\Closure $factory, array $serviceMap) + { + $this->factory = $factory; + $this->serviceMap = $serviceMap; + parent::__construct($serviceMap); + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php new file mode 100644 index 0000000000..8214f8bb3a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +/** + * Represents a closure acting as a service locator. + * + * @author Nicolas Grekas
+ */
+class ServiceLocatorArgument implements ArgumentInterface
+{
+ use ReferenceSetArgumentTrait;
+}
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index 4393b8c44c..de2421a60a 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -5,6 +5,7 @@ CHANGELOG
-----
* added `ServiceSubscriberTrait`
+ * added `ServiceLocatorArgument` for creating optimized service-locators
4.1.0
-----
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php
index b99c252fef..8ac301f192 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php
@@ -11,13 +11,11 @@
namespace Symfony\Component\DependencyInjection\Compiler;
-use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\EnvVarProcessor;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
-use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\Reference;
/**
@@ -41,7 +39,7 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
}
foreach ($class::getProvidedTypes() as $prefix => $type) {
- $processors[$prefix] = new ServiceClosureArgument(new Reference($id));
+ $processors[$prefix] = new Reference($id);
$types[$prefix] = self::validateProvidedTypes($type, $class);
}
}
@@ -56,9 +54,8 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface
}
if ($processors) {
- $container->register('container.env_var_processors_locator', ServiceLocator::class)
+ $container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors))
->setPublic(true)
- ->setArguments(array($processors))
;
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
index 3969c74fcb..1b66b55290 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
@@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -28,6 +29,9 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass
{
protected function processValue($value, $isRoot = false)
{
+ if ($value instanceof ServiceLocatorArgument) {
+ return self::register($this->container, $value->getValues());
+ }
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
return parent::processValue($value, $isRoot);
}
diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php
index 2b3ab61e42..2eaa278cdb 100644
--- a/src/Symfony/Component/DependencyInjection/Container.php
+++ b/src/Symfony/Component/DependencyInjection/Container.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
@@ -401,6 +402,30 @@ class Container implements ResettableContainerInterface
}
}
+ /**
+ * @internal
+ */
+ final protected function getService($registry, $id, $method, $load)
+ {
+ if ('service_container' === $id) {
+ return $this;
+ }
+ if (\is_string($load)) {
+ throw new RuntimeException($load);
+ }
+ if (null === $method) {
+ return false !== $registry ? $this->{$registry}[$id] ?? null : null;
+ }
+ if (false !== $registry) {
+ return $this->{$registry}[$id] ?? $this->{$registry}[$id] = $load ? $this->load($method) : $this->{$method}();
+ }
+ if (!$load) {
+ return $this->{$method}();
+ }
+
+ return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method);
+ }
+
private function __clone()
{
}
diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
index 1c2c239869..3aa0e55af1 100644
--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -15,6 +15,8 @@ use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Compiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
@@ -1215,6 +1217,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $count;
});
+ } elseif ($value instanceof ServiceLocatorArgument) {
+ $refs = array();
+ foreach ($value->getValues() as $k => $v) {
+ if ($v) {
+ $refs[$k] = array($v);
+ }
+ }
+ $value = new ServiceLocator(\Closure::fromCallable(array($this, 'resolveServices')), $refs);
} elseif ($value instanceof Reference) {
$value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices);
} elseif ($value instanceof Definition) {
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index 3988bdf324..8dcd4829ec 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -14,6 +14,8 @@ namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Variable;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass;
@@ -22,6 +24,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
@@ -71,6 +74,8 @@ class PhpDumper extends Dumper
private $circularReferences = array();
private $singleUsePrivateIds = array();
private $addThrow = false;
+ private $locatedIds = array();
+ private $serviceLocatorTag;
/**
* @var ProxyDumper
@@ -113,6 +118,7 @@ class PhpDumper extends Dumper
*/
public function dump(array $options = array())
{
+ $this->locatedIds = array();
$this->targetDirRegex = null;
$this->inlinedRequires = array();
$options = array_merge(array(
@@ -123,6 +129,7 @@ class PhpDumper extends Dumper
'debug' => true,
'hot_path_tag' => 'container.hot_path',
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
+ 'service_locator_tag' => 'container.service_locator',
'build_time' => time(),
), $options);
@@ -131,6 +138,7 @@ class PhpDumper extends Dumper
$this->asFiles = $options['as_files'];
$this->hotPathTag = $options['hot_path_tag'];
$this->inlineRequires = $options['inline_class_loader_parameter'] && $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']);
+ $this->serviceLocatorTag = $options['service_locator_tag'];
if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
$baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
@@ -269,6 +277,7 @@ EOF;
$this->targetDirRegex = null;
$this->inlinedRequires = array();
$this->circularReferences = array();
+ $this->locatedIds = array();
$unusedEnvs = array();
foreach ($this->container->getEnvCounters() as $env => $use) {
@@ -508,7 +517,7 @@ EOTXT
throw new ServiceCircularReferenceException($id, array($id));
}
- $code .= $this->addNewInstance($def, '$'.$name, ' = ', $id);
+ $code .= $this->addNewInstance($def, ' $'.$name.' = ', $id);
if (!$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) {
$code .= $this->addServiceProperties($def, $name);
@@ -552,7 +561,7 @@ EOTXT
$instantiation .= ' = ';
}
- $code = $this->addNewInstance($definition, $return, $instantiation, $id);
+ $code = $this->addNewInstance($definition, ' '.$return.$instantiation, $id);
if (!$isSimpleInstance) {
$code .= "\n";
@@ -816,7 +825,7 @@ EOF;
}
if ($definition->isPublic()) {
$publicServices .= $this->addService($id, $definition);
- } elseif (!$this->isTrivialInstance($definition)) {
+ } elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) {
$privateServices .= $this->addService($id, $definition);
}
}
@@ -829,7 +838,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
- if (!$definition->isSynthetic() && !$this->isHotPath($definition) && ($definition->isPublic() || !$this->isTrivialInstance($definition))) {
+ if (!$definition->isSynthetic() && !$this->isHotPath($definition) && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) {
$code = $this->addService($id, $definition, $file);
if (!$definition->isShared()) {
@@ -850,10 +859,18 @@ EOF;
}
}
- private function addNewInstance(Definition $definition, $return, $instantiation, $id)
+ private function addNewInstance(Definition $definition, string $return = '', string $id = null)
{
- $class = $this->dumpValue($definition->getClass());
- $return = ' '.$return.$instantiation;
+ $tail = $return ? ";\n" : '';
+
+ if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) {
+ $arguments = array();
+ foreach ($definition->getArgument(0) as $k => $argument) {
+ $arguments[$k] = $argument->getValues()[0];
+ }
+
+ return $return.$this->dumpValue(new ServiceLocatorArgument($arguments)).$tail;
+ }
$arguments = array();
foreach ($definition->getArguments() as $value) {
@@ -862,6 +879,7 @@ EOF;
if (null !== $definition->getFactory()) {
$callable = $definition->getFactory();
+
if (is_array($callable)) {
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) {
throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a'));
@@ -869,34 +887,34 @@ EOF;
if ($callable[0] instanceof Reference
|| ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) {
- return $return.sprintf("%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '');
+ return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
$class = $this->dumpValue($callable[0]);
// If the class is a string we can optimize away
if (0 === strpos($class, "'") && false === strpos($class, '$')) {
if ("''" === $class) {
- throw new RuntimeException(sprintf('Cannot dump definition: The "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id));
+ throw new RuntimeException(sprintf('Cannot dump definition: %s service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline'));
}
- return $return.sprintf("%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '');
+ return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
if (0 === strpos($class, 'new ')) {
- return $return.sprintf("(%s)->%s(%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : '');
+ return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
- return $return.sprintf("[%s, '%s'](%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : '');
+ return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
}
- return $return.sprintf("%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '');
+ return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail;
}
- if (false !== strpos($class, '$')) {
- return sprintf(" \$class = %s;\n\n%snew \$class(%s);\n", $class, $return, implode(', ', $arguments));
+ if (null === $class = $definition->getClass()) {
+ throw new RuntimeException('Cannot dump definitions which have no class nor factory.');
}
- return $return.sprintf("new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments));
+ return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail;
}
private function startClass(string $class, string $baseClass, string $baseClassWithNamespace): string
@@ -1537,6 +1555,27 @@ EOF;
return implode("\n", $code);
}
+
+ if ($value instanceof ServiceLocatorArgument) {
+ $serviceMap = '';
+ foreach ($value->getValues() as $k => $v) {
+ if (!$v) {
+ continue;
+ }
+ $definition = $this->container->findDefinition($id = (string) $v);
+ $load = !($e = $definition->getErrors()) ? $this->asFiles && !$this->isHotPath($definition) : reset($e);
+ $serviceMap .= sprintf("\n %s => array(%s, %s, %s, %s),",
+ $this->export($k),
+ $this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false),
+ $this->export($id),
+ $this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id).($load ? '.php' : '') : null),
+ $this->export($load)
+ );
+ $this->locatedIds[$id] = true;
+ }
+
+ return sprintf('new \%s(\Closure::fromCallable(array($this, \'getService\')), array(%s%s))', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '');
+ }
} finally {
list($this->definitionVariables, $this->referenceVariables, $this->variableCount) = $scope;
}
@@ -1559,50 +1598,7 @@ EOF;
throw new RuntimeException('Cannot dump definitions which have a configurator.');
}
- $arguments = array();
- foreach ($value->getArguments() as $argument) {
- $arguments[] = $this->dumpValue($argument);
- }
-
- if (null !== $value->getFactory()) {
- $factory = $value->getFactory();
-
- if (is_string($factory)) {
- return sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory)), implode(', ', $arguments));
- }
-
- if (is_array($factory)) {
- if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) {
- throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a'));
- }
-
- $class = $this->dumpValue($factory[0]);
- if (is_string($factory[0])) {
- return sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $factory[1], implode(', ', $arguments));
- }
-
- if ($factory[0] instanceof Definition) {
- if (0 === strpos($class, 'new ')) {
- return sprintf('(%s)->%s(%s)', $class, $factory[1], implode(', ', $arguments));
- }
-
- return sprintf("[%s, '%s'](%s)", $class, $factory[1], implode(', ', $arguments));
- }
-
- if ($factory[0] instanceof Reference) {
- return sprintf('%s->%s(%s)', $class, $factory[1], implode(', ', $arguments));
- }
- }
-
- throw new RuntimeException('Cannot dump definition because of invalid factory');
- }
-
- $class = $value->getClass();
- if (null === $class) {
- throw new RuntimeException('Cannot dump definitions which have no class nor factory.');
- }
-
- return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments));
+ return $this->addNewInstance($value);
} elseif ($value instanceof Variable) {
return '$'.$value;
} elseif ($value instanceof Reference) {
@@ -1696,7 +1692,7 @@ EOF;
return sprintf('$this->throw(%s)', $this->export(reset($e)));
}
- $code = substr($this->addNewInstance($definition, '', '', $id), 8, -2);
+ $code = $this->addNewInstance($definition, '', $id);
if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
$code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code);
}
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
index 664e58aa87..a644ff3fc2 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
@@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Dumper;
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\ContainerInterface;
use Symfony\Component\DependencyInjection\Parameter;
@@ -280,6 +281,9 @@ class XmlDumper extends Dumper
} elseif ($value instanceof IteratorArgument) {
$element->setAttribute('type', 'iterator');
$this->convertParameters($value->getValues(), $type, $element, 'key');
+ } elseif ($value instanceof ServiceLocatorArgument) {
+ $element->setAttribute('type', 'service_locator');
+ $this->convertParameters($value->getValues(), $type, $element, 'key');
} elseif ($value instanceof Reference) {
$element->setAttribute('type', 'service');
$element->setAttribute('id', (string) $value);
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
index 73f29a6145..ce53c602db 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
@@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
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\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
@@ -233,6 +234,8 @@ class YamlDumper extends Dumper
}
if ($value instanceof IteratorArgument) {
$tag = 'iterator';
+ } elseif ($value instanceof ServiceLocatorArgument) {
+ $tag = 'service_locator';
} else {
throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_class($value)));
}
diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php
index 8ea04119d8..f002ee2f38 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
@@ -92,6 +93,16 @@ function inline(string $class = null): InlineServiceConfigurator
return new InlineServiceConfigurator(new Definition($class));
}
+/**
+ * Creates a service locator.
+ *
+ * @param ReferenceConfigurator[] $values
+ */
+function service_locator(array $values): ServiceLocatorArgument
+{
+ return new ServiceLocatorArgument(AbstractConfigurator::processValue($values, true));
+}
+
/**
* Creates a lazy iterator.
*
diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
index 9fbff021d6..71e991c9ff 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
@@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -510,7 +511,15 @@ class XmlFileLoader extends FileLoader
try {
$arguments[$key] = new IteratorArgument($arg);
} catch (InvalidArgumentException $e) {
- throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file));
+ throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" only accepts collections of type="service" references in "%s".', $name, $file));
+ }
+ break;
+ case 'service_locator':
+ $arg = $this->getArgumentsAsPhp($arg, $name, $file, false);
+ try {
+ $arguments[$key] = new ServiceLocatorArgument($arg);
+ } catch (InvalidArgumentException $e) {
+ throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" only accepts maps of type="service" references in "%s".', $name, $file));
}
break;
case 'tagged':
diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
index 6b9f12e515..11eb8e45c7 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\Alias;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -691,6 +692,17 @@ class YamlFileLoader extends FileLoader
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file));
}
}
+ if ('service_locator' === $value->getTag()) {
+ if (!is_array($argument)) {
+ throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file));
+ }
+ $argument = $this->resolveServices($argument, $file, $isParameter);
+ try {
+ return new ServiceLocatorArgument($argument);
+ } catch (InvalidArgumentException $e) {
+ throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file));
+ }
+ }
if ('tagged' === $value->getTag()) {
if (!is_string($argument) || !$argument) {
throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string 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 015f81f953..21e3b59310 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
@@ -259,6 +259,7 @@