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

Skip to content

Commit 7ad9582

Browse files
committed
Fixing a bug where services that were eventually removed could cause autowire errors
1 parent f562455 commit 7ad9582

File tree

6 files changed

+177
-5
lines changed

6 files changed

+177
-5
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Compiler;
4+
5+
use Symfony\Component\DependencyInjection\ContainerBuilder;
6+
7+
class AutowireExceptionPass implements CompilerPassInterface
8+
{
9+
private $autowirePass;
10+
11+
public function __construct(AutowirePass $autowirePass)
12+
{
13+
$this->autowirePass = $autowirePass;
14+
}
15+
16+
public function process(ContainerBuilder $container)
17+
{
18+
foreach ($this->autowirePass->getAutowiringExceptions() as $exception) {
19+
if ($container->hasDefinition($exception->getServiceId())) {
20+
throw $exception;
21+
}
22+
}
23+
}
24+
}

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

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Definition;
17+
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
1718
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1819
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
1920
use Symfony\Component\DependencyInjection\TypedReference;
@@ -31,6 +32,14 @@ class AutowirePass extends AbstractRecursivePass
3132
private $ambiguousServiceTypes = array();
3233
private $autowired = array();
3334
private $lastFailure;
35+
private $throwOnAutowiringException;
36+
private $autowiringExceptions = array();
37+
38+
public function __construct($throwOnAutowireException = true)
39+
{
40+
$this->throwOnAutowiringException = $throwOnAutowireException;
41+
}
42+
3443

3544
/**
3645
* {@inheritdoc}
@@ -75,6 +84,22 @@ public static function createResourceForClass(\ReflectionClass $reflectionClass)
7584
* {@inheritdoc}
7685
*/
7786
protected function processValue($value, $isRoot = false)
87+
{
88+
try {
89+
return $this->doProcessValue($value, $isRoot);
90+
} catch (AutowiringFailedException $e) {
91+
// createAutowiredDefinition() already catch the exception
92+
if ($this->throwOnAutowiringException) {
93+
throw $e;
94+
}
95+
96+
$this->autowiringExceptions[] = $e;
97+
98+
return parent::processValue($value, $isRoot);
99+
}
100+
}
101+
102+
private function doProcessValue($value, $isRoot = false)
78103
{
79104
if ($value instanceof TypedReference) {
80105
if ($ref = $this->getAutowiredReference($value)) {
@@ -190,7 +215,7 @@ private function autowireCalls(\ReflectionClass $reflectionClass, array $methodC
190215

191216
if (!$reflectionMethod->isPublic()) {
192217
$class = $reflectionClass->name;
193-
throw new RuntimeException(sprintf('Cannot autowire service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
218+
throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
194219
}
195220
$methodCalls[] = array($method, $this->autowireMethod($reflectionMethod, array()));
196221
}
@@ -231,7 +256,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
231256

232257
// no default value? Then fail
233258
if (!$parameter->isDefaultValueAvailable()) {
234-
throw new RuntimeException(sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" must have a type-hint or be given a value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
259+
throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" must have a type-hint or be given a value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
235260
}
236261

237262
// specifically pass the default value
@@ -246,7 +271,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
246271
if ($parameter->isDefaultValueAvailable()) {
247272
$value = $parameter->getDefaultValue();
248273
} elseif (!$parameter->allowsNull()) {
249-
throw new RuntimeException($failureMessage);
274+
throw new AutowiringFailedException($this->currentId, $failureMessage);
250275
}
251276
$this->container->log($this, $failureMessage);
252277
}
@@ -407,15 +432,18 @@ private function createAutowiredDefinition($type)
407432
$argumentDefinition->setAutowired(true);
408433

409434
try {
435+
$originalThrowSetting = $this->throwOnAutowiringException;
436+
$this->throwOnAutowiringException = true;
410437
$this->processValue($argumentDefinition, true);
411438
$this->container->setDefinition($argumentId, $argumentDefinition);
412-
} catch (RuntimeException $e) {
439+
} catch (AutowiringFailedException $e) {
413440
$this->autowired[$type] = false;
414441
$this->lastFailure = $e->getMessage();
415442
$this->container->log($this, $this->lastFailure);
416443

417444
return;
418445
} finally {
446+
$this->throwOnAutowiringException = $originalThrowSetting;
419447
$this->currentId = $currentId;
420448
}
421449

@@ -503,4 +531,12 @@ private static function getResourceMetadataForMethod(\ReflectionMethod $method)
503531

504532
return $methodArgumentsMetadata;
505533
}
534+
535+
/**
536+
* @return AutowiringFailedException[]
537+
*/
538+
public function getAutowiringExceptions()
539+
{
540+
return $this->autowiringExceptions;
541+
}
506542
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function __construct()
5757
new CheckDefinitionValidityPass(),
5858
new RegisterServiceSubscribersPass(),
5959
new ResolveNamedArgumentsPass(),
60-
new AutowirePass(),
60+
$autowirePass = new AutowirePass(false),
6161
new ResolveServiceSubscribersPass(),
6262
new ResolveReferencesToAliasesPass(),
6363
new ResolveInvalidReferencesPass(),
@@ -77,6 +77,7 @@ public function __construct()
7777
new AnalyzeServiceReferencesPass(),
7878
new RemoveUnusedDefinitionsPass(),
7979
)),
80+
new AutowireExceptionPass($autowirePass),
8081
new CheckExceptionOnInvalidReferenceBehaviorPass(),
8182
));
8283
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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\Exception;
13+
14+
/**
15+
* Thrown when something cannot be autowired.
16+
*/
17+
class AutowiringFailedException extends RuntimeException
18+
{
19+
private $serviceId;
20+
21+
public function __construct($serviceId, $message = '', $code = 0, Exception $previous = null)
22+
{
23+
$this->serviceId = $serviceId;
24+
25+
parent::__construct($message, $code, $previous);
26+
}
27+
28+
public function getServiceId()
29+
{
30+
return $this->serviceId;
31+
}
32+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\AutowireExceptionPass;
16+
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
19+
20+
class AutowireExceptionPassTest extends TestCase
21+
{
22+
public function testThrowsException()
23+
{
24+
$autowirePass = $this->getMockBuilder(AutowirePass::class)
25+
->getMock();
26+
27+
$autowireException = new AutowiringFailedException('foo_service_id', 'An autowiring exception message');
28+
$autowirePass->expects($this->any())
29+
->method('getAutowiringExceptions')
30+
->will($this->returnValue(array($autowireException)));
31+
32+
$container = new ContainerBuilder();
33+
$container->register('foo_service_id');
34+
35+
$pass = new AutowireExceptionPass($autowirePass);
36+
37+
try {
38+
$pass->process($container);
39+
$this->fail('->process() should throw the exception if the service id exists');
40+
} catch (\Exception $e) {
41+
$this->assertSame($autowireException, $e);
42+
}
43+
}
44+
45+
public function testNoExceptionIfServiceRemoved()
46+
{
47+
$autowirePass = $this->getMockBuilder(AutowirePass::class)
48+
->getMock();
49+
50+
$autowireException = new AutowiringFailedException('non_existent_service');
51+
$autowirePass->expects($this->any())
52+
->method('getAutowiringExceptions')
53+
->will($this->returnValue(array($autowireException)));
54+
55+
$container = new ContainerBuilder();
56+
57+
$pass = new AutowireExceptionPass($autowirePass);
58+
59+
$pass->process($container);
60+
// mark the test as passed
61+
$this->assertTrue(true);
62+
}
63+
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Compiler\AutowireExceptionPass;
1516
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
1617
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
1718
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -135,6 +136,21 @@ public function testCompleteExistingDefinitionWithNotDefinedArguments()
135136
$this->assertEquals(DInterface::class, (string) $container->getDefinition('h')->getArgument(1));
136137
}
137138

139+
public function testExceptionsAreStored()
140+
{
141+
$container = new ContainerBuilder();
142+
143+
$container->register('c1', __NAMESPACE__.'\CollisionA');
144+
$container->register('c2', __NAMESPACE__.'\CollisionB');
145+
$container->register('c3', __NAMESPACE__.'\CollisionB');
146+
$aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
147+
$aDefinition->setAutowired(true);
148+
149+
$pass = new AutowirePass(false);
150+
$pass->process($container);
151+
$this->assertCount(1, $pass->getAutowiringExceptions());
152+
}
153+
138154
/**
139155
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
140156
* @expectedExceptionMessage Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\CannotBeAutowired::__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "c1", "c2", "c3".

0 commit comments

Comments
 (0)