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

Skip to content

Commit 820b728

Browse files
committed
feature #26627 [DI] Add runtime service exceptions to improve the error message when controller arguments cannot be injected (nicolas-grekas)
This PR was merged into the 4.1-dev branch. Discussion ---------- [DI] Add runtime service exceptions to improve the error message when controller arguments cannot be injected | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23997 | License | MIT | Doc PR | - ![image](https://user-images.githubusercontent.com/243674/37775694-e5c9814c-2de3-11e8-8290-8fd05086da28.png) Commits ------- 9e8e063 [DI] Add ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE
2 parents ba05588 + 9e8e063 commit 820b728

27 files changed

+745
-16
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,16 @@ private function doProcessValue($value, $isRoot = false)
8686
if ($ref = $this->getAutowiredReference($value)) {
8787
return $ref;
8888
}
89-
$this->container->log($this, $this->createTypeNotFoundMessage($value, 'it'));
89+
$message = $this->createTypeNotFoundMessage($value, 'it');
90+
91+
if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
92+
// since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition
93+
$this->container->register($id = sprintf('_errored.%s.%s', $this->currentId, (string) $value), $value->getType())
94+
->addError($message);
95+
96+
return new TypedReference($id, $value->getType(), $value->getInvalidBehavior());
97+
}
98+
$this->container->log($this, $message);
9099
}
91100
$value = parent::processValue($value, $isRoot);
92101

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected function processValue($value, $isRoot = false)
2828
if (!$value instanceof Reference) {
2929
return parent::processValue($value, $isRoot);
3030
}
31-
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) {
31+
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) {
3232
throw new ServiceNotFoundException($id, $this->currentId);
3333
}
3434
if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() && $this->container->has($id = (string) $value) && !$this->container->findDefinition($id)->isShared()) {

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\ContainerInterface;
1415
use Symfony\Component\DependencyInjection\Definition;
1516
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
17+
use Symfony\Component\DependencyInjection\Reference;
1618

1719
/**
1820
* Throws an exception for any Definitions that have errors and still exist.
@@ -30,6 +32,21 @@ protected function processValue($value, $isRoot = false)
3032
return parent::processValue($value, $isRoot);
3133
}
3234

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+
}
48+
}
49+
3350
// only show the first error so the user can focus on it
3451
$errors = $value->getErrors();
3552
$message = reset($errors);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
9292
return true;
9393
}
9494

95-
if ($definition->isDeprecated() || $definition->isPublic() || $definition->isLazy()) {
95+
if ($definition->isDeprecated() || $definition->isPublic() || $definition->isLazy() || $definition->getErrors()) {
9696
return false;
9797
}
9898

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ private function doResolveDefinition(ChildDefinition $definition)
163163
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
164164
}
165165

166+
foreach (array_merge($parentDef->getErrors(), $definition->getErrors()) as $v) {
167+
$def->addError($v);
168+
}
169+
166170
// these attributes are always taken from the child
167171
$def->setAbstract($definition->isAbstract());
168172
$def->setTags($definition->getTags());

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1616
use Symfony\Component\DependencyInjection\ContainerInterface;
1717
use Symfony\Component\DependencyInjection\Definition;
18+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1819
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\DependencyInjection\TypedReference;
1921
use Symfony\Component\DependencyInjection\ContainerBuilder;
2022
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2123

@@ -29,6 +31,7 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
2931
{
3032
private $container;
3133
private $signalingException;
34+
private $currentId;
3235

3336
/**
3437
* Process the ContainerBuilder to resolve invalid references.
@@ -67,6 +70,9 @@ private function processValue($value, $rootLevel = 0, $level = 0)
6770
$i = 0;
6871

6972
foreach ($value as $k => $v) {
73+
if (!$rootLevel) {
74+
$this->currentId = $k;
75+
}
7076
try {
7177
if (false !== $i && $k !== $i++) {
7278
$i = false;
@@ -90,11 +96,21 @@ private function processValue($value, $rootLevel = 0, $level = 0)
9096
$value = array_values($value);
9197
}
9298
} elseif ($value instanceof Reference) {
93-
if ($this->container->has($value)) {
99+
if ($this->container->has($id = (string) $value)) {
94100
return $value;
95101
}
96102
$invalidBehavior = $value->getInvalidBehavior();
97103

104+
if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) {
105+
$e = new ServiceNotFoundException($id, $this->currentId);
106+
107+
// since the error message varies by $id and $this->currentId, so should the id of the dummy errored definition
108+
$this->container->register($id = sprintf('_errored.%s.%s', $this->currentId, $id), $value->getType())
109+
->addError($e->getMessage());
110+
111+
return new TypedReference($id, $value->getType(), $value->getInvalidBehavior());
112+
}
113+
98114
// resolve invalid behavior
99115
if (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
100116
$value = null;

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ public function has($id)
529529
*/
530530
public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
531531
{
532-
if ($this->isCompiled() && isset($this->removedIds[$id = (string) $id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
532+
if ($this->isCompiled() && isset($this->removedIds[$id = (string) $id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior) {
533533
return parent::get($id);
534534
}
535535

@@ -555,13 +555,17 @@ private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_
555555
try {
556556
$definition = $this->getDefinition($id);
557557
} catch (ServiceNotFoundException $e) {
558-
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
558+
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $invalidBehavior) {
559559
return;
560560
}
561561

562562
throw $e;
563563
}
564564

565+
if ($e = $definition->getErrors()) {
566+
throw new RuntimeException(reset($e));
567+
}
568+
565569
$loading = isset($this->alreadyLoading[$id]) ? 'loading' : 'alreadyLoading';
566570
$this->{$loading}[$id] = true;
567571

src/Symfony/Component/DependencyInjection/ContainerInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
interface ContainerInterface extends PsrContainerInterface
2626
{
27+
const RUNTIME_EXCEPTION_ON_INVALID_REFERENCE = 0;
2728
const EXCEPTION_ON_INVALID_REFERENCE = 1;
2829
const NULL_ON_INVALID_REFERENCE = 2;
2930
const IGNORE_ON_INVALID_REFERENCE = 3;

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,10 +873,14 @@ public function setBindings(array $bindings)
873873
* Add an error that occurred when building this Definition.
874874
*
875875
* @param string $error
876+
*
877+
* @return $this
876878
*/
877879
public function addError($error)
878880
{
879881
$this->errors[] = $error;
882+
883+
return $this;
880884
}
881885

882886
/**

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ public function dump(array $options = array())
187187
<?php
188188
189189
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
190+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
190191
191192
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
192193
@@ -320,7 +321,7 @@ private function addServiceLocalTempVariables(string $cId, Definition $definitio
320321
$name = $this->getNextVariableName();
321322
$this->referenceVariables[$id] = new Variable($name);
322323

323-
$reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id] ? new Reference($id, $behavior[$id]) : null;
324+
$reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior[$id] ? new Reference($id, $behavior[$id]) : null;
324325
$code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($id, $reference));
325326
}
326327

@@ -552,7 +553,7 @@ private function isTrivialInstance(Definition $definition): bool
552553
if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) {
553554
return false;
554555
}
555-
if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < count($definition->getArguments())) {
556+
if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < count($definition->getArguments()) || $definition->getErrors()) {
556557
return false;
557558
}
558559

@@ -738,6 +739,12 @@ protected function {$methodName}($lazyInitialization)
738739
EOF;
739740
}
740741

742+
if ($e = $definition->getErrors()) {
743+
$e = sprintf("throw new RuntimeException(%s);\n", $this->export(reset($e)));
744+
745+
return $asFile ? substr($code, 8).$e : $code.' '.$e." }\n";
746+
}
747+
741748
$inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition));
742749
$constructorDefinitions = $this->getDefinitionsFromArguments(array($definition->getArguments(), $definition->getFactory()));
743750
$otherDefinitions = new \SplObjectStorage();
@@ -1470,7 +1477,7 @@ private function dumpValue($value, bool $interpolate = true): string
14701477

14711478
$returnedType = '';
14721479
if ($value instanceof TypedReference) {
1473-
$returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() ? '' : '?', $value->getType());
1480+
$returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', $value->getType());
14741481
}
14751482

14761483
$code = sprintf('return %s;', $code);
@@ -1675,7 +1682,7 @@ private function getServiceCall(string $id, Reference $reference = null): string
16751682
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
16761683
return 'null';
16771684
}
1678-
if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
1685+
if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) {
16791686
$code = sprintf('$this->get(\'%s\', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $id, ContainerInterface::NULL_ON_INVALID_REFERENCE);
16801687
} else {
16811688
$code = sprintf('$this->get(\'%s\')', $id);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ private function getServiceCall(string $id, Reference $reference = null): string
266266
{
267267
if (null !== $reference) {
268268
switch ($reference->getInvalidBehavior()) {
269+
case ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: break;
269270
case ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: break;
270271
case ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: return sprintf('@!%s', $id);
271272
default: return sprintf('@?%s', $id);

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass;
2121
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
2222
use Symfony\Component\DependencyInjection\ContainerBuilder;
23+
use Symfony\Component\DependencyInjection\Definition;
2324
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2425
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
2526
use Symfony\Component\DependencyInjection\Reference;
@@ -845,4 +846,18 @@ public function testDoNotAutowireDecoratorWhenSeveralArgumentOfTheType()
845846
(new DecoratorServicePass())->process($container);
846847
(new AutowirePass())->process($container);
847848
}
849+
850+
public function testErroredServiceLocator()
851+
{
852+
$container = new ContainerBuilder();
853+
$container->register('some_locator', 'stdClass')
854+
->addArgument(new TypedReference(MissingClass::class, MissingClass::class, ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE))
855+
->addTag('container.service_locator');
856+
857+
(new AutowirePass())->process($container);
858+
859+
$erroredDefinition = new Definition(MissingClass::class);
860+
861+
$this->assertEquals($erroredDefinition->addError('Cannot autowire service "some_locator": it has type "Symfony\Component\DependencyInjection\Tests\Compiler\MissingClass" but this class was not found.'), $container->getDefinition('_errored.some_locator.'.MissingClass::class));
862+
}
848863
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,21 @@ public function testIdCanBeAnObjectAsLongAsItCanBeCastToString()
13671367
$container->removeAlias($aliasId);
13681368
$container->removeDefinition($id);
13691369
}
1370+
1371+
/**
1372+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
1373+
* @expectedExceptionMessage Service "errored_definition" is broken.
1374+
*/
1375+
public function testErroredDefinition()
1376+
{
1377+
$container = new ContainerBuilder();
1378+
1379+
$container->register('errored_definition', 'stdClass')
1380+
->addError('Service "errored_definition" is broken.')
1381+
->setPublic(true);
1382+
1383+
$container->get('errored_definition');
1384+
}
13701385
}
13711386

13721387
class FooClass

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,24 @@ public function testParameterWithMixedCase()
967967
$this->assertSame('bar', $container->getParameter('Foo'));
968968
$this->assertSame('foo', $container->getParameter('BAR'));
969969
}
970+
971+
/**
972+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
973+
* @expectedExceptionMessage Service "errored_definition" is broken.
974+
*/
975+
public function testErroredDefinition()
976+
{
977+
$container = include self::$fixturesPath.'/containers/container9.php';
978+
$container->setParameter('foo_bar', 'foo_bar');
979+
$container->compile();
980+
$dumper = new PhpDumper($container);
981+
$dump = $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Errored_Definition'));
982+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_errored_definition.php', str_replace(str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), '%path%', $dump));
983+
eval('?>'.$dump);
984+
985+
$container = new \Symfony_DI_PhpDumper_Errored_Definition();
986+
$container->get('runtime_error');
987+
}
970988
}
971989

972990
class Rot13EnvVarProcessor implements EnvVarProcessorInterface

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services9.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
44

55
use Bar\FooClass;
6+
use Symfony\Component\DependencyInjection\ContainerInterface;
7+
use Symfony\Component\DependencyInjection\Reference;
68
use Symfony\Component\DependencyInjection\Parameter;
79

810
require_once __DIR__.'/../includes/classes.php';
@@ -128,6 +130,11 @@
128130
->public()
129131
->args(array(tagged('foo')));
130132

133+
$s->set('runtime_error', 'stdClass')
134+
->args(array(new Reference('errored_definition', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)))
135+
->public();
136+
$s->set('errored_definition', 'stdClass')->private();
137+
131138
$s->alias('alias_for_foo', 'foo')->private()->public();
132139
$s->alias('alias_for_alias', ref('alias_for_foo'));
133140
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,11 @@
180180
$container->setAlias('alias_for_foo', 'foo')->setPublic(true);
181181
$container->setAlias('alias_for_alias', 'alias_for_foo')->setPublic(true);
182182

183+
$container->register('runtime_error', 'stdClass')
184+
->addArgument(new Reference('errored_definition', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE))
185+
->setPublic(true);
186+
187+
$container->register('errored_definition', 'stdClass')
188+
->addError('Service "errored_definition" is broken.');
189+
183190
return $container;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ digraph sc {
3434
node_BAR2 [label="BAR2\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
3535
node_tagged_iterator_foo [label="tagged_iterator_foo\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
3636
node_tagged_iterator [label="tagged_iterator\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
37+
node_runtime_error [label="runtime_error\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
38+
node_errored_definition [label="errored_definition\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
3739
node_foo2 [label="foo2\n\n", shape=record, fillcolor="#ff9999", style="filled"];
3840
node_foo3 [label="foo3\n\n", shape=record, fillcolor="#ff9999", style="filled"];
3941
node_foobaz [label="foobaz\n\n", shape=record, fillcolor="#ff9999", style="filled"];
@@ -57,4 +59,5 @@ digraph sc {
5759
node_lazy_context_ignore_invalid_ref -> node_foo_baz [label="" style="filled" color="#9999ff"];
5860
node_lazy_context_ignore_invalid_ref -> node_invalid [label="" style="filled" color="#9999ff"];
5961
node_BAR -> node_bar [label="" style="dashed"];
62+
node_runtime_error -> node_errored_definition [label="" style="filled"];
6063
}

0 commit comments

Comments
 (0)