Skip to content

Commit e8b87b1

Browse files
[DI] Add logging and better failure recovery to AutowirePass
1 parent 6643fe2 commit e8b87b1

File tree

2 files changed

+52
-18
lines changed

2 files changed

+52
-18
lines changed

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

+48-14
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
use Symfony\Component\DependencyInjection\TypedReference;
2121

2222
/**
23-
* Guesses constructor arguments of services definitions and try to instantiate services if necessary.
23+
* Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
2424
*
2525
* @author Kévin Dunglas <dunglas@gmail.com>
26+
* @author Nicolas Grekas <p@tchwork.com>
2627
*/
2728
class AutowirePass extends AbstractRecursivePass
2829
{
@@ -95,6 +96,8 @@ protected function processValue($value, $isRoot = false)
9596
if ($value instanceof TypedReference && $this->currentDefinition->isAutowired() && !$this->container->has((string) $value)) {
9697
if ($ref = $this->getAutowiredReference($value->getType(), $value->canBeAutoregistered())) {
9798
$value = new TypedReference((string) $ref, $value->getType(), $value->getInvalidBehavior(), $value->canBeAutoregistered());
99+
} else {
100+
$this->container->log($this, $this->createTypeNotFoundMessage($value->getType(), 'typed reference'));
98101
}
99102
}
100103
if (!$value instanceof Definition) {
@@ -275,18 +278,17 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu
275278

276279
if ($value = $this->getAutowiredReference($type)) {
277280
$this->usedTypes[$type] = $this->currentId;
278-
} elseif ($parameter->isDefaultValueAvailable()) {
279-
$value = $parameter->getDefaultValue();
280-
} elseif ($parameter->allowsNull()) {
281-
$value = null;
282281
} else {
283-
if ($classOrInterface = class_exists($type, false) ? 'class' : (interface_exists($type, false) ? 'interface' : null)) {
284-
$message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $type, $this->currentId, $classOrInterface);
282+
$failureMessage = $this->createTypeNotFoundMessage($type, 'argument $'.$parameter->name.' of method '.$reflectionMethod->class.'::'.$reflectionMethod->name.'()');
283+
284+
if ($parameter->isDefaultValueAvailable()) {
285+
$value = $parameter->getDefaultValue();
286+
} elseif ($parameter->allowsNull()) {
287+
$value = null;
285288
} else {
286-
$message = sprintf('Cannot autowire argument $%s of method %s::%s() for service "%s": Class %s does not exist.', $parameter->name, $reflectionMethod->class, $reflectionMethod->name, $this->currentId, $type);
289+
throw new RuntimeException($failureMessage);
287290
}
288-
289-
throw new RuntimeException($message);
291+
$this->container->log($this, $failureMessage);
290292
}
291293

292294
$arguments[$index] = $value;
@@ -320,6 +322,7 @@ private function autowireOverridenGetters(array $overridenGetters, array $autowi
320322
}
321323

322324
if (!$typeRef = $this->getAutowiredReference($type)) {
325+
$this->container->log($this, $this->createTypeNotFoundMessage($type, 'return value of method '.$reflectionMethod->class.'::'.$reflectionMethod->name.'()'));
323326
continue;
324327
}
325328

@@ -344,6 +347,8 @@ private function getAutowiredReference($type, $autoRegister = true)
344347
}
345348

346349
if (isset($this->types[$type])) {
350+
$this->container->log($this, sprintf('Service "%s" matches type "%s" and has been autowired into service "%s".', $this->types[$type], $type, $this->currentId));
351+
347352
return new Reference($this->types[$type]);
348353
}
349354

@@ -449,24 +454,53 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint)
449454
}
450455

451456
if (!$typeHint->isInstantiable()) {
457+
$this->container->log($this, sprintf('Type "%s" is not instantiable thus cannot be auto-registered for service "%s".', $typeHint->name, $this->currentId));
458+
452459
return;
453460
}
454461

462+
$ambiguousServiceTypes = $this->ambiguousServiceTypes;
463+
$currentDefinition = $this->currentDefinition;
464+
$definitions = $this->container->getDefinitions();
455465
$currentId = $this->currentId;
456466
$this->currentId = $argumentId = sprintf('autowired.%s', $typeHint->name);
457-
458-
$argumentDefinition = $this->container->register($argumentId, $typeHint->name);
467+
$this->currentDefinition = $argumentDefinition = (new Definition($typeHint->name));
459468
$argumentDefinition->setPublic(false);
460469
$argumentDefinition->setAutowired(true);
461470

462471
$this->populateAvailableType($argumentId, $argumentDefinition);
463472

464-
$this->processValue($argumentDefinition, true);
465-
$this->currentId = $currentId;
473+
try {
474+
$this->processValue($argumentDefinition, true);
475+
$this->container->setDefinition($argumentId, $argumentDefinition);
476+
} catch (RuntimeException $e) {
477+
// revert any changes done to our internal state
478+
unset($this->types[$typeHint->name]);
479+
$this->ambiguousServiceTypes = $ambiguousServiceTypes;
480+
$this->container->setDefinitions($definitions);
481+
$this->container->log($this, $e->getMessage());
482+
483+
return;
484+
} finally {
485+
$this->currentId = $currentId;
486+
$this->currentDefinition = $currentDefinition;
487+
}
488+
489+
$this->container->log($this, sprintf('Type "%s" has been auto-registered for service "%s".', $typeHint->name, $this->currentId));
466490

467491
return new Reference($argumentId);
468492
}
469493

494+
private function createTypeNotFoundMessage($type, $label)
495+
{
496+
if (!$classOrInterface = class_exists($type, false) ? 'class' : (interface_exists($type, false) ? 'interface' : null)) {
497+
return sprintf('Cannot autowire %s for service "%s": Class or interface "%s" does not exist.', $label, $this->currentId, $type);
498+
}
499+
$message = sprintf('No services were found matching the "%s" %s and it cannot be auto-registered', $type, $classOrInterface);
500+
501+
return sprintf('Cannot autowire %s for service "%s": %s.', $label, $this->currentId, $message);
502+
}
503+
470504
/**
471505
* @deprecated since version 3.3, to be removed in 4.0.
472506
*/

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public function testTypeNotGuessableWithSubclass()
177177

178178
/**
179179
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
180-
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". No services were found matching this interface and it cannot be auto-registered.
180+
* @expectedExceptionMessage Cannot autowire argument $collision of method Symfony\Component\DependencyInjection\Tests\Compiler\CannotBeAutowired::__construct() for service "a": No services were found matching the "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" interface and it cannot be auto-registered.
181181
*/
182182
public function testTypeNotGuessableNoServicesFound()
183183
{
@@ -295,7 +295,7 @@ public function testDontTriggerAutowiring()
295295

296296
/**
297297
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
298-
* @expectedExceptionMessage Cannot autowire argument $r of method Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct() for service "a": Class Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass does not exist.
298+
* @expectedExceptionMessage Cannot autowire argument $r of method Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct() for service "a": Class or interface "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" does not exist.
299299
*/
300300
public function testClassNotFoundThrowsException()
301301
{
@@ -310,7 +310,7 @@ public function testClassNotFoundThrowsException()
310310

311311
/**
312312
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
313-
* @expectedExceptionMessage Cannot autowire argument $r of method Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct() for service "a": Class Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass does not exist.
313+
* @expectedExceptionMessage Cannot autowire argument $r of method Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct() for service "a": Class or interface "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" does not exist.
314314
*/
315315
public function testParentClassNotFoundThrowsException()
316316
{
@@ -716,7 +716,7 @@ public function testNotWireableCalls($method, $expectedMsg)
716716
public function provideNotWireableCalls()
717717
{
718718
return array(
719-
array('setNotAutowireable', 'Cannot autowire argument $n of method Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable() for service "foo": Class Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass does not exist.'),
719+
array('setNotAutowireable', 'Cannot autowire argument $n of method Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable() for service "foo": Class or interface "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" does not exist.'),
720720
array('setBar', 'Cannot autowire service "foo": method Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setBar() has only optional arguments, thus must be wired explicitly.'),
721721
array('setOptionalNotAutowireable', 'Cannot autowire service "foo": method Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setOptionalNotAutowireable() has only optional arguments, thus must be wired explicitly.'),
722722
array('setOptionalNoTypeHint', 'Cannot autowire service "foo": method Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setOptionalNoTypeHint() has only optional arguments, thus must be wired explicitly.'),

0 commit comments

Comments
 (0)