Skip to content

Commit 0a55740

Browse files
committed
Adding Definition::addError() and a compiler pass to throw errors as
exceptions
1 parent 701d41c commit 0a55740

12 files changed

+187
-9
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
*/
2323
abstract class AbstractRecursivePass implements CompilerPassInterface
2424
{
25+
/**
26+
* @var ContainerBuilder
27+
*/
2528
protected $container;
2629
protected $currentId;
2730

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

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

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
@trigger_error('The '.__NAMESPACE__.'\AutowireExceptionPass class is deprecated since version 3.4 and will be removed in 4.0. Use the DefinitionErrorExceptionPass class instead.', E_USER_DEPRECATED);
15+
1416
use Symfony\Component\DependencyInjection\ContainerBuilder;
1517

1618
/**

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,21 @@ class AutowirePass extends AbstractRecursivePass
3636
private $autowiringExceptions = array();
3737

3838
/**
39-
* @param bool $throwOnAutowireException If false, retrieved errors via getAutowiringExceptions
39+
* @param bool $throwOnAutowireException Errors can be retrieved via Definition::getErrors()
4040
*/
4141
public function __construct($throwOnAutowireException = true)
4242
{
4343
$this->throwOnAutowiringException = $throwOnAutowireException;
4444
}
4545

4646
/**
47+
* @deprecated
4748
* @return AutowiringFailedException[]
4849
*/
4950
public function getAutowiringExceptions()
5051
{
52+
@trigger_error('Calling AutowirePass::getAutowiringExceptions() is deprecated since Symfony 3.4 and will be removed in 4.0. Use Definition::getErrors() instead.', E_USER_DEPRECATED);
53+
5154
return $this->autowiringExceptions;
5255
}
5356

@@ -106,6 +109,7 @@ protected function processValue($value, $isRoot = false)
106109
}
107110

108111
$this->autowiringExceptions[] = $e;
112+
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
109113

110114
return parent::processValue($value, $isRoot);
111115
}

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@
2222
*/
2323
class CheckArgumentsValidityPass extends AbstractRecursivePass
2424
{
25+
private $throwExceptions;
26+
27+
public function __construct($throwExceptions = true)
28+
{
29+
$this->throwExceptions = $throwExceptions;
30+
}
31+
32+
2533
/**
2634
* {@inheritdoc}
2735
*/
@@ -35,10 +43,19 @@ protected function processValue($value, $isRoot = false)
3543
foreach ($value->getArguments() as $k => $v) {
3644
if ($k !== $i++) {
3745
if (!is_int($k)) {
38-
throw new RuntimeException(sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k));
46+
$value->addError(sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k));
47+
48+
if ($this->throwExceptions) {
49+
throw new RuntimeException(sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k));
50+
}
51+
52+
break;
3953
}
4054

41-
throw new RuntimeException(sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i));
55+
$value->addError(sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i));
56+
if ($this->throwExceptions) {
57+
throw new RuntimeException(sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i));
58+
}
4259
}
4360
}
4461

@@ -47,10 +64,18 @@ protected function processValue($value, $isRoot = false)
4764
foreach ($methodCall[1] as $k => $v) {
4865
if ($k !== $i++) {
4966
if (!is_int($k)) {
50-
throw new RuntimeException(sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k));
67+
$value->addError(sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k));
68+
if ($this->throwExceptions) {
69+
throw new RuntimeException(sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k));
70+
}
71+
72+
break;
5173
}
5274

53-
throw new RuntimeException(sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i));
75+
$value->addError(sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i));
76+
if ($this->throwExceptions) {
77+
throw new RuntimeException(sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i));
78+
}
5479
}
5580
}
5681
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Definition;
15+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
16+
17+
/**
18+
* Throws an exception for any Definitions that have errors and still exist.
19+
*
20+
* @author Ryan Weaver <ryan@knpuniversity.com>
21+
*/
22+
class DefinitionErrorExceptionPass extends AbstractRecursivePass
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
protected function processValue($value, $isRoot = false)
28+
{
29+
if (!$value instanceof Definition || empty($value->getErrors())) {
30+
return parent::processValue($value, $isRoot);
31+
}
32+
33+
// only show the first error so they user can focus on it
34+
$errors = $value->getErrors();
35+
$message = reset($errors);
36+
37+
throw new RuntimeException($message);
38+
}
39+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ public function __construct()
5959
new RegisterServiceSubscribersPass(),
6060
new ResolveNamedArgumentsPass(),
6161
new ResolveBindingsPass(),
62-
$autowirePass = new AutowirePass(false),
62+
new AutowirePass(false),
6363
new ResolveServiceSubscribersPass(),
6464
new ResolveReferencesToAliasesPass(),
6565
new ResolveInvalidReferencesPass(),
6666
new AnalyzeServiceReferencesPass(true),
6767
new CheckCircularReferencesPass(),
6868
new CheckReferenceValidityPass(),
69-
new CheckArgumentsValidityPass(),
69+
new CheckArgumentsValidityPass(false),
7070
));
7171

7272
$this->removingPasses = array(array(
@@ -75,11 +75,11 @@ public function __construct()
7575
new RemoveAbstractDefinitionsPass(),
7676
new RepeatedPass(array(
7777
new AnalyzeServiceReferencesPass(),
78-
$inlinedServicePass = new InlineServiceDefinitionsPass(),
78+
new InlineServiceDefinitionsPass(),
7979
new AnalyzeServiceReferencesPass(),
8080
new RemoveUnusedDefinitionsPass(),
8181
)),
82-
new AutowireExceptionPass($autowirePass, $inlinedServicePass),
82+
new DefinitionErrorExceptionPass(),
8383
new CheckExceptionOnInvalidReferenceBehaviorPass(),
8484
));
8585
}

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Definition
4444
private $autowiringTypes = array();
4545
private $changes = array();
4646
private $bindings = array();
47+
private $errors = array();
4748

4849
protected $arguments = array();
4950

@@ -959,4 +960,24 @@ public function setBindings(array $bindings)
959960

960961
return $this;
961962
}
963+
964+
/**
965+
* Add an error that occurred when building this Definition.
966+
*
967+
* @param string $error
968+
*/
969+
public function addError($error)
970+
{
971+
$this->errors[] = $error;
972+
}
973+
974+
/**
975+
* Returns any errors that occurred while building this Definition
976+
*
977+
* @return array
978+
*/
979+
public function getErrors()
980+
{
981+
return $this->errors;
982+
}
962983
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
2020

21+
/**
22+
* @group legacy
23+
*/
2124
class AutowireExceptionPassTest extends TestCase
2225
{
2326
public function testThrowsException()

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ public function testCompleteExistingDefinitionWithNotDefinedArguments()
157157
$this->assertEquals(DInterface::class, (string) $container->getDefinition('h')->getArgument(1));
158158
}
159159

160+
/**
161+
* @group legacy
162+
*/
160163
public function testExceptionsAreStored()
161164
{
162165
$container = new ContainerBuilder();

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,15 @@ public function definitionProvider()
6464
array(array(), array(array('baz', array(1 => 1)))),
6565
);
6666
}
67+
68+
public function testNoException()
69+
{
70+
$container = new ContainerBuilder();
71+
$definition = $container->register('foo');
72+
$definition->setArguments(array(null, 'a' => 'a'));
73+
74+
$pass = new CheckArgumentsValidityPass(false);
75+
$pass->process($container);
76+
$this->assertCount(1, $definition->getErrors());
77+
}
6778
}

0 commit comments

Comments
 (0)