Skip to content

Commit fef31dc

Browse files
[DependencyInjection] Skip errored definitions deep-referenced as runtime exceptions
1 parent d048b8a commit fef31dc

File tree

2 files changed

+109
-18
lines changed

2 files changed

+109
-18
lines changed

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

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
1416
use Symfony\Component\DependencyInjection\ContainerInterface;
1517
use Symfony\Component\DependencyInjection\Definition;
1618
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
@@ -23,34 +25,101 @@
2325
*/
2426
class DefinitionErrorExceptionPass extends AbstractRecursivePass
2527
{
28+
private $erroredDefinitions = [];
29+
private $targetReferences = [];
30+
private $sourceReferences = [];
31+
32+
/**
33+
* @return void
34+
*/
35+
public function process(ContainerBuilder $container)
36+
{
37+
try {
38+
parent::process($container);
39+
40+
if (!$this->erroredDefinitions) {
41+
return;
42+
}
43+
44+
$runtimeIds = [];
45+
46+
foreach ($this->sourceReferences as $id => $sourceIds) {
47+
foreach ($sourceIds as $sourceId => $isRuntime) {
48+
if (!$isRuntime) {
49+
continue 2;
50+
}
51+
}
52+
53+
unset($this->erroredDefinitions[$id]);
54+
$runtimeIds[$id] = $id;
55+
}
56+
57+
if (!$this->erroredDefinitions) {
58+
return;
59+
}
60+
61+
foreach ($this->targetReferences as $id => $targetIds) {
62+
if (!isset($this->sourceReferences[$id]) || isset($runtimeIds[$id]) || isset($this->erroredDefinitions[$id])) {
63+
continue;
64+
}
65+
foreach ($this->targetReferences[$id] as $targetId => $isRuntime) {
66+
foreach ($this->sourceReferences[$id] as $sourceId => $isRuntime) {
67+
if ($sourceId !== $targetId) {
68+
$this->sourceReferences[$targetId][$sourceId] = false;
69+
$this->targetReferences[$sourceId][$targetId] = false;
70+
}
71+
}
72+
}
73+
74+
unset($this->sourceReferences[$id]);
75+
}
76+
77+
foreach ($this->erroredDefinitions as $id => $definition) {
78+
if (isset($this->sourceReferences[$id])) {
79+
continue;
80+
}
81+
82+
// only show the first error so the user can focus on it
83+
$errors = $definition->getErrors();
84+
85+
throw new RuntimeException(reset($errors));
86+
}
87+
} finally {
88+
$this->erroredDefinitions = [];
89+
$this->targetReferences = [];
90+
$this->sourceReferences = [];
91+
}
92+
}
93+
2694
/**
2795
* {@inheritdoc}
2896
*/
2997
protected function processValue($value, bool $isRoot = false)
3098
{
31-
if (!$value instanceof Definition || !$value->hasErrors()) {
32-
return parent::processValue($value, $isRoot);
99+
if ($value instanceof ArgumentInterface) {
100+
parent::processValue($value->getValues());
101+
102+
return $value;
33103
}
34104

35-
if ($isRoot && !$value->isPublic()) {
36-
$graph = $this->container->getCompiler()->getServiceReferenceGraph();
37-
$runtimeException = false;
38-
foreach ($graph->getNode($this->currentId)->getInEdges() as $edge) {
39-
if (!$edge->getValue() instanceof Reference || ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE !== $edge->getValue()->getInvalidBehavior()) {
40-
$runtimeException = false;
41-
break;
42-
}
43-
$runtimeException = true;
44-
}
45-
if ($runtimeException) {
46-
return parent::processValue($value, $isRoot);
105+
if ($value instanceof Reference && $this->currentId !== $targetId = (string) $value) {
106+
if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
107+
$this->sourceReferences[$targetId][$this->currentId] ?? $this->sourceReferences[$targetId][$this->currentId] = true;
108+
$this->targetReferences[$this->currentId][$targetId] ?? $this->targetReferences[$this->currentId][$targetId] = true;
109+
} else {
110+
$this->sourceReferences[$targetId][$this->currentId] = false;
111+
$this->targetReferences[$this->currentId][$targetId] = false;
47112
}
113+
114+
return $value;
115+
}
116+
117+
if (!$value instanceof Definition || !$value->hasErrors()) {
118+
return parent::processValue($value, $isRoot);
48119
}
49120

50-
// only show the first error so the user can focus on it
51-
$errors = $value->getErrors();
52-
$message = reset($errors);
121+
$this->erroredDefinitions[$this->currentId] = $value;
53122

54-
throw new RuntimeException($message);
123+
return parent::processValue($value);
55124
}
56125
}

src/Symfony/Component/DependencyInjection/Tests/Compiler/DefinitionErrorExceptionPassTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Definition;
1818
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
19+
use Symfony\Component\DependencyInjection\Reference;
1920

2021
class DefinitionErrorExceptionPassTest extends TestCase
2122
{
@@ -49,4 +50,25 @@ public function testNoExceptionThrown()
4950
$pass->process($container);
5051
$this->assertSame($def, $container->getDefinition('foo_service_id')->getArgument(0));
5152
}
53+
54+
public function testSkipNestedErrors()
55+
{
56+
$container = new ContainerBuilder();
57+
58+
$container->register('nested_error', 'stdClass')
59+
->addError('Things went wrong!');
60+
61+
$container->register('bar', 'stdClass')
62+
->addArgument(new Reference('nested_error'));
63+
64+
$container->register('foo', 'stdClass')
65+
->addArgument(new Reference('bar', ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE));
66+
67+
$pass = new DefinitionErrorExceptionPass();
68+
$pass->process($container);
69+
70+
$this->expectException(RuntimeException::class);
71+
$this->expectExceptionMessage('Things went wrong!');
72+
$container->get('foo');
73+
}
5274
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy