8000 [DI] Allow creating ServiceLocator-based services in extensions · symfony/symfony@490745f · GitHub
[go: up one dir, main page]

Skip to content

Commit 490745f

Browse files
[DI] Allow creating ServiceLocator-based services in extensions
1 parent 75dffd1 commit 490745f

17 files changed

+545
-5
lines changed

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

Lines 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

Lines 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

Lines 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);
Lines 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

Lines 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

Lines 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

Lines 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

Lines 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+
10000 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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