4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\DependencyInjection\Compiler;
14 use Symfony\Component\DependencyInjection\ContainerBuilder;
15 use Symfony\Component\DependencyInjection\Definition;
16 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
17 use Symfony\Component\DependencyInjection\Reference;
20 * Guesses constructor arguments of services definitions and try to instantiate services if necessary.
22 * @author Kévin Dunglas <dunglas@gmail.com>
24 class AutowirePass implements CompilerPassInterface
27 private $reflectionClasses = array();
28 private $definedTypes = array();
30 private $notGuessableTypes = array();
31 private $autowired = array();
36 public function process(ContainerBuilder $container)
38 $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); };
39 spl_autoload_register($throwingAutoloader);
42 $this->container = $container;
43 foreach ($container->getDefinitions() as $id => $definition) {
44 if ($definition->isAutowired()) {
45 $this->completeDefinition($id, $definition);
48 } catch (\Exception $e) {
49 } catch (\Throwable $e) {
52 spl_autoload_unregister($throwingAutoloader);
54 // Free memory and remove circular reference to container
55 $this->container = null;
56 $this->reflectionClasses = array();
57 $this->definedTypes = array();
59 $this->notGuessableTypes = array();
60 $this->autowired = array();
68 * Wires the given definition.
71 * @param Definition $definition
73 * @throws RuntimeException
75 private function completeDefinition($id, Definition $definition)
77 if ($definition->getFactory() || $definition->getFactoryClass(false) || $definition->getFactoryService(false)) {
78 throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $id));
81 if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
85 $this->container->addClassResource($reflectionClass);
87 if (!$constructor = $reflectionClass->getConstructor()) {
90 $parameters = $constructor->getParameters();
91 if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) {
92 array_pop($parameters);
95 $arguments = $definition->getArguments();
96 foreach ($parameters as $index => $parameter) {
97 if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
102 if (!$typeHint = $parameter->getClass()) {
103 if (isset($arguments[$index])) {
107 // no default value? Then fail
108 if (!$parameter->isOptional()) {
109 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));
112 // specifically pass the default value
113 $arguments[$index] = $parameter->getDefaultValue();
118 if (isset($this->autowired[$typeHint->name])) {
119 $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null;
123 if (null === $this->types) {
124 $this->populateAvailableTypes();
127 if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) {
128 $value = new Reference($this->types[$typeHint->name]);
131 $value = $this->createAutowiredDefinition($typeHint, $id);
132 } catch (RuntimeException $e) {
133 if ($parameter->isDefaultValueAvailable()) {
134 $value = $parameter->getDefaultValue();
135 } elseif ($parameter->allowsNull()) {
140 $this->autowired[$typeHint->name] = false;
143 } catch (\ReflectionException $e) {
144 // Typehint against a non-existing class
146 if (!$parameter->isDefaultValueAvailable()) {
147 throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e);
150 $value = $parameter->getDefaultValue();
153 $arguments[$index] = $value;
156 if ($parameters && !isset($arguments[++$index])) {
157 while (0 <= --$index) {
158 $parameter = $parameters[$index];
159 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
162 unset($arguments[$index]);
166 // it's possible index 1 was set, then index 0, then 2, etc
167 // make sure that we re-order so they're injected as expected
169 $definition->setArguments($arguments);
173 * Populates the list of available types.
175 private function populateAvailableTypes()
177 $this->types = array();
179 foreach ($this->container->getDefinitions() as $id => $definition) {
180 $this->populateAvailableType($id, $definition);
185 * Populates the list of available types for a given definition.
188 * @param Definition $definition
190 private function populateAvailableType($id, Definition $definition)
192 // Never use abstract services
193 if ($definition->isAbstract()) {
197 foreach ($definition->getAutowiringTypes() as $type) {
198 $this->definedTypes[$type] = true;
199 $this->types[$type] = $id;
200 unset($this->notGuessableTypes[$type]);
203 if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
207 foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
208 $this->set($reflectionInterface->name, $id);
212 $this->set($reflectionClass->name, $id);
213 } while ($reflectionClass = $reflectionClass->getParentClass());
217 * Associates a type and a service id if applicable.
219 * @param string $type
222 private function set($type, $id)
224 if (isset($this->definedTypes[$type])) {
228 if (!isset($this->types[$type])) {
229 $this->types[$type] = $id;
234 if ($this->types[$type] === $id) {
238 if (!isset($this->notGuessableTypes[$type])) {
239 $this->notGuessableTypes[$type] = true;
240 $this->types[$type] = (array) $this->types[$type];
243 $this->types[$type][] = $id;
247 * Registers a definition for the type if possible or throws an exception.
249 * @param \ReflectionClass $typeHint
252 * @return Reference A reference to the registered definition
254 * @throws RuntimeException
256 private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
258 if (isset($this->notGuessableTypes[$typeHint->name])) {
259 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
260 $matchingServices = implode(', ', $this->types[$typeHint->name]);
262 throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices));
265 if (!$typeHint->isInstantiable()) {
266 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
267 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));
270 $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name);
272 $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
273 $argumentDefinition->setPublic(false);
276 $this->completeDefinition($argumentId, $argumentDefinition);
277 } catch (RuntimeException $e) {
278 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
279 $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.', $typeHint->name, $id, $classOrInterface);
280 throw new RuntimeException($message, 0, $e);
283 return new Reference($argumentId);
287 * Retrieves the reflection class associated with the given service.
290 * @param Definition $definition
292 * @return \ReflectionClass|false
294 private function getReflectionClass($id, Definition $definition)
296 if (isset($this->reflectionClasses[$id])) {
297 return $this->reflectionClasses[$id];
300 // Cannot use reflection if the class isn't set
301 if (!$class = $definition->getClass()) {
305 $class = $this->container->getParameterBag()->resolveValue($class);
307 if ($deprecated = $definition->isDeprecated()) {
308 $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
309 return (E_USER_DEPRECATED === $level || !$prevErrorHandler) ? false : $prevErrorHandler($level, $message, $file, $line);
316 $reflector = new \ReflectionClass($class);
317 } catch (\Exception $e) {
318 } catch (\Throwable $e) {
322 restore_error_handler();
326 if (!$e instanceof \ReflectionException) {
332 return $this->reflectionClasses[$id] = $reflector;