Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d905d51

Browse filesBrowse files
[DI] Allow creating ServiceLocator-based services in extensions
1 parent 6bfa483 commit d905d51
Copy full SHA for d905d51

19 files changed

+602
-31
lines changed

‎src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\DependencyInjection\Alias;
1515
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
16+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718
use Symfony\Component\DependencyInjection\Definition;
1819
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@@ -388,6 +389,10 @@ private function describeValue($value, $omitTags, $showArguments)
388389
return $data;
389390
}
390391

392+
if ($value instanceof ServiceClosureArgument) {
393+
$value = $value->getValues()[0];
394+
}
395+
391396
if ($value instanceof Reference) {
392397
return array(
393398
'type' => 'service',

‎src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\DependencyInjection\Alias;
1717
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1818
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
19+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1920
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
2021
use Symfony\Component\DependencyInjection\ContainerBuilder;
2122
use Symfony\Component\DependencyInjection\Definition;
@@ -324,6 +325,9 @@ protected function describeContainerDefinition(Definition $definition, array $op
324325
$argumentsInformation = array();
325326
if ($showArguments && ($arguments = $definition->getArguments())) {
326327
foreach ($arguments as $argument) {
328+
if ($argument instanceof ServiceClosureArgument) {
329+
$argument = $argument->getValues()[0];
330+
}
327331
if ($argument instanceof Reference) {
328332
$argumentsInformation[] = sprintf('Service(%s)', (string) $argument);
329333
} elseif ($argument instanceof IteratorArgument) {

‎src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\Alias;
1515
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1616
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
17+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1718
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
1920
use Symfony\Component\DependencyInjection\Definition;
@@ -425,6 +426,10 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom)
425426
$argumentXML->setAttribute('key', $argumentKey);
426427
}
427428

429+
if ($argument instanceof ServiceClosureArgument) {
430+
$argument = $argument->getValues()[0];
431+
}
432+
428433
if ($argument instanceof Reference) {
429434
$argumentXML->setAttribute('type', 'service');
430435
$argumentXML->setAttribute('id', (string) $argument);
+46Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
use Symfony\Component\DependencyInjection\Reference;
15+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16+
17+
/**
18+
* Represents a service wrapped in a memoizing closure.
19+
*
20+
* @author Nicolas Grekas <p@tchwork.com>
21+
*
22+
* @experimental in version 3.3
23+
*/
24+
class ServiceClosureArgument implements ArgumentInterface
25+
{
26+
private $values;
27+
28+
public function __construct(Reference $reference)
29+
{
30+
$this->values = array($reference);
31+
}
32+
33+
public function getValues()
34+
{
35+
return $this->values;
36+
}
37+
38+
public function setValues(array $values)
39+
{
40+
if (array(0) !== array_keys($values) || !($values[0] instanceof Reference || null === $values[0])) {
41+
throw new InvalidArgumentException('A ServiceClosureArgument must hold one and only one Reference.');
42+
}
43+
44+
$this->values = $values;
45+
}
46+
}

‎src/Symfony/Component/DependencyInjection/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.3.0
55
-----
66

7+
* [EXPERIMENTAL] added "TypedReference" and "ServiceClosureArgument" for creating service-locator services
78
* [EXPERIMENTAL] added "instanceof" section for local interface-defined configs
89
* added "service-locator" argument for lazy loading a set of identified values and services
910
* [EXPERIMENTAL] added prototype services for PSR4-based discovery and registration

‎src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+40-26Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1818
use Symfony\Component\DependencyInjection\LazyProxy\InheritanceProxyHelper;
1919
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\DependencyInjection\TypedReference;
2021

2122
/**
2223
* Guesses constructor arguments of services definitions and try to instantiate services if necessary.
@@ -39,6 +40,7 @@ class AutowirePass extends AbstractRecursivePass
3940
private $types;
4041
private $ambiguousServiceTypes = array();
4142
private $usedTypes = array();
43+
private $currentDefinition;
4244

4345
/**
4446
* {@inheritdoc}
@@ -100,43 +102,55 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
100102
*/
101103
protected function processValue($value, $isRoot = false)
102104
{
103-
if (!$value instanceof Definition || !$value->isAutowired()) {
104-
return parent::processValue($value, $isRoot);
105+
if ($value instanceof TypedReference && $this->currentDefinition->isAutowired() && !$this->container->has((string) $value)) {
106+
if ($ref = $this->getAutowiredReference($value->getType(), $value->canBeAutoregistered())) {
107+
$value = new TypedReference((string) $ref, $value->getType());
108+
}
105109
}
106-
107-
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
110+
if (!$value instanceof Definition) {
108111
return parent::processValue($value, $isRoot);
109112
}
110113

111-
$autowiredMethods = $this->getMethodsToAutowire($reflectionClass);
112-
$methodCalls = $value->getMethodCalls();
114+
$parentDefinition = $this->currentDefinition;
115+
$this->currentDefinition = $value;
113116

114-
if ($constructor = $reflectionClass->getConstructor()) {
115-
array_unshift($methodCalls, array($constructor->name, $value->getArguments()));
116-
} elseif ($value->getArguments()) {
117-
throw new RuntimeException(sprintf('Cannot autowire service "%s": class %s has no constructor but arguments are defined.', $this->currentId, $reflectionClass->name));
118-
}
117+
try {
118+
if (!$value->isAutowired() || !$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
119+
return parent::processValue($value, $isRoot);
120+
}
121+
122+
$autowiredMethods = $this->getMethodsToAutowire($reflectionClass);
123+
$methodCalls = $value->getMethodCalls();
124+
125+
if ($constructor = $reflectionClass->getConstructor()) {
126+
array_unshift($methodCalls, array($constructor->name, $value->getArguments()));
127+
} elseif ($value->getArguments()) {
128+
throw new RuntimeException(sprintf('Cannot autowire service "%s": class %s has no constructor but arguments are defined.', $this->currentId, $reflectionClass->name));
129+
}
119130

120-
$methodCalls = $this->autowireCalls($reflectionClass, $methodCalls, $autowiredMethods);
121-
$overriddenGetters = $this->autowireOverridenGetters($value->getOverriddenGetters(), $autowiredMethods);
131+
$methodCalls = $this->autowireCalls($reflectionClass, $methodCalls, $autowiredMethods);
132+
$overriddenGetters = $this->autowireOverridenGetters($value->getOverriddenGetters(), $autowiredMethods);
122133

123-
if ($constructor) {
124-
list(, $arguments) = array_shift($methodCalls);
134+
if ($constructor) {
135+
list(, $arguments) = array_shift($methodCalls);
125136

126-
if ($arguments !== $value->getArguments()) {
127-
$value->setArguments($arguments);
137+
if ($arguments !== $value->getArguments()) {
138+
$value->setArguments($arguments);
139+
}
128140
}
129-
}
130141

131-
if ($methodCalls !== $value->getMethodCalls()) {
132-
$value->setMethodCalls($methodCalls);
133-
}
142+
if ($methodCalls !== $value->getMethodCalls()) {
143+
$value->setMethodCalls($methodCalls);
144+
}
134145

135-
if ($overriddenGetters !== $value->getOverriddenGetters()) {
136-
$value->setOverriddenGetters($overriddenGetters);
137-
}
146+
if ($overriddenGetters !== $value->getOverriddenGetters()) {
147+
$value->setOverriddenGetters($overriddenGetters);
148+
}
138149

139-
return parent::processValue($value, $isRoot);
150+
return parent::processValue($value, $isRoot);
151+
} finally {
152+
$this->currentDefinition = $parentDefinition;
153+
}
140154
}
141155

142156
/**
@@ -465,7 +479,7 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint)
465479

466480
$this->populateAvailableType($argumentId, $argumentDefinition);
467481

468-
$this->processValue($argumentDefinition);
482+
$this->processValue($argumentDefinition, true);
469483
$this->currentId = $currentId;
470484

471485
return new Reference($argumentId);

‎src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1516
use Symfony\Component\DependencyInjection\ContainerInterface;
1617
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\Reference;
@@ -53,7 +54,9 @@ public function process(ContainerBuilder $container)
5354
*/
5455
private function processValue($value, $rootLevel = 0, $level = 0)
5556
{
56-
if ($value instanceof ArgumentInterface) {
57+
if ($value instanceof ServiceClosureArgument) {
58+
$value->setValues($this->processValue($value->getValues(), 1, 1));
59+
} elseif ($value instanceof ArgumentInterface) {
5760
$value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level));
5861
} elseif ($value instanceof Definition) {
5962
if ($value->isSynthetic() || $value->isAbstract()) {

‎src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1616
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1717
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
18+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1819
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1920
use Symfony\Component\DependencyInjection\Compiler\Compiler;
2021
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -1140,11 +1141,16 @@ public function resolveServices($value)
11401141
foreach ($value as $k => $v) {
11411142
$value[$k] = $this->resolveServices($v);
11421143
}
1144+
} elseif ($value instanceof ServiceClosureArgument) {
1145+
$reference = $value->getValues()[0];
1146+
$value = function () use ($reference) {
1147+
return $this->resolveServices($reference);
1148+
};
11431149
} elseif ($value instanceof ServiceLocatorArgument) {
11441150
$parameterBag = $this->getParameterBag();
11451151
$services = array();
11461152
foreach ($value->getValues() as $k => $v) {
1147-
if ($v->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE && !$this->has((string) $v)) {
1153+
if ($v && $v->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE && !$this->has((string) $v)) {
11481154
continue;
11491155
}
11501156
$services[$k] = function () use ($v, $parameterBag) {

‎src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+26-1Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1515
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1616
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
17+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1718
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1819
use Symfony\Component\DependencyInjection\Variable;
1920
use Symfony\Component\DependencyInjection\Definition;
2021
use Symfony\Component\DependencyInjection\ContainerBuilder;
2122
use Symfony\Component\DependencyInjection\Container;
2223
use Symfony\Component\DependencyInjection\ContainerInterface;
2324
use Symfony\Component\DependencyInjection\Reference;
25+
use Symfony\Component\DependencyInjection\TypedReference;
2426
use Symfony\Component\DependencyInjection\Parameter;
2527
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
2628
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -1540,10 +1542,12 @@ private function dumpValue($value, $interpolate = true)
15401542
}
15411543

15421544
return sprintf('array(%s)', implode(', ', $code));
1545+
} elseif ($value instanceof ServiceClosureArgument) {
1546+
return $this->dumpServiceClosure($value->getValues()[0], $interpolate, false);
15431547
} elseif ($value instanceof ServiceLocatorArgument) {
15441548
$code = "\n";
15451549
foreach ($value->getValues() as $k => $v) {
1546-
$code .= sprintf(" %s => function () { return %s; },\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
1550+
$code .= sprintf(" %s => %s,\n", $this->dumpValue($k, $interpolate), $this->dumpServiceClosure($v, $interpolate, true));
15471551
}
15481552
$code .= ' ';
15491553

@@ -1681,6 +1685,27 @@ private function dumpValue($value, $interpolate = true)
16811685
return $this->export($value);
16821686
}
16831687

1688+
private function dumpServiceClosure(Reference $reference, $interpolate, $oneLine)
1689+
{
1690+
$type = '';
1691+
if (PHP_VERSION_ID >= 70000 && $reference instanceof TypedReference) {
1692+
$type = $reference->getType();
1693+
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $reference->getInvalidBehavior()) {
1694+
$type = ': \\'.$type;
1695+
} elseif (PHP_VERSION_ID >= 70100) {
1696+
$type = ': ?\\'.$type;
1697+
} else {
1698+
$type = '';
1699+
}
1700+
}
1701+
1702+
if ($oneLine) {
1703+
return sprintf('function ()%s { return %s; }', $type, $this->dumpValue($reference, $interpolate));
1704+
}
1705+
1706+
return sprintf("function ()%s {\n return %s;\n }", $type, $this->dumpValue($reference, $interpolate));
1707+
}
1708+
16841709
/**
16851710
* Dumps a string to a literal (aka PHP Code) class value.
16861711
*

‎src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
16+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1617
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1718
use Symfony\Component\DependencyInjection\ContainerInterface;
1819
use Symfony\Component\DependencyInjection\Parameter;
@@ -289,6 +290,9 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
289290
$element->setAttribute($keyAttribute, $key);
290291
}
291292

293+
if ($value instanceof ServiceClosureArgument) {
294+
$value = $value->getValues()[0];
295+
}
292296
if (is_array($value)) {
293297
$element->setAttribute('type', 'collection');
294298
$this->convertParameters($value, $type, $element, 'key');

‎src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1818
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1919
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
20+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
2021
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
2122
use Symfony\Component\DependencyInjection\ContainerInterface;
2223
use Symfony\Component\DependencyInjection\Definition;
@@ -254,6 +255,9 @@ private function dumpCallable($callable)
254255
*/
255256
private function dumpValue($value)
256257
{
258+
if ($value instanceof ServiceClosureArgument) {
259+
$value = $value->getValues()[0];
260+
}
257261
if ($value instanceof ArgumentInterface) {
258262
if ($value instanceof IteratorArgument) {
259263
$tag = 'iterator';

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\DependencyInjection\Tests\Fixtures\AbstractGetterOverriding;
1919
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
2020
use Symfony\Component\DependencyInjection\Tests\Fixtures\GetterOverriding;
21+
use Symfony\Component\DependencyInjection\TypedReference;
2122

2223
/**
2324
* @author Kévin Dunglas <dunglas@gmail.com>
@@ -518,6 +519,22 @@ public function testExplicitMethodInjection()
518519
);
519520
}
520521

522+
public function testTtypedReference()
523+
{
524+
$container = new ContainerBuilder();
525+
526+
$container
527+
->register('bar', Bar::class)
528+
->setAutowired(true)
529+
->setProperty('a', array(new TypedReference(A::class, A::class)))
530+
;
531+
532+
$pass = new AutowirePass();
533+
$pass->process($container);
534+
535+
$this->assertSame(A::class, $container->getDefinition('autowired.'.A::class)->getClass());
536+
}
537+
521538
/**
522539
* @requires PHP 7.0
523540
*/

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.