diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 006f7fe269230..81d7233811f1b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -13,6 +13,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -388,6 +389,10 @@ private function describeValue($value, $omitTags, $showArguments) return $data; } + if ($value instanceof ServiceClosureArgument) { + $value = $value->getValues()[0]; + } + if ($value instanceof Reference) { return array( 'type' => 'service', diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index a42e58c895a83..06d8e1588b4d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -324,6 +325,9 @@ protected function describeContainerDefinition(Definition $definition, array $op $argumentsInformation = array(); if ($showArguments && ($arguments = $definition->getArguments())) { foreach ($arguments as $argument) { + if ($argument instanceof ServiceClosureArgument) { + $argument = $argument->getValues()[0]; + } if ($argument instanceof Reference) { $argumentsInformation[] = sprintf('Service(%s)', (string) $argument); } elseif ($argument instanceof IteratorArgument) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index f9c03bef916a6..91d1bd424f53e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -425,6 +426,10 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom) $argumentXML->setAttribute('key', $argumentKey); } + if ($argument instanceof ServiceClosureArgument) { + $argument = $argument->getValues()[0]; + } + if ($argument instanceof Reference) { $argumentXML->setAttribute('type', 'service'); $argumentXML->setAttribute('id', (string) $argument); diff --git a/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php b/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php new file mode 100644 index 0000000000000..2d52ad91919d2 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php @@ -0,0 +1,46 @@ + + * + * 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\Reference; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Represents a service wrapped in a memoizing closure. + * + * @author Nicolas Grekas
+ *
+ * @experimental in version 3.3
+ */
+class ServiceClosureArgument implements ArgumentInterface
+{
+ private $values;
+
+ public function __construct(Reference $reference)
+ {
+ $this->values = array($reference);
+ }
+
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ public function setValues(array $values)
+ {
+ if (array(0) !== array_keys($values) || !($values[0] instanceof Reference || null === $values[0])) {
+ throw new InvalidArgumentException('A ServiceClosureArgument must hold one and only one Reference.');
+ }
+
+ $this->values = $values;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index 70a417268d8e9..57128a39ebab7 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -4,6 +4,7 @@ CHANGELOG
3.3.0
-----
+ * [EXPERIMENTAL] added "TypedReference" and "ServiceClosureArgument" for creating service-locator services
* [EXPERIMENTAL] added "instanceof" section for local interface-defined configs
* added "service-locator" argument for lazy loading a set of identified values and services
* [EXPERIMENTAL] added prototype services for PSR4-based discovery and registration
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
index a39ae7577eeed..42d4fba96d04a 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
@@ -17,6 +17,7 @@
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\LazyProxy\InheritanceProxyHelper;
use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\TypedReference;
/**
* Guesses constructor arguments of services definitions and try to instantiate services if necessary.
@@ -39,6 +40,7 @@ class AutowirePass extends AbstractRecursivePass
private $types;
private $ambiguousServiceTypes = array();
private $usedTypes = array();
+ private $currentDefinition;
/**
* {@inheritdoc}
@@ -100,43 +102,55 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
*/
protected function processValue($value, $isRoot = false)
{
- if (!$value instanceof Definition || !$value->isAutowired()) {
- return parent::processValue($value, $isRoot);
+ if ($value instanceof TypedReference && $this->currentDefinition->isAutowired() && !$this->container->has((string) $value)) {
+ if ($ref = $this->getAutowiredReference($value->getType(), $value->canBeAutoregistered())) {
+ $value = new TypedReference((string) $ref, $value->getType(), $value->getInvalidBehavior(), $value->canBeAutoregistered());
+ }
}
-
- if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
+ if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
- $autowiredMethods = $this->getMethodsToAutowire($reflectionClass);
- $methodCalls = $value->getMethodCalls();
+ $parentDefinition = $this->currentDefinition;
+ $this->currentDefinition = $value;
- if ($constructor = $reflectionClass->getConstructor()) {
- array_unshift($methodCalls, array($constructor->name, $value->getArguments()));
- } elseif ($value->getArguments()) {
- throw new RuntimeException(sprintf('Cannot autowire service "%s": class %s has no constructor but arguments are defined.', $this->currentId, $reflectionClass->name));
- }
+ try {
+ if (!$value->isAutowired() || !$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
+ return parent::processValue($value, $isRoot);
+ }
+
+ $autowiredMethods = $this->getMethodsToAutowire($reflectionClass);
+ $methodCalls = $value->getMethodCalls();
+
+ if ($constructor = $reflectionClass->getConstructor()) {
+ array_unshift($methodCalls, array($constructor->name, $value->getArguments()));
+ } elseif ($value->getArguments()) {
+ throw new RuntimeException(sprintf('Cannot autowire service "%s": class %s has no constructor but arguments are defined.', $this->currentId, $reflectionClass->name));
+ }
- $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls, $autowiredMethods);
- $overriddenGetters = $this->autowireOverridenGetters($value->getOverriddenGetters(), $autowiredMethods);
+ $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls, $autowiredMethods);
+ $overriddenGetters = $this->autowireOverridenGetters($value->getOverriddenGetters(), $autowiredMethods);
- if ($constructor) {
- list(, $arguments) = array_shift($methodCalls);
+ if ($constructor) {
+ list(, $arguments) = array_shift($methodCalls);
- if ($arguments !== $value->getArguments()) {
- $value->setArguments($arguments);
+ if ($arguments !== $value->getArguments()) {
+ $value->setArguments($arguments);
+ }
}
- }
- if ($methodCalls !== $value->getMethodCalls()) {
- $value->setMethodCalls($methodCalls);
- }
+ if ($methodCalls !== $value->getMethodCalls()) {
+ $value->setMethodCalls($methodCalls);
+ }
- if ($overriddenGetters !== $value->getOverriddenGetters()) {
- $value->setOverriddenGetters($overriddenGetters);
- }
+ if ($overriddenGetters !== $value->getOverriddenGetters()) {
+ $value->setOverriddenGetters($overriddenGetters);
+ }
- return parent::processValue($value, $isRoot);
+ return parent::processValue($value, $isRoot);
+ } finally {
+ $this->currentDefinition = $parentDefinition;
+ }
}
/**
@@ -465,7 +479,7 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint)
$this->populateAvailableType($argumentId, $argumentDefinition);
- $this->processValue($argumentDefinition);
+ $this->processValue($argumentDefinition, true);
$this->currentId = $currentId;
return new Reference($argumentId);
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php
index d705e3aaf3797..da0f13e14aee0 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
@@ -53,7 +54,9 @@ public function process(ContainerBuilder $container)
*/
private function processValue($value, $rootLevel = 0, $level = 0)
{
- if ($value instanceof ArgumentInterface) {
+ if ($value instanceof ServiceClosureArgument) {
+ $value->setValues($this->processValue($value->getValues(), 1, 1));
+ } elseif ($value instanceof ArgumentInterface) {
$value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level));
} elseif ($value instanceof Definition) {
if ($value->isSynthetic() || $value->isAbstract()) {
diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
index 074f942a75aa7..e8f33fc8ad619 100644
--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -15,6 +15,7 @@
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Compiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -1140,11 +1141,16 @@ public function resolveServices($value)
foreach ($value as $k => $v) {
$value[$k] = $this->resolveServices($v);
}
+ } elseif ($value instanceof ServiceClosureArgument) {
+ $reference = $value->getValues()[0];
+ $value = function () use ($reference) {
+ return $this->resolveServices($reference);
+ };
} elseif ($value instanceof ServiceLocatorArgument) {
$parameterBag = $this->getParameterBag();
$services = array();
foreach ($value->getValues() as $k => $v) {
- if ($v->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE && !$this->has((string) $v)) {
+ if ($v && $v->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE && !$this->has((string) $v)) {
continue;
}
$services[$k] = function () use ($v, $parameterBag) {
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index 014edf8dbce9e..b4de1a058d20e 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -14,6 +14,7 @@
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Variable;
use Symfony\Component\DependencyInjection\Definition;
@@ -21,6 +22,7 @@
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -1540,10 +1542,12 @@ private function dumpValue($value, $interpolate = true)
}
return sprintf('array(%s)', implode(', ', $code));
+ } elseif ($value instanceof ServiceClosureArgument) {
+ return $this->dumpServiceClosure($value->getValues()[0], $interpolate, false);
} elseif ($value instanceof ServiceLocatorArgument) {
$code = "\n";
foreach ($value->getValues() as $k => $v) {
- $code .= sprintf(" %s => function () { return %s; },\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
+ $code .= sprintf(" %s => %s,\n", $this->dumpValue($k, $interpolate), $this->dumpServiceClosure($v, $interpolate, true));
}
$code .= ' ';
@@ -1681,6 +1685,27 @@ private function dumpValue($value, $interpolate = true)
return $this->export($value);
}
+ private function dumpServiceClosure(Reference $reference, $interpolate, $oneLine)
+ {
+ $type = '';
+ if (PHP_VERSION_ID >= 70000 && $reference instanceof TypedReference) {
+ $type = $reference->getType();
+ if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $reference->getInvalidBehavior()) {
+ $type = ': \\'.$type;
+ } elseif (PHP_VERSION_ID >= 70100) {
+ $type = ': ?\\'.$type;
+ } else {
+ $type = '';
+ }
+ }
+
+ if ($oneLine) {
+ return sprintf('function ()%s { return %s; }', $type, $this->dumpValue($reference, $interpolate));
+ }
+
+ return sprintf("function ()%s {\n return %s;\n }", $type, $this->dumpValue($reference, $interpolate));
+ }
+
/**
* Dumps a string to a literal (aka PHP Code) class value.
*
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
index 2d1b65b50e408..1490e97c96a3e 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
@@ -13,6 +13,7 @@
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Parameter;
@@ -289,6 +290,9 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
$element->setAttribute($keyAttribute, $key);
}
+ if ($value instanceof ServiceClosureArgument) {
+ $value = $value->getValues()[0];
+ }
if (is_array($value)) {
$element->setAttribute('type', 'collection');
$this->convertParameters($value, $type, $element, 'key');
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
index 10592131f3b21..d708ea5f7eaa9 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
@@ -17,6 +17,7 @@
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
@@ -254,6 +255,9 @@ private function dumpCallable($callable)
*/
private function dumpValue($value)
{
+ if ($value instanceof ServiceClosureArgument) {
+ $value = $value->getValues()[0];
+ }
if ($value instanceof ArgumentInterface) {
if ($value instanceof IteratorArgument) {
$tag = 'iterator';
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
index ccb7c66c81a0a..4df3a611a5c2a 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
@@ -18,6 +18,7 @@
use Symfony\Component\DependencyInjection\Tests\Fixtures\AbstractGetterOverriding;
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
use Symfony\Component\DependencyInjection\Tests\Fixtures\GetterOverriding;
+use Symfony\Component\DependencyInjection\TypedReference;
/**
* @author Kévin Dunglas
+ *
+ * @experimental in version 3.3
+ */
+class TypedReference extends Reference
+{
+ private $type;
+ private $canBeAutoregistered;
+
+ /**
+ * @param string $id The service identifier
+ * @param string $type The PHP type of the identified service
+ * @param int $invalidBehavior The behavior when the service does not exist
+ * @param bool $canBeAutoregistered Whether autowiring can autoregister the referenced service when it's a FQCN or not
+ */
+ public function __construct($id, $type, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $canBeAutoregistered = true)
+ {
+ parent::__construct($id, $invalidBehavior);
+ $this->type = $type;
+ $this->canBeAutoregistered = $canBeAutoregistered;
+ }
+
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function canBeAutoregistered()
+ {
+ return $this->canBeAutoregistered;
+ }
+}