Yaffs site version 1.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                     $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null;
120                     continue;
121                 }
122
123                 if (null === $this->types) {
124                     $this->populateAvailableTypes();
125                 }
126
127                 if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) {
128                     $value = new Reference($this->types[$typeHint->name]);
129                 } else {
130                     try {
131                         $value = $this->createAutowiredDefinition($typeHint, $id);
132                     } catch (RuntimeException $e) {
133                         if ($parameter->isDefaultValueAvailable()) {
134                             $value = $parameter->getDefaultValue();
135                         } elseif ($parameter->allowsNull()) {
136                             $value = null;
137                         } else {
138                             throw $e;
139                         }
140                         $this->autowired[$typeHint->name] = false;
141                     }
142                 }
143             } catch (\ReflectionException $e) {
144                 // Typehint against a non-existing class
145
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);
148                 }
149
150                 $value = $parameter->getDefaultValue();
151             }
152
153             $arguments[$index] = $value;
154         }
155
156         if ($parameters && !isset($arguments[++$index])) {
157             while (0 <= --$index) {
158                 $parameter = $parameters[$index];
159                 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
160                     break;
161                 }
162                 unset($arguments[$index]);
163             }
164         }
165
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
168         ksort($arguments);
169         $definition->setArguments($arguments);
170     }
171
172     /**
173      * Populates the list of available types.
174      */
175     private function populateAvailableTypes()
176     {
177         $this->types = array();
178
179         foreach ($this->container->getDefinitions() as $id => $definition) {
180             $this->populateAvailableType($id, $definition);
181         }
182     }
183
184     /**
185      * Populates the list of available types for a given definition.
186      *
187      * @param string     $id
188      * @param Definition $definition
189      */
190     private function populateAvailableType($id, Definition $definition)
191     {
192         // Never use abstract services
193         if ($definition->isAbstract()) {
194             return;
195         }
196
197         foreach ($definition->getAutowiringTypes() as $type) {
198             $this->definedTypes[$type] = true;
199             $this->types[$type] = $id;
200             unset($this->notGuessableTypes[$type]);
201         }
202
203         if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
204             return;
205         }
206
207         foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
208             $this->set($reflectionInterface->name, $id);
209         }
210
211         do {
212             $this->set($reflectionClass->name, $id);
213         } while ($reflectionClass = $reflectionClass->getParentClass());
214     }
215
216     /**
217      * Associates a type and a service id if applicable.
218      *
219      * @param string $type
220      * @param string $id
221      */
222     private function set($type, $id)
223     {
224         if (isset($this->definedTypes[$type])) {
225             return;
226         }
227
228         if (!isset($this->types[$type])) {
229             $this->types[$type] = $id;
230
231             return;
232         }
233
234         if ($this->types[$type] === $id) {
235             return;
236         }
237
238         if (!isset($this->notGuessableTypes[$type])) {
239             $this->notGuessableTypes[$type] = true;
240             $this->types[$type] = (array) $this->types[$type];
241         }
242
243         $this->types[$type][] = $id;
244     }
245
246     /**
247      * Registers a definition for the type if possible or throws an exception.
248      *
249      * @param \ReflectionClass $typeHint
250      * @param string           $id
251      *
252      * @return Reference A reference to the registered definition
253      *
254      * @throws RuntimeException
255      */
256     private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
257     {
258         if (isset($this->notGuessableTypes[$typeHint->name])) {
259             $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
260             $matchingServices = implode(', ', $this->types[$typeHint->name]);
261
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));
263         }
264
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));
268         }
269
270         $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name);
271
272         $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
273         $argumentDefinition->setPublic(false);
274
275         try {
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);
281         }
282
283         return new Reference($argumentId);
284     }
285
286     /**
287      * Retrieves the reflection class associated with the given service.
288      *
289      * @param string     $id
290      * @param Definition $definition
291      *
292      * @return \ReflectionClass|false
293      */
294     private function getReflectionClass($id, Definition $definition)
295     {
296         if (isset($this->reflectionClasses[$id])) {
297             return $this->reflectionClasses[$id];
298         }
299
300         // Cannot use reflection if the class isn't set
301         if (!$class = $definition->getClass()) {
302             return false;
303         }
304
305         $class = $this->container->getParameterBag()->resolveValue($class);
306
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);
310             });
311         }
312
313         $e = null;
314
315         try {
316             $reflector = new \ReflectionClass($class);
317         } catch (\Exception $e) {
318         } catch (\Throwable $e) {
319         }
320
321         if ($deprecated) {
322             restore_error_handler();
323         }
324
325         if (null !== $e) {
326             if (!$e instanceof \ReflectionException) {
327                 throw $e;
328             }
329             $reflector = false;
330         }
331
332         return $this->reflectionClasses[$id] = $reflector;
333     }
334 }