10000 [DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE · symfony/symfony@5b34f42 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5b34f42

Browse files
[DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE
1 parent b9fc357 commit 5b34f42

21 files changed

+270
-21
lines changed

src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php

Lines changed: 2 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\ContainerInterface;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\ExpressionLanguage;
1718
use Symfony\Component\DependencyInjection\Reference;
@@ -87,7 +88,7 @@ protected function processValue($value, $isRoot = false)
8788

8889
return $value;
8990
}
90-
if ($value instanceof Reference) {
91+
if ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) {
9192
$targetDefinition = $this->getDefinition((string) $value);
9293

9394
$this->graph->connect(

src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php

Lines changed: 15 additions & 2 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Exception\InvalidargumentException;
1415
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1516
use Symfony\Component\DependencyInjection\ContainerInterface;
1617
use Symfony\Component\DependencyInjection\Reference;
@@ -24,14 +25,26 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
2425
{
2526
protected function processValue($value, $isRoot = false)
2627
{
27-
if ($value instanceof Reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
28+
if (!$value instanceof Reference) {
29+
return parent::processValue($value, $isRoot);
30+
}
31+
32+
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
2833
$destId = (string) $value;
2934

3035
if (!$this->container->has($destId)) {
3136
throw new ServiceNotFoundException($destId, $this->currentId);
3237
}
3338
}
3439

35-
return parent::processValue($value, $isRoot);
40+
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()) {
41+
$destId = (string) $value;
42+
43+
if ($this->container->has($destId) && !$this->container->getDefinition($destId)->isShared()) {
44+
throw new InvalidArgumentException(sprintf('Invalid ignore-on-uninitialized reference found in service "%s": target service "%s" is non-shared.', $this->currentId, $destId));
45+
}
46+
}
47+
48+
return $value;
3649
}
3750
}

src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php

Lines changed: 2 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\ContainerInterface;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\Reference;
1718

@@ -54,7 +55,7 @@ protected function processValue($value, $isRoot = false)
5455
// Reference found in ArgumentInterface::getValues() are not inlineable
5556
return $value;
5657
}
57-
if ($value instanceof Reference && $this->container->hasDefinition($id = (string) $value)) {
58+
if ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) {
5859
$definition = $this->container->getDefinition($id);
5960

6061
if ($this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) {

src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ protected function processValue($value, $isRoot = false)
7474
if ($optionalBehavior = '?' === $type[0]) {
7575
$type = substr($type, 1);
7676
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
77+
} elseif ($optionalBehavior = '!' === $type[0]) {
78+
$type = substr($type, 1);
79+
$optionalBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
7780
}
7881
if (is_int($key)) {
7982
$key = $type;

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* * NULL_ON_INVALID_REFERENCE: Returns null
4444
* * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference
4545
* (for instance, ignore a setter if the service does not exist)
46+
* * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services
4647
*
4748
* @author Fabien Potencier <fabien@symfony.com>
4849
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
@@ -304,9 +305,9 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
304305

305306
try {
306307
if (isset($this->fileMap[$id])) {
307-
return $this->load($this->fileMap[$id]);
308+
return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->load($this->fileMap[$id]);
308309
} elseif (isset($this->methodMap[$id])) {
309-
return $this->{$this->methodMap[$id]}();
310+
return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$this->methodMap[$id]}();
310311
} elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) {
311312
$id = $normalizedId;
312313
continue;
@@ -315,7 +316,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
315316
// and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper)
316317
@trigger_error('Generating a dumped container without populating the method map is deprecated since 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED);
317318

318-
return $this->{$method}();
319+
return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$method}();
319320
}
320321

321322
break;

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,9 @@ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INV
565565
{
566566
$id = $this->normalizeId($id);
567567

568+
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) {
569+
return parent::get($id, $invalidBehavior);
570+
}
568571
if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
569572
return $service;
570573
}
@@ -1160,6 +1163,11 @@ public function resolveServices($value)
11601163
continue 2;
11611164
}
11621165
}
1166+
foreach (self::getInitializedConditionals($v) as $s) {
1167+
if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
1168+
continue 2;
1169+
}
1170+
}
11631171

11641172
yield $k => $this->resolveServices($v);
11651173
}
@@ -1171,6 +1179,11 @@ public function resolveServices($value)
11711179
continue 2;
11721180
}
11731181
}
1182+
foreach (self::getInitializedConditionals($v) as $s) {
1183+
if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
1184+
continue 2;
1185+
}
1186+
}
11741187

11751188
++$count;
11761189
}
@@ -1397,6 +1410,8 @@ public function log(CompilerPassInterface $pass, $message)
13971410
* @param mixed $value An array of conditionals to return
13981411
*
13991412
* @return array An array of Service conditionals
1413+
*
1414+
* @internal since version 3.4
14001415
*/
14011416
public static function getServiceConditionals($value)
14021417
{
@@ -1413,6 +1428,30 @@ public static function getServiceConditionals($value)
14131428
return $services;
14141429
}
14151430

1431+
/**
1432+
* Returns the initialized conditionals.
1433+
*
1434+
* @param mixed $value An array of conditionals to return
1435+
*
1436+
* @return array An array of uninitialized conditionals
1437+
*
1438+
* @internal
1439+
*/
1440+
public static function getInitializedConditionals($value)
1441+
{
1442+
$services = array();
1443+
1444+
if (is_array($value)) {
1445+
foreach ($value as $v) {
1446+
$services = array_unique(array_merge($services, self::getInitializedConditionals($v)));
1447+
}
1448+
} elseif ($value instanceof Reference && $value->getInvalidBehavior() === ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE) {
1449+
$services[] = (string) $value;
1450+
}
1451+
1452+
return $services;
1453+
}
1454+
14161455
/**
14171456
* Computes a reasonably unique hash of a value.
14181457
*
@@ -1465,13 +1504,16 @@ private function getProxyInstantiator()
14651504

14661505
private function callMethod($service, $call)
14671506
{
1468-
$services = self::getServiceConditionals($call[1]);
1469-
1470-
foreach ($services as $s) {
1507+
foreach (self::getServiceConditionals($call[1]) as $s) {
14711508
if (!$this->has($s)) {
14721509
return;
14731510
}
14741511
}
1512+
foreach (self::getInitializedConditionals($call[1]) as $s) {
1513+
if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
1514+
return;
1515+
}
1516+
}
14751517

14761518
call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1]))));
14771519
}

src/Symfony/Component/DependencyInjection/ContainerInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface ContainerInterface extends PsrContainerInterface
2727
const EXCEPTION_ON_INVALID_REFERENCE = 1;
2828
const NULL_ON_INVALID_REFERENCE = 2;
2929
const IGNORE_ON_INVALID_REFERENCE = 3;
30+
const IGNORE_ON_UNINITIALIZED_REFERENCE = 4;
3031

3132
/**
3233
* Sets a service.

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ private function addServiceLocalTempVariables($cId, Definition $definition, arra
277277
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id]) {
278278
$code .= sprintf($template, $name, $this->getServiceCall($id));
279279
} else {
280-
$code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)));
280+
$code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, $behavior[$id])));
281281
}
282282
}
283283
}
@@ -1295,12 +1295,11 @@ private function wrapServiceConditionals($value, $code)
12951295
*/
12961296
private function getServiceConditionals($value)
12971297
{
1298-
if (!$services = ContainerBuilder::getServiceConditionals($value)) {
1299-
return null;
1300-
}
1301-
13021298
$conditions = array();
1303-
foreach ($services as $service) {
1299+
foreach (ContainerBuilder::getInitializedConditionals($value) as $service) {
1300+
$conditions[] = sprintf("isset(\$this->services['%s'])", $service);
1301+
}
1302+
foreach (ContainerBuilder::getServiceConditionals($value) as $service) {
13041303
if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) {
13051304
continue;
13061305
}
@@ -1335,8 +1334,8 @@ private function getServiceCallsFromArguments(array $arguments, array &$calls, a
13351334
}
13361335
if (!isset($behavior[$id])) {
13371336
$behavior[$id] = $argument->getInvalidBehavior();
1338-
} elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) {
1339-
$behavior[$id] = $argument->getInvalidBehavior();
1337+
} else {
1338+
$behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior());
13401339
}
13411340

13421341
++$calls[$id];
@@ -1665,6 +1664,10 @@ private function getServiceCall($id, Reference $reference = null)
16651664
return '$this';
16661665
}
16671666

1667+
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
1668+
// The following is PHP 5.5 syntax for what could be written as "(\$this->services['$id'] ?? null)" on PHP>=7.0
1669+
return "\${(\$_ = isset(\$this->services['$id']) ? \$this->services['$id'] : null) && false ?: '_'}";
1670+
}
16681671
if ($this->asFiles && $this->container->hasDefinition($id)) {
16691672
if ($this->container->getDefinition($id)->isShared()) {
16701673
$code = sprintf("\$this->load(__DIR__.'/%s.php')", $this->generateMethodName($id));
@@ -1681,7 +1684,6 @@ private function getServiceCall($id, Reference $reference = null)
16811684

16821685
if ($this->container->hasDefinition($id) && $this->container->getDefinition($id)->isShared()) {
16831686
// The following is PHP 5.5 syntax for what could be written as "(\$this->services['$id'] ?? $code)" on PHP>=7.0
1684-
16851687
$code = "\${(\$_ = iss B41A et(\$this->services['$id']) ? \$this->services['$id'] : $code) && false ?: '_'}";
16861688
}
16871689

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
309309
$element->setAttribute('on-invalid', 'null');
310310
} elseif ($behaviour == ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
311311
$element->setAttribute('on-invalid', 'ignore');
312+
} elseif ($behaviour == ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE) {
313+
$element->setAttribute('on-invalid', 'ignore_uninitialized');
312314
}
313315
} elseif ($value instanceof Definition) {
314316
$element->setAttribute('type', 'service');

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,12 @@ private function dumpValue($value)
304304
*/
305305
private function getServiceCall($id, Reference $reference = null)
306306
{
307-
if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
308-
return sprintf('@?%s', $id);
307+
if (null !== $reference) {
308+
switch ($reference->getInvalidBehavior()) {
309+
case ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: break;
310+
case ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: return sprintf('@!%s', $id);
311+
default: return sprintf('@?%s', $id);
312+
}
309313
}
310314

311315
return sprintf('@%s', $id);

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,8 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $lowercase =
492492
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
493493
if ('ignore' == $onInvalid) {
494494
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
495+
} elseif ('ignore_uninitialized' == $onInvalid) {
496+
$invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
495497
} elseif ('null' == $onInvalid) {
496498
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
497499
}

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,9 @@ private function resolveServices($value, $file, $isParameter = false)
758758
if (0 === strpos($value, '@@')) {
759759
$value = substr($value, 1);
760760
$invalidBehavior = null;
761+
} elseif (0 === strpos($value, '@!')) {
762+
$value = substr($value, 2);
763+
$invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
761764
} elseif (0 === strpos($value, '@?')) {
762765
$value = substr($value, 2);
763766
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@
266266
<xsd:enumeration value="null" />
267267
<xsd:enumeration value="ignore" />
268268
<xsd:enumeration value="exception" />
269+
<xsd:enumeration value="ignore_uninitialized" />
269270
</xsd:restriction>
270271
</xsd:simpleType>
271272

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,30 @@ public function testServiceLocator()
10981098
$this->assertSame($container->get('bar_service'), $foo->get('bar'));
10991099
}
11001100

1101+
public function testUninitializedReference()
1102+
{
1103+
$container = include __DIR__.'/Fixtures/containers/container_uninitialized_ref.php';
1104+
$container->compile();
1105+
1106+
$bar = $container->get('bar');
1107+
1108+
$this->assertNull($bar->ref);
1109+
$this->assertNull(call_user_func($bar->lambda));
1110+
$this->assertSame(array(), iterator_to_array($bar->iter));
1111+
1112+
$container = include __DIR__.'/Fixtures/containers/container_uninitialized_ref.php';
1113+
$container->compile();
1114+
1115+
$foo = new \stdClass();
1116+
$container->set('foo', $foo);
1117+
1118+
$bar = $container->get('bar');
1119+
1120+
$this->assertSame($foo, $bar->ref);
1121+
$this->assertSame($foo, call_user_func($bar->lambda));
1122+
$this->assertSame(array('foo' => $foo), iterator_to_array($bar->iter));
1123+
}
1124+
11011125
public function testRegisterForAutoconfiguration()
11021126
{
11031127
$container = new ContainerBuilder();

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,15 @@ public function testExpressionReferencingPrivateService()
631631
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_private_in_expression.php', $dumper->dump());
632632
}
633633

634+
public function testUninitializedReference()
635+
{
636+
$container = include self::$fixturesPath.'/containers/container_uninitialized_ref.php';
637+
$container->compile();
638+
$dumper = new PhpDumper($container);
639+
640+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/container_uninitialized_ref.php', $dumper->dump());
641+
}
642+
634643
public function testDumpHandlesLiteralClassWithRootNamespace()
635644
{
636645
$container = new ContainerBuilder();

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
namespace Symfony\Component\DependencyInjection\Tests\Dumper;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Config\FileLocator;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\ContainerInterface;
1618
use Symfony\Component\DependencyInjection\Dumper\XmlDumper;
19+
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
20+
use Symfony\Component\DependencyInjection\Reference;
1721

1822
class XmlDumperTest extends TestCase
1923
{
@@ -184,6 +188,18 @@ public function testDumpAutowireData()
184188
$this->assertEquals(file_get_contents(self::$fixturesPath.'/xml/services24.xml'), $dumper->dump());
185189
}
186190

191+
public function testDumpLoad()
192+
{
193+
$container = new ContainerBuilder();
194+
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
195+
$loader->load('services_dump_load.xml');
196+
197+
$this->assertEquals(array(new Reference('bar', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)), $container->getDefinition('foo')->getArguments());
198+
199+
$dumper = new XmlDumper($container);
200+
$this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_dump_load.xml', $dumper->dump());
201+
}
202+
187203
public function testDumpAbstractServices()
188204
{
189205
$container = include self::$fixturesPath.'/containers/container_abstract.php';

0 commit comments

Comments
 (0)
0