8000 [DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE · symfony/symfony@0908ea6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0908ea6

Browse files
[DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE
1 parent b9fc357 commit 0908ea6

22 files changed

+313
-20
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
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) 10000 && !$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: 12 additions & 8 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();
< 10000 div aria-hidden="true" class="position-absolute top-0 d-flex user-select-none DiffLineTableCellParts-module__comment-indicator--eI0hb">
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,11 @@ 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+
1670+
return "\${(\$_ = isset(\$this->services['$id']) ? \$this->services['$id'] : null) && false ?: '_'}";
1671+
}
16681672
if ($this->asFiles && $this->container->hasDefinition($id)) {
16691673
if ($this->container->getDefinition($id)->isShared()) {
16701674
$code = sprintf("\$this->load(__DIR__.'/%s.php')", $this->generateMethodName($id));

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/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ public function testProcessThrowsExceptionOnInvalidReferenceFromInlinedDefinitio
6767
$this->process($container);
6868
}
6969

70+
/**
71+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
72+
* @expectedExceptionMessage Invalid ignore-on-uninitialized reference found in service
73+
*/
74+
public function testProcessThrowsExceptionOnNonSharedUninitializedReference()
75+
{
76+
$container = new ContainerBuilder();
77+
78+
$container
79+
->register('a', 'stdClass')
80+
->addArgument(new Reference('b', $container::IGNORE_ON_UNINITIALIZED_REFERENCE))
81+
;
82+
83+
$container
84+
->register('b', 'stdClass')
85+
->setShared(false)
86+
;
87+
88+
$this->process($container);
89+
}
90+
7091
private function process(ContainerBuilder $container)
7192
{
7293
$pass = new CheckExceptionOnInvalidReferenceBehaviorPass();

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();
< 8DCB /td>
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();

0 commit comments

Comments
 (0)
0