Version 1
[yaffs-website] / vendor / symfony / dependency-injection / Compiler / AutowirePass.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
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\ContainerBuilder;
15 use Symfony\Component\DependencyInjection\Definition;
16 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
17 use Symfony\Component\DependencyInjection\Reference;
18
19 /**
20  * Guesses constructor arguments of services definitions and try to instantiate services if necessary.
21  *
22  * @author Kévin Dunglas <dunglas@gmail.com>
23  */
24 class AutowirePass implements CompilerPassInterface
25 {
26     private $container;
27     private $reflectionClasses = array();
28     private $definedTypes = array();
29     private $types;
30     private $notGuessableTypes = array();
31     private $autowired = array();
32
33     /**
34      * {@inheritdoc}
35      */
36     public function process(ContainerBuilder $container)
37     {
38         $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); };
39         spl_autoload_register($throwingAutoloader);
40
41         try {
42             $this->container = $container;
43             foreach ($container->getDefinitions() as $id => $definition) {
44                 if ($definition->isAutowired()) {
45                     $this->completeDefinition($id, $definition);
46                 }
47             }
48         } catch (\Exception $e) {
49         } catch (\Throwable $e) {
50         }
51
52         spl_autoload_unregister($throwingAutoloader);
53
54         // Free memory and remove circular reference to container
55         $this->container = null;
56         $this->reflectionClasses = array();
57         $this->definedTypes = array();
58         $this->types = null;
59         $this->notGuessableTypes = array();
60         $this->autowired = array();
61
62         if (isset($e)) {
63             throw $e;
64         }
65     }
66
67     /**
68      * Wires the given definition.
69      *
70      * @param string     $id
71      * @param Definition $definition
72      *
73      * @throws RuntimeException
74      */
75     private function completeDefinition($id, Definition $definition)
76     {
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));
79         }
80
81         if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
82             return;
83         }
84
85         $this->container->addClassResource($reflectionClass);
86
87         if (!$constructor = $reflectionClass->getConstructor()) {
88             return;
89         }
90         $parameters = $constructor->getParameters();
91         if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) {
92             array_pop($parameters);
93         }
94
95         $arguments = $definition->getArguments();
96         foreach ($parameters as $index => $parameter) {
97             if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
98                 continue;
99             }
100
101             try {
102                 if (!$typeHint = $parameter->getClass()) {
103                     if (isset($arguments[$index])) {
104                         continue;
105                     }
106
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));
110                     }
111
112                     // specifically pass the default value
113                     $arguments[$index] = $parameter->getDefaultValue();
114
115                     continue;
116                 }
117
118                 if (isset($this->autowired[$typeHint->name])) {
119                     return $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null;
120                 }
121
122                 if (null === $this->types) {
123                     $this->populateAvailableTypes();
124                 }
125
126                 if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) {
127                     $value = new Reference($this->types[$typeHint->name]);
128                 } else {
129                     try {
130                         $value = $this->createAutowiredDefinition($typeHint, $id);
131                     } catch (RuntimeException $e) {
132                         if ($parameter->isDefaultValueAvailable()) {
133                             $value = $parameter->getDefaultValue();
134                         } elseif ($parameter->allowsNull()) {
135                             $value = null;
136                         } else {
137                             throw $e;
138                         }
139                         $this->autowired[$typeHint->name] = false;
140                     }
141                 }
142             } catch (\ReflectionException $e) {
143                 // Typehint against a non-existing class
144
145                 if (!$parameter->isDefaultValueAvailable()) {
146                     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);
147                 }
148
149                 $value = $parameter->getDefaultValue();
150             }
151
152             $arguments[$index] = $value;
153         }
154
155         if ($parameters && !isset($arguments[++$index])) {
156             while (0 <= --$index) {
157                 $parameter = $parameters[$index];
158                 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
159                     break;
160                 }
161                 unset($arguments[$index]);
162             }
163         }
164
165         // it's possible index 1 was set, then index 0, then 2, etc
166         // make sure that we re-order so they're injected as expected
167         ksort($arguments);
168         $definition->setArguments($arguments);
169     }
170
171     /**
172      * Populates the list of available types.
173      */
174     private function populateAvailableTypes()
175     {
176         $this->types = array();
177
178         foreach ($this->container->getDefinitions() as $id => $definition) {
179             $this->populateAvailableType($id, $definition);
180         }
181     }
182
183     /**
184      * Populates the list of available types for a given definition.
185      *
186      * @param string     $id
187      * @param Definition $definition
188      */
189     private function populateAvailableType($id, Definition $definition)
190     {
191         // Never use abstract services
192         if ($definition->isAbstract()) {
193             return;
194         }
195
196         foreach ($definition->getAutowiringTypes() as $type) {
197             $this->definedTypes[$type] = true;
198             $this->types[$type] = $id;
199             unset($this->notGuessableTypes[$type]);
200         }
201
202         if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
203             return;
204         }
205
206         foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
207             $this->set($reflectionInterface->name, $id);
208         }
209
210         do {
211             $this->set($reflectionClass->name, $id);
212         } while ($reflectionClass = $reflectionClass->getParentClass());
213     }
214
215     /**
216      * Associates a type and a service id if applicable.
217      *
218      * @param string $type
219      * @param string $id
220      */
221     private function set($type, $id)
222     {
223         if (isset($this->definedTypes[$type])) {
224             return;
225         }
226
227         if (!isset($this->types[$type])) {
228             $this->types[$type] = $id;
229
230             return;
231         }
232
233         if ($this->types[$type] === $id) {
234             return;
235         }
236
237         if (!isset($this->notGuessableTypes[$type])) {
238             $this->notGuessableTypes[$type] = true;
239             $this->types[$type] = (array) $this->types[$type];
240         }
241
242         $this->types[$type][] = $id;
243     }
244
245     /**
246      * Registers a definition for the type if possible or throws an exception.
247      *
248      * @param \ReflectionClass $typeHint
249      * @param string           $id
250      *
251      * @return Reference A reference to the registered definition
252      *
253      * @throws RuntimeException
254      */
255     private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
256     {
257         if (isset($this->notGuessableTypes[$typeHint->name])) {
258             $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
259             $matchingServices = implode(', ', $this->types[$typeHint->name]);
260
261             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));
262         }
263
264         if (!$typeHint->isInstantiable()) {
265             $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
266             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));
267         }
268
269         $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name);
270
271         $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
272         $argumentDefinition->setPublic(false);
273
274         try {
275             $this->completeDefinition($argumentId, $argumentDefinition);
276         } catch (RuntimeException $e) {
277             $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
278             $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);
279             throw new RuntimeException($message, 0, $e);
280         }
281
282         return new Reference($argumentId);
283     }
284
285     /**
286      * Retrieves the reflection class associated with the given service.
287      *
288      * @param string     $id
289      * @param Definition $definition
290      *
291      * @return \ReflectionClass|false
292      */
293     private function getReflectionClass($id, Definition $definition)
294     {
295         if (isset($this->reflectionClasses[$id])) {
296             return $this->reflectionClasses[$id];
297         }
298
299         // Cannot use reflection if the class isn't set
300         if (!$class = $definition->getClass()) {
301             return false;
302         }
303
304         $class = $this->container->getParameterBag()->resolveValue($class);
305
306         try {
307             $reflector = new \ReflectionClass($class);
308         } catch (\ReflectionException $e) {
309             $reflector = false;
310         }
311
312         return $this->reflectionClasses[$id] = $reflector;
313     }
314 }