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 db6375f

Browse filesBrowse files
[DI] Allow creating ServiceLocator-based services in extensions
1 parent 3fa8a05 commit db6375f
Copy full SHA for db6375f

18 files changed

+628
-5
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+
}
+65Lines 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 PHP 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::IGNORE_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

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 "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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+10Lines changed: 10 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,14 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
100102
*/
101103
protected function processValue($value, $isRoot = false)
102104
{
105+
if ($value instanceof AutowirableReference && !$this->container->has((string) $value)) {
106+
if ($ref = $this->getAutowiredReference($value->getType(), $value->autoRegister())) {
107+
$value->setId($ref);
108+
}
109+
110+
return parent::processValue($value, $isRoot);
111+
}
112+
103113
if (!$value instanceof Definition || !$value->isAutowired()) {
104114
return parent::processValue($value, $isRoot);
105115
}

‎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,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

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
+3Lines 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/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
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
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;
28+
use Symfony\Component\DependencyInjection\AutowirableReference;
2729
use Symfony\Component\DependencyInjection\ChildDefinition;
2830
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
2931
use Symfony\Component\DependencyInjection\Container;
@@ -1159,6 +1161,25 @@ 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 AutowirableReference('baz_service', 'stdClass', ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE)),
1172+
'test' => new ServiceClosureArgument(new AutowirableReference(__CLASS__, __CLASS__)),
1173+
))
1174+
;
1175+
$container->register('bar_service', 'stdClass')->setArguments(array(new Reference('baz_service')));
1176+
$container->register('baz_service', 'stdClass')->setPublic(false);
1177+
$container->compile();
1178+
1179+
$this->assertInstanceOf(ServiceLocator::class, $foo = $container->get('foo_service'));
1180+
$this->assertSame($container->get('bar_service'), $foo->get('bar'));
1181+
$this->assertInstanceOf(__CLASS__, $foo->get('test'));
1182+
}
11621183
}
11631184

11641185
class FooClass

0 commit comments

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