Skip to content

Commit b584db1

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

26 files changed

+432
-53
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* added support for ignore-on-uninitialized references
78
* deprecated service auto-registration while autowiring
89
* deprecated the ability to check for the initialization of a private service with the `Container::initialized()` method
910
* deprecated support for top-level anonymous services in XML

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

+3-1
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;
@@ -96,7 +97,8 @@ protected function processValue($value, $isRoot = false)
9697
$this->getDefinitionId((string) $value),
9798
$targetDefinition,
9899
$value,
99-
$this->lazy || ($targetDefinition && $targetDefinition->isLazy())
100+
$this->lazy || ($targetDefinition && $targetDefinition->isLazy()),
101+
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()
100102
);
101103

102104
return $value;

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

+10-7
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,16 @@ 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-
$destId = (string) $value;
29-
30-
if (!$this->container->has($destId)) {
31-
throw new ServiceNotFoundException($destId, $this->currentId);
32-
}
28+
if (!$value instanceof Reference) {
29+
return parent::processValue($value, $isRoot);
30+
}
31+
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) {
32+
throw new ServiceNotFoundException($id, $this->currentId);
33+
}
34+
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() && $this->container->has($id = (string) $value) && !$this->container->findDefinition($id)->isShared()) {
35+
throw new InvalidArgumentException(sprintf('Invalid ignore-on-uninitialized reference found in service "%s": target service "%s" is not shared.', $this->currentId, $id));
3336
}
3437

35-
return parent::processValue($value, $isRoot);
38+
return $value;
3639
}
3740
}

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

+4
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

@@ -96,6 +97,9 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
9697

9798
$ids = array();
9899
foreach ($graph->getNode($id)->getInEdges() as $edge) {
100+
if ($edge->isWeak()) {
101+
return false;
102+
}
99103
$ids[] = $edge->getSourceNode()->getId();
100104
}
101105

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

+3
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/Compiler/RemoveUnusedDefinitionsPass.php

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public function process(ContainerBuilder $container)
5050
$referencingAliases = array();
5151
$sourceIds = array();
5252
foreach ($edges as $edge) {
53+
if ($edge->isWeak()) {
54+
continue;
55+
}
5356
$node = $edge->getSourceNode();
5457
$sourceIds[] = $node->getId();
5558

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

+8-13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
* it themselves which improves performance quite a lot.
2121
*
2222
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
23+
*
24+
* @final since version 3.4
2325
*/
2426
class ServiceReferenceGraph
2527
{
@@ -85,23 +87,16 @@ public function clear()
8587
* @param string $destValue
8688
* @param string $reference
8789
* @param bool $lazy
90+
* @param bool $weak
8891
*/
89-
public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false*/)
92+
public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false, bool $weak = false*/)
9093
{
91-
if (func_num_args() >= 6) {
92-
$lazy = func_get_arg(5);
93-
} else {
94-
if (__CLASS__ !== get_class($this)) {
95-
$r = new \ReflectionMethod($this, __FUNCTION__);
96-
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
97-
@trigger_error(sprintf('Method %s() will have a 6th `bool $lazy = false` argument in version 4.0. Not defining it is deprecated since 3.3.', __METHOD__), E_USER_DEPRECATED);
98-
}
99-
}
100-
$lazy = false;
101-
}
94+
$lazy = func_num_args() >= 6 ? func_get_arg(5) : false;
95+
$weak = func_num_args() >= 7 ? func_get_arg(6) : false;
96+
10297
$sourceNode = $this->createNode($sourceId, $sourceValue);
10398
$destNode = $this->createNode($destId, $destValue);
104-
$edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy);
99+
$edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak);
105100

106101
$sourceNode->addOutEdge($edge);
107102
$destNode->addInEdge($edge);

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,22 @@ class ServiceReferenceGraphEdge
2424
private $destNode;
2525
private $value;
2626
private $lazy;
27+
private $weak;
2728

2829
/**
2930
* @param ServiceReferenceGraphNode $sourceNode
3031
* @param ServiceReferenceGraphNode $destNode
3132
* @param string $value
3233
* @param bool $lazy
34+
* @param bool $weak
3335
*/
34-
public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false)
36+
public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false, $weak = false)
3537
{
3638
$this->sourceNode = $sourceNode;
3739
$this->destNode = $destNode;
3840
$this->value = $value;
3941
$this->lazy = $lazy;
42+
$this->weak = $weak;
4043
}
4144

4245
/**
@@ -78,4 +81,14 @@ public function isLazy()
7881
{
7982
return $this->lazy;
8083
}
84+
85+
/**
86+
* Returns true if the edge is weak, meaning it shouldn't prevent removing the target service.
87+
*
88+
* @return bool
89+
*/
90+
public function isWeak()
91+
{
92+
return $this->weak;
93+
}
8194
}

src/Symfony/Component/DependencyInjection/Container.php

+6-17
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,15 @@
2323
* Container is a dependency injection container.
2424
*
2525
* It gives access to object instances (services).
26-
*
2726
* Services and parameters are simple key/pair stores.
28-
*
29-
* Parameter and service keys are case insensitive.
30-
*
31-
* A service can also be defined by creating a method named
32-
* getXXXService(), where XXX is the camelized version of the id:
33-
*
34-
* <ul>
35-
* <li>request -> getRequestService()</li>
36-
* <li>mysql_session_storage -> getMysqlSessionStorageService()</li>
37-
* <li>symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService()</li>
38-
* </ul>
39-
*
40-
* The container can have three possible behaviors when a service does not exist:
27+
* The container can have four possible behaviors when a service
28+
* does not exist (or is not initialized for the last case):
4129
*
4230
* * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default)
4331
* * NULL_ON_INVALID_REFERENCE: Returns null
4432
* * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference
4533
* (for instance, ignore a setter if the service does not exist)
34+
* * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references
4635
*
4736
* @author Fabien Potencier <fabien@symfony.com>
4837
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
@@ -304,9 +293,9 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
304293

305294
try {
306295
if (isset($this->fileMap[$id])) {
307-
return $this->load($this->fileMap[$id]);
296+
return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->load($this->fileMap[$id]);
308297
} elseif (isset($this->methodMap[$id])) {
309-
return $this->{$this->methodMap[$id]}();
298+
return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$this->methodMap[$id]}();
310299
} elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) {
311300
$id = $normalizedId;
312301
continue;
@@ -315,7 +304,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
315304
// and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper)
316305
@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);
317306

318-
return $this->{$method}();
307+
return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$method}();
319308
}
320309

321310
break;

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

+45-3
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

+1
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

+13-9
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,14 @@ 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+
if (!$this->container->hasDefinition($service)) {
1301+
return 'false';
1302+
}
1303+
$conditions[] = sprintf("isset(\$this->services['%s'])", $service);
1304+
}
1305+
foreach (ContainerBuilder::getServiceConditionals($value) as $service) {
13041306
if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) {
13051307
continue;
13061308
}
@@ -1335,8 +1337,8 @@ private function getServiceCallsFromArguments(array $arguments, array &$calls, a
13351337
}
13361338
if (!isset($behavior[$id])) {
13371339
$behavior[$id] = $argument->getInvalidBehavior();
1338-
} elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) {
1339-
$behavior[$id] = $argument->getInvalidBehavior();
1340+
} else {
1341+
$behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior());
13401342
}
13411343

13421344
++$calls[$id];
@@ -1665,7 +1667,9 @@ private function getServiceCall($id, Reference $reference = null)
16651667
return '$this';
16661668
}
16671669

1668-
if ($this->asFiles && $this->container->hasDefinition($id)) {
1670+
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
1671+
$code = 'null';
1672+
} elseif ($this->asFiles && $this->container->hasDefinition($id)) {
16691673
if ($this->container->getDefinition($id)->isShared()) {
16701674
$code = sprintf("\$this->load(__DIR__.'/%s.php')", $this->generateMethodName($id));
16711675
} else {

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

+2
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');

0 commit comments

Comments
 (0)