diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 9454d1bd4b..445a544f41 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -28,7 +28,7 @@ class AutowirePass implements CompilerPassInterface private $definedTypes = array(); private $types; private $notGuessableTypes = array(); - private $usedTypes = array(); + private $autowired = array(); /** * {@inheritdoc} @@ -45,15 +45,6 @@ class AutowirePass implements CompilerPassInterface $this->completeDefinition($id, $definition); } } - - foreach ($this->usedTypes as $type => $id) { - if (isset($this->usedTypes[$type]) && isset($this->notGuessableTypes[$type])) { - $classOrInterface = class_exists($type) ? 'class' : 'interface'; - $matchingServices = implode(', ', $this->types[$type]); - - throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $type, $id, $classOrInterface, $matchingServices)); - } - } } catch (\Exception $e) { } catch (\Throwable $e) { } @@ -66,7 +57,7 @@ class AutowirePass implements CompilerPassInterface $this->definedTypes = array(); $this->types = null; $this->notGuessableTypes = array(); - $this->usedTypes = array(); + $this->autowired = array(); if (isset($e)) { throw $e; @@ -92,47 +83,56 @@ class AutowirePass implements CompilerPassInterface if (!$constructor = $reflectionClass->getConstructor()) { return; } + $parameters = $constructor->getParameters(); + if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) { + array_pop($parameters); + } $arguments = $definition->getArguments(); - foreach ($constructor->getParameters() as $index => $parameter) { + foreach ($parameters as $index => $parameter) { if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) { continue; } try { if (!$typeHint = $parameter->getClass()) { + if (isset($arguments[$index])) { + continue; + } + // no default value? Then fail if (!$parameter->isOptional()) { throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id)); } - if (!array_key_exists($index, $arguments)) { - // specifically pass the default value - $arguments[$index] = $parameter->getDefaultValue(); - } + // specifically pass the default value + $arguments[$index] = $parameter->getDefaultValue(); continue; } + if (isset($this->autowired[$typeHint->name])) { + return $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null; + } + if (null === $this->types) { $this->populateAvailableTypes(); } if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) { $value = new Reference($this->types[$typeHint->name]); - $this->usedTypes[$typeHint->name] = $id; } else { try { $value = $this->createAutowiredDefinition($typeHint, $id); - $this->usedTypes[$typeHint->name] = $id; } catch (RuntimeException $e) { - if ($parameter->allowsNull()) { - $value = null; - } elseif ($parameter->isDefaultValueAvailable()) { + if ($parameter->isDefaultValueAvailable()) { $value = $parameter->getDefaultValue(); + } elseif ($parameter->allowsNull()) { + $value = null; } else { throw $e; } + $this->autowired[$typeHint->name] = false; } } } catch (\ReflectionException $e) { @@ -148,6 +148,16 @@ class AutowirePass implements CompilerPassInterface $arguments[$index] = $value; } + if ($parameters && !isset($arguments[++$index])) { + while (0 <= --$index) { + $parameter = $parameters[$index]; + if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) { + break; + } + unset($arguments[$index]); + } + } + // it's possible index 1 was set, then index 0, then 2, etc // make sure that we re-order so they're injected as expected ksort($arguments); @@ -252,13 +262,11 @@ class AutowirePass implements CompilerPassInterface throw new RuntimeException(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.', $typeHint->name, $id, $classOrInterface)); } - $argumentId = sprintf('autowired.%s', $typeHint->name); + $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name); $argumentDefinition = $this->container->register($argumentId, $typeHint->name); $argumentDefinition->setPublic(false); - $this->populateAvailableType($argumentId, $argumentDefinition); - try { $this->completeDefinition($argumentId, $argumentDefinition); } catch (RuntimeException $e) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 047801ceba..9a41fa2ee0 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -422,10 +422,6 @@ class AutowirePassTest extends TestCase array( new Reference('a'), new Reference('lille'), - // third arg shouldn't *need* to be passed - // but that's hard to "pull of" with autowiring, so - // this assumes passing the default val is ok - 'some_val', ), $definition->getArguments() ); @@ -462,23 +458,6 @@ class AutowirePassTest extends TestCase $this->assertEquals(array(new Reference('a'), '', new Reference('lille')), $container->getDefinition('foo')->getArguments()); } - /** - * @dataProvider provideAutodiscoveredAutowiringOrder - * - * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException - * @expectedExceptionMEssage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". Multiple services exist for this interface (autowired.Symfony\Component\DependencyInjection\Tests\Compiler\CollisionA, autowired.Symfony\Component\DependencyInjection\Tests\Compiler\CollisionB). - */ - public function testAutodiscoveredAutowiringOrder($class) - { - $container = new ContainerBuilder(); - - $container->register('a', __NAMESPACE__.'\\'.$class) - ->setAutowired(true); - - $pass = new AutowirePass(); - $pass->process($container); - } - public function provideAutodiscoveredAutowiringOrder() { return array(