Security update for Core, with self-updated composer
[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\Config\AutowireServiceResource;
15 use Symfony\Component\DependencyInjection\ContainerBuilder;
16 use Symfony\Component\DependencyInjection\Definition;
17 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
18 use Symfony\Component\DependencyInjection\Reference;
19
20 /**
21  * Guesses constructor arguments of services definitions and try to instantiate services if necessary.
22  *
23  * @author Kévin Dunglas <dunglas@gmail.com>
24  */
25 class AutowirePass implements CompilerPassInterface
26 {
27     private $container;
28     private $reflectionClasses = array();
29     private $definedTypes = array();
30     private $types;
31     private $ambiguousServiceTypes = array();
32     private $autowired = array();
33
34     /**
35      * {@inheritdoc}
36      */
37     public function process(ContainerBuilder $container)
38     {
39         $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); };
40         spl_autoload_register($throwingAutoloader);
41
42         try {
43             $this->container = $container;
44             foreach ($container->getDefinitions() as $id => $definition) {
45                 if ($definition->isAutowired()) {
46                     $this->completeDefinition($id, $definition);
47                 }
48             }
49         } finally {
50             spl_autoload_unregister($throwingAutoloader);
51
52             // Free memory and remove circular reference to container
53             $this->reflectionClasses = array();
54             $this->definedTypes = array();
55             $this->types = null;
56             $this->ambiguousServiceTypes = array();
57             $this->autowired = array();
58         }
59     }
60
61     /**
62      * Creates a resource to help know if this service has changed.
63      *
64      * @param \ReflectionClass $reflectionClass
65      *
66      * @return AutowireServiceResource
67      */
68     public static function createResourceForClass(\ReflectionClass $reflectionClass)
69     {
70         $metadata = array();
71
72         if ($constructor = $reflectionClass->getConstructor()) {
73             $metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
74         }
75
76         foreach (self::getSetters($reflectionClass) as $reflectionMethod) {
77             $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
78         }
79
80         return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
81     }
82
83     /**
84      * Wires the given definition.
85      *
86      * @param string     $id
87      * @param Definition $definition
88      *
89      * @throws RuntimeException
90      */
91     private function completeDefinition($id, Definition $definition)
92     {
93         if ($definition->getFactory()) {
94             throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $id));
95         }
96
97         if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
98             return;
99         }
100
101         if ($this->container->isTrackingResources()) {
102             $this->container->addResource(static::createResourceForClass($reflectionClass));
103         }
104
105         if (!$constructor = $reflectionClass->getConstructor()) {
106             return;
107         }
108         $parameters = $constructor->getParameters();
109         if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) {
110             array_pop($parameters);
111         }
112
113         $arguments = $definition->getArguments();
114         foreach ($parameters as $index => $parameter) {
115             if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
116                 continue;
117             }
118
119             try {
120                 if (!$typeHint = $parameter->getClass()) {
121                     if (isset($arguments[$index])) {
122                         continue;
123                     }
124
125                     // no default value? Then fail
126                     if (!$parameter->isOptional()) {
127                         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));
128                     }
129
130                     // specifically pass the default value
131                     $arguments[$index] = $parameter->getDefaultValue();
132
133                     continue;
134                 }
135
136                 if (isset($this->autowired[$typeHint->name])) {
137                     $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null;
138                     continue;
139                 }
140
141                 if (null === $this->types) {
142                     $this->populateAvailableTypes();
143                 }
144
145                 if (isset($this->types[$typeHint->name])) {
146                     $value = new Reference($this->types[$typeHint->name]);
147                 } else {
148                     try {
149                         $value = $this->createAutowiredDefinition($typeHint, $id);
150                     } catch (RuntimeException $e) {
151                         if ($parameter->isDefaultValueAvailable()) {
152                             $value = $parameter->getDefaultValue();
153                         } elseif ($parameter->allowsNull()) {
154                             $value = null;
155                         } else {
156                             throw $e;
157                         }
158                         $this->autowired[$typeHint->name] = false;
159                     }
160                 }
161             } catch (\ReflectionException $e) {
162                 // Typehint against a non-existing class
163
164                 if (!$parameter->isDefaultValueAvailable()) {
165                     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);
166                 }
167
168                 $value = $parameter->getDefaultValue();
169             }
170
171             $arguments[$index] = $value;
172         }
173
174         if ($parameters && !isset($arguments[++$index])) {
175             while (0 <= --$index) {
176                 $parameter = $parameters[$index];
177                 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
178                     break;
179                 }
180                 unset($arguments[$index]);
181             }
182         }
183
184         // it's possible index 1 was set, then index 0, then 2, etc
185         // make sure that we re-order so they're injected as expected
186         ksort($arguments);
187         $definition->setArguments($arguments);
188     }
189
190     /**
191      * Populates the list of available types.
192      */
193     private function populateAvailableTypes()
194     {
195         $this->types = array();
196
197         foreach ($this->container->getDefinitions() as $id => $definition) {
198             $this->populateAvailableType($id, $definition);
199         }
200     }
201
202     /**
203      * Populates the list of available types for a given definition.
204      *
205      * @param string     $id
206      * @param Definition $definition
207      */
208     private function populateAvailableType($id, Definition $definition)
209     {
210         // Never use abstract services
211         if ($definition->isAbstract()) {
212             return;
213         }
214
215         foreach ($definition->getAutowiringTypes() as $type) {
216             $this->definedTypes[$type] = true;
217             $this->types[$type] = $id;
218             unset($this->ambiguousServiceTypes[$type]);
219         }
220
221         if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
222             return;
223         }
224
225         foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
226             $this->set($reflectionInterface->name, $id);
227         }
228
229         do {
230             $this->set($reflectionClass->name, $id);
231         } while ($reflectionClass = $reflectionClass->getParentClass());
232     }
233
234     /**
235      * Associates a type and a service id if applicable.
236      *
237      * @param string $type
238      * @param string $id
239      */
240     private function set($type, $id)
241     {
242         if (isset($this->definedTypes[$type])) {
243             return;
244         }
245
246         // is this already a type/class that is known to match multiple services?
247         if (isset($this->ambiguousServiceTypes[$type])) {
248             $this->ambiguousServiceTypes[$type][] = $id;
249
250             return;
251         }
252
253         // check to make sure the type doesn't match multiple services
254         if (!isset($this->types[$type]) || $this->types[$type] === $id) {
255             $this->types[$type] = $id;
256
257             return;
258         }
259
260         // keep an array of all services matching this type
261         if (!isset($this->ambiguousServiceTypes[$type])) {
262             $this->ambiguousServiceTypes[$type] = array($this->types[$type]);
263             unset($this->types[$type]);
264         }
265         $this->ambiguousServiceTypes[$type][] = $id;
266     }
267
268     /**
269      * Registers a definition for the type if possible or throws an exception.
270      *
271      * @param \ReflectionClass $typeHint
272      * @param string           $id
273      *
274      * @return Reference A reference to the registered definition
275      *
276      * @throws RuntimeException
277      */
278     private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
279     {
280         if (isset($this->ambiguousServiceTypes[$typeHint->name])) {
281             $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
282             $matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]);
283
284             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));
285         }
286
287         if (!$typeHint->isInstantiable()) {
288             $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
289             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));
290         }
291
292         $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name);
293
294         $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
295         $argumentDefinition->setPublic(false);
296
297         try {
298             $this->completeDefinition($argumentId, $argumentDefinition);
299         } catch (RuntimeException $e) {
300             $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
301             $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);
302             throw new RuntimeException($message, 0, $e);
303         }
304
305         return new Reference($argumentId);
306     }
307
308     /**
309      * Retrieves the reflection class associated with the given service.
310      *
311      * @param string     $id
312      * @param Definition $definition
313      *
314      * @return \ReflectionClass|false
315      */
316     private function getReflectionClass($id, Definition $definition)
317     {
318         if (isset($this->reflectionClasses[$id])) {
319             return $this->reflectionClasses[$id];
320         }
321
322         // Cannot use reflection if the class isn't set
323         if (!$class = $definition->getClass()) {
324             return false;
325         }
326
327         $class = $this->container->getParameterBag()->resolveValue($class);
328
329         if ($deprecated = $definition->isDeprecated()) {
330             $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
331                 return (E_USER_DEPRECATED === $level || !$prevErrorHandler) ? false : $prevErrorHandler($level, $message, $file, $line);
332             });
333         }
334
335         $e = null;
336
337         try {
338             $reflector = new \ReflectionClass($class);
339         } catch (\Exception $e) {
340         } catch (\Throwable $e) {
341         }
342
343         if ($deprecated) {
344             restore_error_handler();
345         }
346
347         if (null !== $e) {
348             if (!$e instanceof \ReflectionException) {
349                 throw $e;
350             }
351             $reflector = false;
352         }
353
354         return $this->reflectionClasses[$id] = $reflector;
355     }
356
357     /**
358      * @param \ReflectionClass $reflectionClass
359      *
360      * @return \ReflectionMethod[]
361      */
362     private static function getSetters(\ReflectionClass $reflectionClass)
363     {
364         foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
365             if (!$reflectionMethod->isStatic() && 1 === $reflectionMethod->getNumberOfParameters() && 0 === strpos($reflectionMethod->name, 'set')) {
366                 yield $reflectionMethod;
367             }
368         }
369     }
370
371     private static function getResourceMetadataForMethod(\ReflectionMethod $method)
372     {
373         $methodArgumentsMetadata = array();
374         foreach ($method->getParameters() as $parameter) {
375             try {
376                 $class = $parameter->getClass();
377             } catch (\ReflectionException $e) {
378                 // type-hint is against a non-existent class
379                 $class = false;
380             }
381
382             $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic();
383             $methodArgumentsMetadata[] = array(
384                 'class' => $class,
385                 'isOptional' => $parameter->isOptional(),
386                 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null,
387             );
388         }
389
390         return $methodArgumentsMetadata;
391     }
392 }