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

Skip to content

Commit 4cf0b83

Browse files
[DI] Allow creating ServiceLocator-based services in extensions
1 parent 3729a15 commit 4cf0b83

16 files changed

+597
-3
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;
@@ -399,6 +400,10 @@ private function describeValue($value, $omitTags, $showArguments)
399400
);
400401
}
401402

403+
if ($value instanceof ServiceClosureArgument) {
404+
return $this->describeValue($value->getValues()[0], $omitTags, $showArguments);
405+
}
406+
402407
if ($value instanceof ArgumentInterface) {
403408
return $this->describeValue($value->getValues(), $omitTags, $showArguments);
404409
}

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

Lines changed: 3 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;
@@ -332,6 +333,8 @@ protected function describeContainerDefinition(Definition $definition, array $op
332333
$argumentsInformation[] = sprintf('Service(%s)', (string) $argument);
333334
} elseif ($argument instanceof IteratorArgument) {
334335
$argumentsInformation[] = sprintf('Iterator (%d element(s))', count($argument->getValues()));
336+
} elseif ($argument instanceof ServiceClosureArgument) {
337+
$argumentsInformation[] = sprintf('Service(%s)', (string) $argument->getValues()[0]);
335338
} elseif ($argument instanceof ServiceLocatorArgument) {
336339
$argumentsInformation[] = sprintf('ServiceLocator (%d service(s))', count($argument->getValues()));
337340
} elseif ($argument instanceof ClosureProxyArgument) {

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

Lines changed: 4 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;
@@ -446,6 +447,9 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom)
446447
foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) {
447448
$argumentXML->appendChild($childArgumentXML);
448449
}
450+
} elseif ($argument instanceof ServiceClosureArgument) {
451+
$argumentXML->setAttribute('type', 'service');
452+
$argumentXML->setAttribute('id', (string) $argument->getValues()[0]);
449453
} elseif ($argument instanceof ServiceLocatorArgument) {
450454
$argumentXML->setAttribute('type', 'service-locator');
451455

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+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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;
13+
14+
/**
15+
* Represents a typed service reference that can be autowired if the target is invalid.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*
19+
* @experimental in version 3.3
20+
*/
21+
class AutowirableReference extends Reference
22+
{
23+
private $id;
24+
private $type;
25+
private $autoRegister;
26+
27+
/**
28+
* @param string $id The service identifier
29+
* @param string $type The type of the identified service
30+
* @param int $invalidBehavior The behavior when the service does not exist
31+
* @param bool $autoRegister Whether new services should be created for discovered classes or not
32+
*
33+
* @see Container
34+
*/
35+
public function __construct($id, $type, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $autoRegister = true)
36+
{
37+
parent::__construct($id, $invalidBehavior);
38+
$this->id = $id;
39+
$this->type = $type;
40+
$this->autoRegister = $autoRegister;
41+
}
42+
43+
public function getType()
44+
{
45+
return $this->type;
46+
}
47+
48+
public function autoRegister()
49+
{
50+
return $this->autoRegister;
51+
}
52+
53+
public function setId($id)
54+
{
55+
$this->id = (string) $id;
56+
}
57+
58+
/**
59+
* {@inheritdoc}
60+
*/
61+
public function __toString()
62+
{
63+
return $this->id;
64+
}
65+
}

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 "AutowirableReference" and "ServiceClosureArgument" for autowirable 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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\AutowirableReference;
1415
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\ContainerInterface;
1618
use Symfony\Component\DependencyInjection\Definition;
1719
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1820
use Symfony\Component\DependencyInjection\LazyProxy\InheritanceProxyHelper;
@@ -100,6 +102,18 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
100102
*/
101103
protected function processValue($value, $isRoot = false)
102104
{
105+
if ($value instanceof AutowirableReference) {
106+
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() || $this->container->has((string) $value)) {
107+
return $value;
108+
}
109+
110+
if ($ref = $this->getAutowiredReference($value->getType(), $value->autoRegister())) {
111+
$value->setId($ref);
112+
}
113+
114+
return $value;
115+
}
116+
103117
if (!$value instanceof Definition || !$value->getAutowiredCalls()) {
104118
return parent::processValue($value, $isRoot);
105119
}

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1616
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
17+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1718
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1819
use Symfony\Component\DependencyInjection\Compiler\Compiler;
1920
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -1128,6 +1129,11 @@ public function resolveServices($value)
11281129
foreach ($value as $k => $v) {
11291130
$value[$k] = $this->resolveServices($v);
11301131
}
1132+
} elseif ($value instanceof ServiceClosureArgument) {
1133+
$reference = $value->getValues()[0];
1134+
$value = function () use ($reference) {
1135+
return $this->get((string) $reference, $reference->getInvalidBehavior());
1136+
};
11311137
} elseif ($value instanceof ServiceLocatorArgument) {
11321138
$parameterBag = $this->getParameterBag();
11331139
$services = array();

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
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;
19+
use Symfony\Component\DependencyInjection\AutowirableReference;
1820
use Symfony\Component\DependencyInjection\Variable;
1921
use Symfony\Component\DependencyInjection\Definition;
2022
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -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 AutowirableReference) {
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

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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ private function dumpCallable($callable)
254254
*/
255255
private function dumpValue($value)
256256
{
257+
if ($value instanceof ServiceClosureArgument) {
258+
$value = $value->getValues()[0];
259+
}
257260
if ($value instanceof ArgumentInterface) {
258261
if ($value instanceof IteratorArgument) {
259262
$tag = 'iterator';

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
2323
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
2424
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
25+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
2526
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
27+
use Symfony\Component\DependencyInjection\AutowirableReference;
2628
use Symfony\Component\DependencyInjection\ChildDefinition;
2729
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
2830
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -1130,6 +1132,25 @@ public function testNoClassFromNsSeparatorId()
11301132
$definition = $container->register('\\foo');
11311133
$container->compile();
11321134
}
1135+
1136+
public function testServiceLocator()
1137+
{
1138+
$container = new ContainerBuilder();
1139+
$container->register('foo_service', ServiceLocator::class)
1140+
->addArgument(array(
1141+
'bar' => new ServiceClosureArgument(new Reference('bar_service')),
1142+
'baz' => new ServiceClosureArgument(new AutowirableReference('baz_service', 'stdClass')),
1143+
'test' => new ServiceClosureArgument(new AutowirableReference(__CLASS__, __CLASS__, ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)),
1144+
))
1145+
;
1146+
$container->register('bar_service', 'stdClass')->setArguments(array(new Reference('baz_service')));
1147+
$container->register('baz_service', 'stdClass')->setPublic(false);
1148+
$container->compile();
1149+
1150+
$this->assertInstanceOf(ServiceLocator::class, $foo = $container->get('foo_service'));
1151+
$this->assertSame($container->get('bar_service'), $foo->get('bar'));
1152+
$this->assertInstanceOf(__CLASS__, $foo->get('test'));
1153+
}
11331154
}
11341155

11351156
class FooClass

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
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\AutowirableReference;
22+
use Symfony\Component\DependencyInjection\ContainerBuilder;
23+
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
2224
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
2325
use Symfony\Component\DependencyInjection\Reference;
2426
use Symfony\Component\DependencyInjection\Definition;
@@ -630,4 +632,24 @@ 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 AutowirableReference('baz_service', 'stdClass')),
643+
'test' => new ServiceClosureArgument(new AutowirableReference(__CLASS__, __CLASS__, ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)),
644+
))
645+
;
646+
$container->register('bar_service', 'stdClass')->setArguments(array(new Reference('baz_service')));
647+
$container->register('baz_service', 'stdClass')->setPublic(false);
648+
$container->compile();
649+
650+
$dumper = new PhpDumper($container);
651+
652+
$suffix = PHP_VERSION_ID >= 70100 ? '71' : (PHP_VERSION_ID >= 70000 ? '70' : '55');
653+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_locator_php'.$suffix.'.php', $dumper->dump());
654+
}
633655
}

0 commit comments

Comments
 (0)
0