Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ec75183

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

File tree

2 files changed

+115
-17
lines changed

2 files changed

+115
-17
lines changed

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

Lines changed: 93 additions & 17 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,108 @@
2325
*/
2426
class DefinitionErrorExceptionPass extends AbstractRecursivePass
2527
{
28+
private array $erroredDefinitions = [];
29+
private array $targetReferences = [];
30+
private array $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
{
99+
if ($value instanceof ArgumentInterface) {
100+
parent::processValue($value->getValues());
101+
102+
return $value;
103+
}
104+
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] ??= true;
108+
$this->targetReferences[$this->currentId][$targetId] ??= true;
109+
} else {
110+
$this->sourceReferences[$targetId][$this->currentId] = false;
111+
$this->targetReferences[$this->currentId][$targetId] = false;
112+
}
113+
114+
return $value;
115+
}
116+
31117
if (!$value instanceof Definition || !$value->hasErrors()) {
32118
return parent::processValue($value, $isRoot);
33119
}
34120

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);
47-
}
121+
if ($value->isPublic()) {
122+
// only show the first error so the user can focus on it
123+
$errors = $value->getErrors();
124+
125+
throw new RuntimeException(reset($errors));
48126
}
49127

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

54-
throw new RuntimeException($message);
130+
return parent::processValue($value);
55131
}
56132
}

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)