Skip to content

Commit a85b37a

Browse files
weaverryannicolas-grekas
authored andcommitted
Adding Definition::addError() and a compiler pass to throw errors as exceptions
1 parent 701d41c commit a85b37a

14 files changed

+194
-9
lines changed

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

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

+4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
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
/**
1719
* Throws autowire exceptions from AutowirePass for definitions that still exist.
1820
*
21+
* @deprecated since version 3.4, will be removed in 4.0.
22+
*
1923
* @author Ryan Weaver <[email protected]>
2024
*/
2125
class AutowireExceptionPass implements CompilerPassInterface

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,22 @@ 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 since version 3.4, to be removed in 4.0.
48+
*
4749
* @return AutowiringFailedException[]
4850
*/
4951
public function getAutowiringExceptions()
5052
{
53+
@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);
54+
5155
return $this->autowiringExceptions;
5256
}
5357

@@ -106,6 +110,7 @@ protected function processValue($value, $isRoot = false)
106110
}
107111

108112
$this->autowiringExceptions[] = $e;
113+
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
109114

110115
return parent::processValue($value, $isRoot);
111116
}

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

+31-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
*/
2323
class CheckArgumentsValidityPass extends AbstractRecursivePass
2424
{
25+
private $throwExceptions;
26+
27+
public function __construct($throwExceptions = true)
28+
{
29+
$this->throwExceptions = $throwExceptions;
30+
}
31+
2532
/**
2633
* {@inheritdoc}
2734
*/
@@ -35,10 +42,20 @@ protected function processValue($value, $isRoot = false)
3542
foreach ($value->getArguments() as $k => $v) {
3643
if ($k !== $i++) {
3744
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));
45+
$msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k);
46+
$value->addError($msg);
47+
if ($this->throwExceptions) {
48+
throw new RuntimeException($msg);
49+
}
50+
51+
break;
3952
}
4053

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));
54+
$msg = 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($msg);
56+
if ($this->throwExceptions) {
57+
throw new RuntimeException($msg);
58+
}
4259
}
4360
}
4461

@@ -47,10 +64,20 @@ 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+
$msg = 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+
$value->addError($msg);
69+
if ($this->throwExceptions) {
70+
throw new RuntimeException($msg);
71+
}
72+
73+
break;
5174
}
5275

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));
76+
$msg = 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);
77+
$value->addError($msg);
78+
if ($this->throwExceptions) {
79+
throw new RuntimeException($msg);
80+
}
5481
}
5582
}
5683
}
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 <[email protected]>
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 <[email protected]>
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/InlineServiceDefinitionsPass.php

+4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,14 @@ public function setRepeatedPass(RepeatedPass $repeatedPass)
3838
*
3939
* The key is the inlined service id and its value is the list of services it was inlined into.
4040
*
41+
* @deprecated since version 3.4, to be removed in 4.0.
42+
*
4143
* @return array
4244
*/
4345
public function getInlinedServiceIds()
4446
{
47+
@trigger_error('Calling InlineServiceDefinitionsPass::getInlinedServiceIds() is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED);
48+
4549
return $this->inlinedServiceIds;
4650
}
4751

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

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

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

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

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

+11
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
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Compiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Compiler\DefinitionErrorExceptionPass;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Definition;
18+
19+
class DefinitionErrorExceptionPassTest extends TestCase
20+
{
21+
/**
22+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
23+
* @expectedExceptionMessage Things went wrong!
24+
*/
25+
public function testThrowsException()
26+
{
27+
$container = new ContainerBuilder();
28+
$def = new Definition();
29+
$def->addError('Things went wrong!');
30+
$def->addError('Now something else!');
31+
$container->register('foo_service_id')
32+
->setArguments(array(
33+
$def,
34+
));
35+
36+
$pass = new DefinitionErrorExceptionPass();
37+
$pass->process($container);
38+
}
39+
40+
public function testNoExceptionThrown()
41+
{
42+
$container = new ContainerBuilder();
43+
$def = new Definition();
44+
$container->register('foo_service_id')
45+
->setArguments(array(
46+
$def,
47+
));
48+
49+
$pass = new DefinitionErrorExceptionPass();
50+
$pass->process($container);
51+
$this->assertSame($def, $container->getDefinition('foo_service_id')->getArgument(0));
52+
}
53+
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ public function testProcessDoesNotSetLazyArgumentValuesAfterInlining()
252252
$this->assertSame('inline', (string) $values[0]);
253253
}
254254

255+
/**
256+
* @group legacy
257+
*/
255258
public function testGetInlinedServiceIdData()
256259
{
257260
$container = new ContainerBuilder();

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

+9
Original file line numberDiff line numberDiff line change
@@ -387,4 +387,13 @@ public function testShouldAutoconfigure()
387387
$def->setAutoconfigured(true);
388388
$this->assertTrue($def->isAutoconfigured());
389389
}
390+
391+
public function testAddError()
392+
{
393+
$def = new Definition('stdClass');
394+
$this->assertEmpty($def->getErrors());
395+
$def->addError('First error');
396+
$def->addError('Second error');
397+
$this->assertSame(array('First error', 'Second error'), $def->getErrors());
398+
}
390399
}

0 commit comments

Comments
 (0)