Updating Media dependent modules to versions compatible with core Media.
[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\Config\Resource\ClassExistenceResource;
15 use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
16 use Symfony\Component\DependencyInjection\ContainerBuilder;
17 use Symfony\Component\DependencyInjection\Definition;
18 use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
19 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
20 use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
21 use Symfony\Component\DependencyInjection\TypedReference;
22
23 /**
24  * Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
25  *
26  * @author Kévin Dunglas <dunglas@gmail.com>
27  * @author Nicolas Grekas <p@tchwork.com>
28  */
29 class AutowirePass extends AbstractRecursivePass
30 {
31     private $definedTypes = array();
32     private $types;
33     private $ambiguousServiceTypes;
34     private $autowired = array();
35     private $lastFailure;
36     private $throwOnAutowiringException;
37     private $autowiringExceptions = array();
38     private $strictMode;
39
40     /**
41      * @param bool $throwOnAutowireException Errors can be retrieved via Definition::getErrors()
42      */
43     public function __construct($throwOnAutowireException = true)
44     {
45         $this->throwOnAutowiringException = $throwOnAutowireException;
46     }
47
48     /**
49      * @deprecated since version 3.4, to be removed in 4.0.
50      *
51      * @return AutowiringFailedException[]
52      */
53     public function getAutowiringExceptions()
54     {
55         @trigger_error('Calling AutowirePass::getAutowiringExceptions() is deprecated since Symfony 3.4 and will be removed in 4.0. Use Definition::getErrors() instead.', E_USER_DEPRECATED);
56
57         return $this->autowiringExceptions;
58     }
59
60     /**
61      * {@inheritdoc}
62      */
63     public function process(ContainerBuilder $container)
64     {
65         // clear out any possibly stored exceptions from before
66         $this->autowiringExceptions = array();
67         $this->strictMode = $container->hasParameter('container.autowiring.strict_mode') && $container->getParameter('container.autowiring.strict_mode');
68
69         try {
70             parent::process($container);
71         } finally {
72             $this->definedTypes = array();
73             $this->types = null;
74             $this->ambiguousServiceTypes = null;
75             $this->autowired = array();
76         }
77     }
78
79     /**
80      * Creates a resource to help know if this service has changed.
81      *
82      * @param \ReflectionClass $reflectionClass
83      *
84      * @return AutowireServiceResource
85      *
86      * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.
87      */
88     public static function createResourceForClass(\ReflectionClass $reflectionClass)
89     {
90         @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', E_USER_DEPRECATED);
91
92         $metadata = array();
93
94         foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
95             if (!$reflectionMethod->isStatic()) {
96                 $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
97             }
98         }
99
100         return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
101     }
102
103     /**
104      * {@inheritdoc}
105      */
106     protected function processValue($value, $isRoot = false)
107     {
108         try {
109             return $this->doProcessValue($value, $isRoot);
110         } catch (AutowiringFailedException $e) {
111             if ($this->throwOnAutowiringException) {
112                 throw $e;
113             }
114
115             $this->autowiringExceptions[] = $e;
116             $this->container->getDefinition($this->currentId)->addError($e->getMessage());
117
118             return parent::processValue($value, $isRoot);
119         }
120     }
121
122     private function doProcessValue($value, $isRoot = false)
123     {
124         if ($value instanceof TypedReference) {
125             if ($ref = $this->getAutowiredReference($value, $value->getRequiringClass() ? sprintf('for "%s" in "%s"', $value->getType(), $value->getRequiringClass()) : '')) {
126                 return $ref;
127             }
128             $this->container->log($this, $this->createTypeNotFoundMessage($value, 'it'));
129         }
130         $value = parent::processValue($value, $isRoot);
131
132         if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
133             return $value;
134         }
135         if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
136             $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
137
138             return $value;
139         }
140
141         $methodCalls = $value->getMethodCalls();
142
143         try {
144             $constructor = $this->getConstructor($value, false);
145         } catch (RuntimeException $e) {
146             throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
147         }
148
149         if ($constructor) {
150             array_unshift($methodCalls, array($constructor, $value->getArguments()));
151         }
152
153         $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls);
154
155         if ($constructor) {
156             list(, $arguments) = array_shift($methodCalls);
157
158             if ($arguments !== $value->getArguments()) {
159                 $value->setArguments($arguments);
160             }
161         }
162
163         if ($methodCalls !== $value->getMethodCalls()) {
164             $value->setMethodCalls($methodCalls);
165         }
166
167         return $value;
168     }
169
170     /**
171      * @param \ReflectionClass $reflectionClass
172      * @param array            $methodCalls
173      *
174      * @return array
175      */
176     private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls)
177     {
178         foreach ($methodCalls as $i => $call) {
179             list($method, $arguments) = $call;
180
181             if ($method instanceof \ReflectionFunctionAbstract) {
182                 $reflectionMethod = $method;
183             } else {
184                 $reflectionMethod = $this->getReflectionMethod(new Definition($reflectionClass->name), $method);
185             }
186
187             $arguments = $this->autowireMethod($reflectionMethod, $arguments);
188
189             if ($arguments !== $call[1]) {
190                 $methodCalls[$i][1] = $arguments;
191             }
192         }
193
194         return $methodCalls;
195     }
196
197     /**
198      * Autowires the constructor or a method.
199      *
200      * @param \ReflectionFunctionAbstract $reflectionMethod
201      * @param array                       $arguments
202      *
203      * @return array The autowired arguments
204      *
205      * @throws AutowiringFailedException
206      */
207     private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments)
208     {
209         $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
210         $method = $reflectionMethod->name;
211         $parameters = $reflectionMethod->getParameters();
212         if (method_exists('ReflectionMethod', 'isVariadic') && $reflectionMethod->isVariadic()) {
213             array_pop($parameters);
214         }
215
216         foreach ($parameters as $index => $parameter) {
217             if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
218                 continue;
219             }
220
221             $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
222
223             if (!$type) {
224                 if (isset($arguments[$index])) {
225                     continue;
226                 }
227
228                 // no default value? Then fail
229                 if (!$parameter->isDefaultValueAvailable()) {
230                     // For core classes, isDefaultValueAvailable() can
231                     // be false when isOptional() returns true. If the
232                     // argument *is* optional, allow it to be missing
233                     if ($parameter->isOptional()) {
234                         continue;
235                     }
236                     $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false);
237                     $type = $type ? sprintf('is type-hinted "%s"', $type) : 'has no type-hint';
238
239                     throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type));
240                 }
241
242                 // specifically pass the default value
243                 $arguments[$index] = $parameter->getDefaultValue();
244
245                 continue;
246             }
247
248             if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, !$parameter->isOptional() ? $class : ''), 'for '.sprintf('argument "$%s" of method "%s()"', $parameter->name, $class.'::'.$method))) {
249                 $failureMessage = $this->createTypeNotFoundMessage($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
250
251                 if ($parameter->isDefaultValueAvailable()) {
252                     $value = $parameter->getDefaultValue();
253                 } elseif (!$parameter->allowsNull()) {
254                     throw new AutowiringFailedException($this->currentId, $failureMessage);
255                 }
256                 $this->container->log($this, $failureMessage);
257             }
258
259             $arguments[$index] = $value;
260         }
261
262         if ($parameters && !isset($arguments[++$index])) {
263             while (0 <= --$index) {
264                 $parameter = $parameters[$index];
265                 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
266                     break;
267                 }
268                 unset($arguments[$index]);
269             }
270         }
271
272         // it's possible index 1 was set, then index 0, then 2, etc
273         // make sure that we re-order so they're injected as expected
274         ksort($arguments);
275
276         return $arguments;
277     }
278
279     /**
280      * @return TypedReference|null A reference to the service matching the given type, if any
281      */
282     private function getAutowiredReference(TypedReference $reference, $deprecationMessage)
283     {
284         $this->lastFailure = null;
285         $type = $reference->getType();
286
287         if ($type !== $this->container->normalizeId($reference) || ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract())) {
288             return $reference;
289         }
290
291         if (null === $this->types) {
292             $this->populateAvailableTypes($this->strictMode);
293         }
294
295         if (isset($this->definedTypes[$type])) {
296             return new TypedReference($this->types[$type], $type);
297         }
298
299         if (!$this->strictMode && isset($this->types[$type])) {
300             $message = 'Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won\'t be supported in version 4.0.';
301             if ($aliasSuggestion = $this->getAliasesSuggestionForType($type = $reference->getType(), $deprecationMessage)) {
302                 $message .= ' '.$aliasSuggestion;
303             } else {
304                 $message .= sprintf(' You should %s the "%s" service to "%s" instead.', isset($this->types[$this->types[$type]]) ? 'alias' : 'rename (or alias)', $this->types[$type], $type);
305             }
306
307             @trigger_error($message, E_USER_DEPRECATED);
308
309             return new TypedReference($this->types[$type], $type);
310         }
311
312         if (!$reference->canBeAutoregistered() || isset($this->types[$type]) || isset($this->ambiguousServiceTypes[$type])) {
313             return;
314         }
315
316         if (isset($this->autowired[$type])) {
317             return $this->autowired[$type] ? new TypedReference($this->autowired[$type], $type) : null;
318         }
319
320         if (!$this->strictMode) {
321             return $this->createAutowiredDefinition($type);
322         }
323     }
324
325     /**
326      * Populates the list of available types.
327      */
328     private function populateAvailableTypes($onlyAutowiringTypes = false)
329     {
330         $this->types = array();
331         if (!$onlyAutowiringTypes) {
332             $this->ambiguousServiceTypes = array();
333         }
334
335         foreach ($this->container->getDefinitions() as $id => $definition) {
336             $this->populateAvailableType($id, $definition, $onlyAutowiringTypes);
337         }
338     }
339
340     /**
341      * Populates the list of available types for a given definition.
342      *
343      * @param string     $id
344      * @param Definition $definition
345      */
346     private function populateAvailableType($id, Definition $definition, $onlyAutowiringTypes)
347     {
348         // Never use abstract services
349         if ($definition->isAbstract()) {
350             return;
351         }
352
353         foreach ($definition->getAutowiringTypes(false) as $type) {
354             $this->definedTypes[$type] = true;
355             $this->types[$type] = $id;
356             unset($this->ambiguousServiceTypes[$type]);
357         }
358
359         if ($onlyAutowiringTypes) {
360             return;
361         }
362
363         if (preg_match('/^\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id) || $definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) {
364             return;
365         }
366
367         foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
368             $this->set($reflectionInterface->name, $id);
369         }
370
371         do {
372             $this->set($reflectionClass->name, $id);
373         } while ($reflectionClass = $reflectionClass->getParentClass());
374     }
375
376     /**
377      * Associates a type and a service id if applicable.
378      *
379      * @param string $type
380      * @param string $id
381      */
382     private function set($type, $id)
383     {
384         if (isset($this->definedTypes[$type])) {
385             return;
386         }
387
388         // is this already a type/class that is known to match multiple services?
389         if (isset($this->ambiguousServiceTypes[$type])) {
390             $this->ambiguousServiceTypes[$type][] = $id;
391
392             return;
393         }
394
395         // check to make sure the type doesn't match multiple services
396         if (!isset($this->types[$type]) || $this->types[$type] === $id) {
397             $this->types[$type] = $id;
398
399             return;
400         }
401
402         // keep an array of all services matching this type
403         if (!isset($this->ambiguousServiceTypes[$type])) {
404             $this->ambiguousServiceTypes[$type] = array($this->types[$type]);
405             unset($this->types[$type]);
406         }
407         $this->ambiguousServiceTypes[$type][] = $id;
408     }
409
410     /**
411      * Registers a definition for the type if possible or throws an exception.
412      *
413      * @param string $type
414      *
415      * @return TypedReference|null A reference to the registered definition
416      */
417     private function createAutowiredDefinition($type)
418     {
419         if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) {
420             return;
421         }
422
423         $currentId = $this->currentId;
424         $this->currentId = $type;
425         $this->autowired[$type] = $argumentId = sprintf('autowired.%s', $type);
426         $argumentDefinition = new Definition($type);
427         $argumentDefinition->setPublic(false);
428         $argumentDefinition->setAutowired(true);
429
430         try {
431             $originalThrowSetting = $this->throwOnAutowiringException;
432             $this->throwOnAutowiringException = true;
433             $this->processValue($argumentDefinition, true);
434             $this->container->setDefinition($argumentId, $argumentDefinition);
435         } catch (AutowiringFailedException $e) {
436             $this->autowired[$type] = false;
437             $this->lastFailure = $e->getMessage();
438             $this->container->log($this, $this->lastFailure);
439
440             return;
441         } finally {
442             $this->throwOnAutowiringException = $originalThrowSetting;
443             $this->currentId = $currentId;
444         }
445
446         @trigger_error(sprintf('Relying on service auto-registration for type "%s" is deprecated since Symfony 3.4 and won\'t be supported in 4.0. Create a service named "%s" instead.', $type, $type), E_USER_DEPRECATED);
447
448         $this->container->log($this, sprintf('Type "%s" has been auto-registered for service "%s".', $type, $this->currentId));
449
450         return new TypedReference($argumentId, $type);
451     }
452
453     private function createTypeNotFoundMessage(TypedReference $reference, $label)
454     {
455         if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
456             // either $type does not exist or a parent class does not exist
457             try {
458                 $resource = new ClassExistenceResource($type, false);
459                 // isFresh() will explode ONLY if a parent class/trait does not exist
460                 $resource->isFresh(0);
461                 $parentMsg = false;
462             } catch (\ReflectionException $e) {
463                 $parentMsg = $e->getMessage();
464             }
465
466             $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
467         } else {
468             $alternatives = $this->createTypeAlternatives($reference);
469             $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
470             $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives);
471
472             if ($r->isInterface() && !$alternatives) {
473                 $message .= ' Did you create a class that implements this interface?';
474             }
475         }
476
477         $message = sprintf('Cannot autowire service "%s": %s %s', $this->currentId, $label, $message);
478
479         if (null !== $this->lastFailure) {
480             $message = $this->lastFailure."\n".$message;
481             $this->lastFailure = null;
482         }
483
484         return $message;
485     }
486
487     private function createTypeAlternatives(TypedReference $reference)
488     {
489         // try suggesting available aliases first
490         if ($message = $this->getAliasesSuggestionForType($type = $reference->getType())) {
491             return ' '.$message;
492         }
493         if (null === $this->ambiguousServiceTypes) {
494             $this->populateAvailableTypes();
495         }
496
497         if (isset($this->ambiguousServiceTypes[$type])) {
498             $message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type]));
499         } elseif (isset($this->types[$type])) {
500             $message = sprintf('the existing "%s" service', $this->types[$type]);
501         } elseif ($reference->getRequiringClass() && !$reference->canBeAutoregistered() && !$this->strictMode) {
502             return ' It cannot be auto-registered because it is from a different root namespace.';
503         } else {
504             return;
505         }
506
507         return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message);
508     }
509
510     /**
511      * @deprecated since version 3.3, to be removed in 4.0.
512      */
513     private static function getResourceMetadataForMethod(\ReflectionMethod $method)
514     {
515         $methodArgumentsMetadata = array();
516         foreach ($method->getParameters() as $parameter) {
517             try {
518                 $class = $parameter->getClass();
519             } catch (\ReflectionException $e) {
520                 // type-hint is against a non-existent class
521                 $class = false;
522             }
523
524             $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic();
525             $methodArgumentsMetadata[] = array(
526                 'class' => $class,
527                 'isOptional' => $parameter->isOptional(),
528                 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null,
529             );
530         }
531
532         return $methodArgumentsMetadata;
533     }
534
535     private function getAliasesSuggestionForType($type, $extraContext = null)
536     {
537         $aliases = array();
538         foreach (class_parents($type) + class_implements($type) as $parent) {
539             if ($this->container->has($parent) && !$this->container->findDefinition($parent)->isAbstract()) {
540                 $aliases[] = $parent;
541             }
542         }
543
544         $extraContext = $extraContext ? ' '.$extraContext : '';
545         if (1 < $len = count($aliases)) {
546             $message = sprintf('Try changing the type-hint%s to one of its parents: ', $extraContext);
547             for ($i = 0, --$len; $i < $len; ++$i) {
548                 $message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
549             }
550             $message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
551
552             return $message;
553         }
554
555         if ($aliases) {
556             return sprintf('Try changing the type-hint%s to "%s" instead.', $extraContext, $aliases[0]);
557         }
558     }
559 }