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 @@ -518,6 +519,22 @@ public function testExplicitMethodInjection() ); } + public function testTtypedReference() + { + $container = new ContainerBuilder(); + + $container + ->register('bar', Bar::class) + ->setAutowired(true) + ->setProperty('a', array(new TypedReference(A::class, A::class))) + ; + + $pass = new AutowirePass(); + $pass->process($container); + + $this->assertSame(A::class, $container->getDefinition('autowired.'.A::class)->getClass()); + } + /** * @requires PHP 7.0 */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInvalidReferencesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInvalidReferencesPassTest.php index e2309a14463d2..03b08db0e1637 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInvalidReferencesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInvalidReferencesPassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Compiler\ResolveInvalidReferencesPass; @@ -121,6 +122,24 @@ public function testProcessRemovesOverriddenGettersOnInvalid() $this->assertEquals(array(), $def->getOverriddenGetters()); } + public function testProcessRemovesArgumentsOnInvalid() + { + $container = new ContainerBuilder(); + $def = $container + ->register('foo') + ->addArgument(array( + array( + new Reference('bar', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), + new ServiceClosureArgument(new Reference('baz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), + ), + )) + ; + + $this->process($container); + + $this->assertSame(array(array(array())), $def->getArguments()); + } + protected function process(ContainerBuilder $container) { $pass = new ResolveInvalidReferencesPass(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index e5405389c53e7..67890faacad54 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -23,6 +23,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\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -34,6 +35,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\Config\Resource\FileResource; @@ -1159,6 +1161,23 @@ public function testNoClassFromNsSeparatorId() $definition = $container->register('\\foo'); $container->compile(); } + + public function testServiceLocator() + { + $container = new ContainerBuilder(); + $container->register('foo_service', ServiceLocator::class) + ->addArgument(array( + 'bar' => new ServiceClosureArgument(new Reference('bar_service')), + 'baz' => new ServiceClosureArgument(new TypedReference('baz_service', 'stdClass')), + )) + ; + $container->register('bar_service', 'stdClass')->setArguments(array(new Reference('baz_service'))); + $container->register('baz_service', 'stdClass')->setPublic(false); + $container->compile(); + + $this->assertInstanceOf(ServiceLocator::class, $foo = $container->get('foo_service')); + $this->assertSame($container->get('bar_service'), $foo->get('bar')); + } } class FooClass diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index a7a1560a14abc..1a51c8f44caef 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -15,12 +15,14 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; @@ -630,4 +632,23 @@ public function testDumpContainerBuilderWithFrozenConstructorIncludingPrivateSer $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_private_frozen.php', $dumper->dump()); } + + public function testServiceLocator() + { + $container = new ContainerBuilder(); + $container->register('foo_service', ServiceLocator::class) + ->addArgument(array( + 'bar' => new ServiceClosureArgument(new Reference('bar_service')), + 'baz' => new ServiceClosureArgument(new TypedReference('baz_service', 'stdClass')), + )) + ; + $container->register('bar_service', 'stdClass')->setArguments(array(new Reference('baz_service'))); + $container->register('baz_service', 'stdClass')->setPublic(false); + $container->compile(); + + $dumper = new PhpDumper($container); + + $suffix = PHP_VERSION_ID >= 70100 ? '71' : (PHP_VERSION_ID >= 70000 ? '70' : '55'); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_locator_php'.$suffix.'.php', $dumper->dump()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php55.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php55.php new file mode 100644 index 0000000000000..75f87de16cc34 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php55.php @@ -0,0 +1,110 @@ +services = array(); + $this->normalizedIds = array( + 'psr\\container\\containerinterface' => 'Psr\\Container\\ContainerInterface', + 'symfony\\component\\dependencyinjection\\container' => 'Symfony\\Component\\DependencyInjection\\Container', + 'symfony\\component\\dependencyinjection\\containerinterface' => 'Symfony\\Component\\DependencyInjection\\ContainerInterface', + ); + $this->methodMap = array( + 'bar_service' => 'getBarServiceService', + 'baz_service' => 'getBazServiceService', + 'foo_service' => 'getFooServiceService', + ); + $this->privates = array( + 'baz_service' => true, + ); + + $this->aliases = array(); + } + + /** + * {@inheritdoc} + */ + public function compile() + { + throw new LogicException('You cannot compile a dumped frozen container.'); + } + + /** + * {@inheritdoc} + */ + public function isFrozen() + { + return true; + } + + /** + * Gets the 'bar_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getBarServiceService() + { + return $this->services['bar_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->getBazServiceService()) && false ?: '_'}); + } + + /** + * Gets the 'foo_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \Symfony\Component\DependencyInjection\ServiceLocator A Symfony\Component\DependencyInjection\ServiceLocator instance + */ + protected function getFooServiceService() + { + return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('bar' => function () { + return ${($_ = isset($this->services['bar_service']) ? $this->services['bar_service'] : $this->get('bar_service')) && false ?: '_'}; + }, 'baz' => function () { + return ${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->getBazServiceService()) && false ?: '_'}; + })); + } + + /** + * Gets the 'baz_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * This service is private. + * If you want to be able to request this service from the container directly, + * make it public, otherwise you might end up with broken code. + * + * @return \stdClass A stdClass instance + */ + protected function getBazServiceService() + { + return $this->services['baz_service'] = new \stdClass(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php70.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php70.php new file mode 100644 index 0000000000000..ef9bda512d3fd --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php70.php @@ -0,0 +1,110 @@ +services = array(); + $this->normalizedIds = array( + 'psr\\container\\containerinterface' => 'Psr\\Container\\ContainerInterface', + 'symfony\\component\\dependencyinjection\\container' => 'Symfony\\Component\\DependencyInjection\\Container', + 'symfony\\component\\dependencyinjection\\containerinterface' => 'Symfony\\Component\\DependencyInjection\\ContainerInterface', + ); + $this->methodMap = array( + 'bar_service' => 'getBarServiceService', + 'baz_service' => 'getBazServiceService', + 'foo_service' => 'getFooServiceService', + ); + $this->privates = array( + 'baz_service' => true, + ); + + $this->aliases = array(); + } + + /** + * {@inheritdoc} + */ + public function compile() + { + throw new LogicException('You cannot compile a dumped frozen container.'); + } + + /** + * {@inheritdoc} + */ + public function isFrozen() + { + return true; + } + + /** + * Gets the 'bar_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getBarServiceService() + { + return $this->services['bar_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->getBazServiceService()) && false ?: '_'}); + } + + /** + * Gets the 'foo_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \Symfony\Component\DependencyInjection\ServiceLocator A Symfony\Component\DependencyInjection\ServiceLocator instance + */ + protected function getFooServiceService() + { + return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('bar' => function () { + return ${($_ = isset($this->services['bar_service']) ? $this->services['bar_service'] : $this->get('bar_service')) && false ?: '_'}; + }, 'baz' => function (): \stdClass { + return ${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->getBazServiceService()) && false ?: '_'}; + })); + } + + /** + * Gets the 'baz_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * This service is private. + * If you want to be able to request this service from the container directly, + * make it public, otherwise you might end up with broken code. + * + * @return \stdClass A stdClass instance + */ + protected function getBazServiceService() + { + return $this->services['baz_service'] = new \stdClass(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php71.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php71.php new file mode 100644 index 0000000000000..ef9bda512d3fd --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator_php71.php @@ -0,0 +1,110 @@ +services = array(); + $this->normalizedIds = array( + 'psr\\container\\containerinterface' => 'Psr\\Container\\ContainerInterface', + 'symfony\\component\\dependencyinjection\\container' => 'Symfony\\Component\\DependencyInjection\\Container', + 'symfony\\component\\dependencyinjection\\containerinterface' => 'Symfony\\Component\\DependencyInjection\\ContainerInterface', + ); + $this->methodMap = array( + 'bar_service' => 'getBarServiceService', + 'baz_service' => 'getBazServiceService', + 'foo_service' => 'getFooServiceService', + ); + $this->privates = array( + 'baz_service' => true, + ); + + $this->aliases = array(); + } + + /** + * {@inheritdoc} + */ + public function compile() + { + throw new LogicException('You cannot compile a dumped frozen container.'); + } + + /** + * {@inheritdoc} + */ + public function isFrozen() + { + return true; + } + + /** + * Gets the 'bar_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getBarServiceService() + { + return $this->services['bar_service'] = new \stdClass(${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->getBazServiceService()) && false ?: '_'}); + } + + /** + * Gets the 'foo_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \Symfony\Component\DependencyInjection\ServiceLocator A Symfony\Component\DependencyInjection\ServiceLocator instance + */ + protected function getFooServiceService() + { + return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('bar' => function () { + return ${($_ = isset($this->services['bar_service']) ? $this->services['bar_service'] : $this->get('bar_service')) && false ?: '_'}; + }, 'baz' => function (): \stdClass { + return ${($_ = isset($this->services['baz_service']) ? $this->services['baz_service'] : $this->getBazServiceService()) && false ?: '_'}; + })); + } + + /** + * Gets the 'baz_service' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * This service is private. + * If you want to be able to request this service from the container directly, + * make it public, otherwise you might end up with broken code. + * + * @return \stdClass A stdClass instance + */ + protected function getBazServiceService() + { + return $this->services['baz_service'] = new \stdClass(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/TypedReference.php b/src/Symfony/Component/DependencyInjection/TypedReference.php new file mode 100644 index 0000000000000..7285070ea925a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/TypedReference.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Represents a PHP type-hinted service reference. + * + * @author Nicolas Grekas + * + * @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; + } +}