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 a8e0b24

Browse filesBrowse files
[DI] Allow creating ServiceLocator-based services in extensions
1 parent 75dffd1 commit a8e0b24
Copy full SHA for a8e0b24

18 files changed

+540
-7
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/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/ResolveInvalidReferencesPassTest.php

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

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1516
use Symfony\Component\DependencyInjection\ContainerInterface;
1617
use Symfony\Component\DependencyInjection\Reference;
1718
use Symfony\Component\DependencyInjection\Compiler\ResolveInvalidReferencesPass;
@@ -121,6 +122,24 @@ public function testProcessRemovesOverriddenGettersOnInvalid()
121122
$this->assertEquals(array(), $def->getOverriddenGetters());
122123
}
123124

125+
public function testProcessRemovesArgumentsOnInvalid()
126+
{
127+
$container = new ContainerBuilder();
128+
$def = $container
129+
->register('foo')
130+
->addArgument(array(
131+
array(
132+
new Reference('bar', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
133+
new ServiceClosureArgument(new Reference('baz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
134+
),
135+
))
136+
;
137+
138+
$this->process($container);
139+
140+
$this->assertSame(array(array(array())), $def->getArguments());
141+
}
142+
124143
protected function process(ContainerBuilder $container)
125144
{
126145
$pass = new ResolveInvalidReferencesPass();

‎src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
2424
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
2525
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
26+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
2627
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
2728
use Symfony\Component\DependencyInjection\ChildDefinition;
2829
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
@@ -34,6 +35,7 @@
3435
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
3536
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
3637
use Symfony\Component\DependencyInjection\Reference;
38+
use Symfony\Component\DependencyInjection\TypedReference;
3739
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
3840
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
3941
use Symfony\Component\Config\Resource\FileResource;
@@ -1159,6 +1161,23 @@ public function testNoClassFromNsSeparatorId()
11591161
$definition = $container->register('\\foo');
11601162
$container->compile();
11611163
}
1164+
1165+
public function testServiceLocator()
1166+
{
1167+
$container = new ContainerBuilder();
1168+
$container->register('foo_service', ServiceLocator::class)
1169+
->addArgument(array(
1170+
'bar' => new ServiceClosureArgument(new Reference('bar_service')),
1171+
'baz' => new ServiceClosureArgument(new TypedReference('baz_service', 'stdClass')),
1172+
))
1173+
;
1174+
$container->register('bar_service', 'stdClass')->setArguments(array(new Reference('baz_service')));
1175+
$container->register('baz_service', 'stdClass')->setPublic(false);
1176+
$container->compile();
1177+
1178+
$this->assertInstanceOf(ServiceLocator::class, $foo = $container->get('foo_service'));
1179+
$this->assertSame($container->get('bar_service'), $foo->get('bar'));
1180+
}
11621181
}
11631182

11641183
class FooClass

‎src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+23-2Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
use PHPUnit\Framework\TestCase;
1616
use Symfony\Component\Config\FileLocator;
1717
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
18-
use Symfony\Component\DependencyInjection\ContainerBuilder;
19-
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
2018
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
19+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
2120
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
21+
use Symfony\Component\DependencyInjection\ContainerBuilder;
22+
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
2223
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
2324
use Symfony\Component\DependencyInjection\Reference;
25+
use Symfony\Component\DependencyInjection\TypedReference;
2426
use Symfony\Component\DependencyInjection\Definition;
2527
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2628
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
@@ -630,4 +632,23 @@ public function testDumpContainerBuilderWithFrozenConstructorIncludingPrivateSer
630632

631633
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_private_frozen.php', $dumper->dump());
632634
}
635+
636+
public function testServiceLocator()
637+
{
638+
$container = new ContainerBuilder();
639+
$container->register('foo_service', ServiceLocator::class)
640+
->addArgument(array(
641+
'bar' => new ServiceClosureArgument(new Reference('bar_service')),
642+
'baz' => new ServiceClosureArgument(new TypedReference('baz_service', 'stdClass')),
643+
))
644+
;
645+
$container->register('bar_service', 'stdClass')->setArguments(array(new Reference('baz_service')));
646+
$container->register('baz_service', 'stdClass')->setPublic(false);
647+
$container->compile();
648+
649+
$dumper = new PhpDumper($container);
650+
651+
$suffix = PHP_VERSION_ID >= 70100 ? '71' : (PHP_VERSION_ID >= 70000 ? '70' : '55');
652+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_locator_php'.$suffix.'.php', $dumper->dump());
653+
}
633654
}

0 commit comments

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