Total Complexity | 194 |
Total Lines | 713 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like AutowirePass often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AutowirePass, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
36 | class AutowirePass extends AbstractRecursivePass |
||
37 | { |
||
38 | protected bool $skipScalars = true; |
||
39 | |||
40 | private array $types; |
||
41 | private array $ambiguousServiceTypes; |
||
42 | private array $autowiringAliases; |
||
43 | private ?string $lastFailure = null; |
||
44 | private ?string $decoratedClass = null; |
||
45 | private ?string $decoratedId = null; |
||
46 | private object $defaultArgument; |
||
47 | private ?\Closure $restorePreviousValue = null; |
||
48 | private ?self $typesClone = null; |
||
49 | |||
50 | public function __construct( |
||
51 | private bool $throwOnAutowiringException = true, |
||
52 | ) { |
||
53 | $this->defaultArgument = new class { |
||
54 | public $value; |
||
55 | public $names; |
||
56 | public $bag; |
||
57 | |||
58 | public function withValue(\ReflectionParameter $parameter): self |
||
59 | { |
||
60 | $clone = clone $this; |
||
61 | $clone->value = $this->bag->escapeValue($parameter->getDefaultValue()); |
||
62 | |||
63 | return $clone; |
||
64 | } |
||
65 | }; |
||
66 | } |
||
67 | |||
68 | public function process(ContainerBuilder $container): void |
||
69 | { |
||
70 | $this->defaultArgument->bag = $container->getParameterBag(); |
||
71 | |||
72 | try { |
||
73 | $this->typesClone = clone $this; |
||
74 | parent::process($container); |
||
75 | } finally { |
||
76 | $this->decoratedClass = null; |
||
77 | $this->decoratedId = null; |
||
78 | $this->defaultArgument->bag = null; |
||
79 | $this->defaultArgument->names = null; |
||
80 | $this->restorePreviousValue = null; |
||
81 | $this->typesClone = null; |
||
82 | } |
||
83 | } |
||
84 | |||
85 | protected function processValue(mixed $value, bool $isRoot = false): mixed |
||
86 | { |
||
87 | if ($value instanceof Autowire) { |
||
88 | return $this->processValue($this->container->getParameterBag()->resolveValue($value->value)); |
||
89 | } |
||
90 | |||
91 | if ($value instanceof AutowireDecorated) { |
||
92 | $definition = $this->container->getDefinition($this->currentId); |
||
93 | |||
94 | return new Reference($definition->innerServiceId ?? $this->currentId.'.inner', $definition->decorationOnInvalid ?? ContainerInterface::NULL_ON_INVALID_REFERENCE); |
||
95 | } |
||
96 | |||
97 | try { |
||
98 | return $this->doProcessValue($value, $isRoot); |
||
99 | } catch (AutowiringFailedException $e) { |
||
100 | if ($this->throwOnAutowiringException) { |
||
101 | throw $e; |
||
102 | } |
||
103 | |||
104 | $this->container->getDefinition($this->currentId)->addError($e->getMessageCallback() ?? $e->getMessage()); |
||
105 | |||
106 | return parent::processValue($value, $isRoot); |
||
107 | } |
||
108 | } |
||
109 | |||
110 | private function doProcessValue(mixed $value, bool $isRoot = false): mixed |
||
111 | { |
||
112 | if ($value instanceof TypedReference) { |
||
113 | foreach ($value->getAttributes() as $attribute) { |
||
114 | if ($attribute === $v = $this->processValue($attribute)) { |
||
115 | continue; |
||
116 | } |
||
117 | if (!$attribute instanceof Autowire || !$v instanceof Reference) { |
||
118 | return $v; |
||
119 | } |
||
120 | |||
121 | $invalidBehavior = ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE !== $v->getInvalidBehavior() ? $v->getInvalidBehavior() : $value->getInvalidBehavior(); |
||
122 | $value = $v instanceof TypedReference |
||
123 | ? new TypedReference($v, $v->getType(), $invalidBehavior, $v->getName() ?? $value->getName(), array_merge($v->getAttributes(), $value->getAttributes())) |
||
124 | : new TypedReference($v, $value->getType(), $invalidBehavior, $value->getName(), $value->getAttributes()); |
||
125 | break; |
||
126 | } |
||
127 | if ($ref = $this->getAutowiredReference($value, true)) { |
||
128 | return $ref; |
||
129 | } |
||
130 | if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { |
||
131 | $message = $this->createTypeNotFoundMessageCallback($value, 'it'); |
||
132 | |||
133 | // since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition |
||
134 | $this->container->register($id = \sprintf('.errored.%s.%s', $this->currentId, (string) $value), $value->getType()) |
||
135 | ->addError($message); |
||
136 | |||
137 | return new TypedReference($id, $value->getType(), $value->getInvalidBehavior(), $value->getName()); |
||
138 | } |
||
139 | } |
||
140 | $value = parent::processValue($value, $isRoot); |
||
141 | |||
142 | if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { |
||
143 | return $value; |
||
144 | } |
||
145 | if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { |
||
146 | $this->container->log($this, \sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass())); |
||
147 | |||
148 | return $value; |
||
149 | } |
||
150 | |||
151 | $methodCalls = $value->getMethodCalls(); |
||
152 | |||
153 | try { |
||
154 | $constructor = $this->getConstructor($value, false); |
||
155 | } catch (RuntimeException $e) { |
||
156 | throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); |
||
157 | } |
||
158 | |||
159 | if ($constructor) { |
||
160 | array_unshift($methodCalls, [$constructor, $value->getArguments()]); |
||
161 | } |
||
162 | |||
163 | $checkAttributes = !$value->hasTag('container.ignore_attributes'); |
||
164 | $methodCalls = $this->autowireCalls($methodCalls, $reflectionClass, $isRoot, $checkAttributes); |
||
165 | |||
166 | if ($constructor) { |
||
167 | [, $arguments] = array_shift($methodCalls); |
||
168 | |||
169 | if ($arguments !== $value->getArguments()) { |
||
170 | $value->setArguments($arguments); |
||
171 | } |
||
172 | } |
||
173 | |||
174 | if ($methodCalls !== $value->getMethodCalls()) { |
||
175 | $value->setMethodCalls($methodCalls); |
||
176 | } |
||
177 | |||
178 | return $value; |
||
179 | } |
||
180 | |||
181 | private function autowireCalls(array $methodCalls, \ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Autowires the constructor or a method. |
||
254 | * |
||
255 | * @throws AutowiringFailedException |
||
256 | */ |
||
257 | private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes): array |
||
258 | { |
||
259 | $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId; |
||
260 | $method = $reflectionMethod->name; |
||
261 | $parameters = $reflectionMethod->getParameters(); |
||
262 | if ($reflectionMethod->isVariadic()) { |
||
263 | array_pop($parameters); |
||
264 | } |
||
265 | $defaultArgument = clone $this->defaultArgument; |
||
266 | $defaultArgument->names = new \ArrayObject(); |
||
267 | |||
268 | foreach ($parameters as $index => $parameter) { |
||
269 | $defaultArgument->names[$index] = $parameter->name; |
||
270 | |||
271 | if (\array_key_exists($parameter->name, $arguments)) { |
||
272 | $arguments[$index] = $arguments[$parameter->name]; |
||
273 | unset($arguments[$parameter->name]); |
||
274 | } |
||
275 | if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) { |
||
276 | continue; |
||
277 | } |
||
278 | |||
279 | $type = ProxyHelper::exportType($parameter, true); |
||
280 | $target = null; |
||
281 | $name = Target::parseName($parameter, $target); |
||
282 | $target = $target ? [$target] : []; |
||
283 | $currentId = $this->currentId; |
||
284 | |||
285 | $getValue = function () use ($type, $parameter, $class, $method, $name, $target, $defaultArgument, $currentId) { |
||
286 | if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target), false)) { |
||
287 | $failureMessage = $this->createTypeNotFoundMessageCallback($ref, \sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $currentId ? $class.'::'.$method : $method)); |
||
288 | |||
289 | if ($parameter->isDefaultValueAvailable()) { |
||
290 | $value = $defaultArgument->withValue($parameter); |
||
291 | } elseif (!$parameter->allowsNull()) { |
||
292 | throw new AutowiringFailedException($currentId, $failureMessage); |
||
293 | } |
||
294 | } |
||
295 | |||
296 | return $value; |
||
297 | }; |
||
298 | |||
299 | if ($checkAttributes) { |
||
300 | $attributes = array_merge($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF), $parameter->getAttributes(Lazy::class, \ReflectionAttribute::IS_INSTANCEOF)); |
||
301 | |||
302 | if (1 < \count($attributes)) { |
||
303 | throw new AutowiringFailedException($this->currentId, 'Using both attributes #[Lazy] and #[Autowire] on an argument is not allowed; use the "lazy" parameter of #[Autowire] instead.'); |
||
304 | } |
||
305 | |||
306 | foreach ($attributes as $attribute) { |
||
307 | $attribute = $attribute->newInstance(); |
||
308 | $value = $attribute instanceof Autowire ? $attribute->value : null; |
||
309 | |||
310 | if (\is_string($value) && str_starts_with($value, '%env(') && str_ends_with($value, ')%')) { |
||
311 | if ($parameter->getType() instanceof \ReflectionNamedType && 'bool' === $parameter->getType()->getName() && !str_starts_with($value, '%env(bool:')) { |
||
312 | $attribute = new Autowire(substr_replace($value, 'bool:', 5, 0)); |
||
313 | } |
||
314 | if ($parameter->isDefaultValueAvailable() && $parameter->allowsNull() && null === $parameter->getDefaultValue() && !preg_match('/(^|:)default:/', $value)) { |
||
315 | $attribute = new Autowire(substr_replace($value, 'default::', 5, 0)); |
||
316 | } |
||
317 | } |
||
318 | |||
319 | $invalidBehavior = $parameter->allowsNull() ? ContainerInterface::NULL_ON_INVALID_REFERENCE : ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE; |
||
320 | |||
321 | try { |
||
322 | $value = $this->processValue(new TypedReference($type ?: '?', $type ?: 'mixed', $invalidBehavior, $name, [$attribute, ...$target])); |
||
323 | } catch (ParameterNotFoundException $e) { |
||
324 | if (!$parameter->isDefaultValueAvailable()) { |
||
325 | throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); |
||
326 | } |
||
327 | $arguments[$index] = clone $defaultArgument; |
||
328 | $arguments[$index]->value = $parameter->getDefaultValue(); |
||
329 | |||
330 | continue 2; |
||
331 | } |
||
332 | |||
333 | if ($attribute instanceof AutowireInline) { |
||
334 | $value = $attribute->buildDefinition($value, $type, $parameter); |
||
335 | $value = $this->doProcessValue($value); |
||
336 | } elseif ($lazy = $attribute->lazy) { |
||
337 | $definition = (new Definition($type)) |
||
338 | ->setFactory('current') |
||
339 | ->setArguments([[$value ??= $getValue()]]) |
||
340 | ->setLazy(true); |
||
341 | |||
342 | if (!\is_array($lazy)) { |
||
343 | if (str_contains($type, '|')) { |
||
344 | throw new AutowiringFailedException($this->currentId, \sprintf('Cannot use #[Autowire] with option "lazy: true" on union types for service "%s"; set the option to the interface(s) that should be proxied instead.', $this->currentId)); |
||
345 | } |
||
346 | $lazy = str_contains($type, '&') ? explode('&', $type) : []; |
||
347 | } |
||
348 | |||
349 | if ($lazy) { |
||
350 | if (!class_exists($type) && !interface_exists($type, false)) { |
||
351 | $definition->setClass('object'); |
||
352 | } |
||
353 | foreach ($lazy as $v) { |
||
354 | $definition->addTag('proxy', ['interface' => $v]); |
||
355 | } |
||
356 | } |
||
357 | |||
358 | if ($definition->getClass() !== (string) $value || $definition->getTag('proxy')) { |
||
359 | $value .= '.'.$this->container->hash([$definition->getClass(), $definition->getTag('proxy')]); |
||
360 | } |
||
361 | $this->container->setDefinition($value = '.lazy.'.$value, $definition); |
||
362 | $value = new Reference($value); |
||
363 | } |
||
364 | $arguments[$index] = $value; |
||
365 | |||
366 | continue 2; |
||
367 | } |
||
368 | |||
369 | foreach ($parameter->getAttributes(AutowireDecorated::class) as $attribute) { |
||
370 | $arguments[$index] = $this->processValue($attribute->newInstance()); |
||
371 | |||
372 | continue 2; |
||
373 | } |
||
374 | } |
||
375 | |||
376 | if (!$type) { |
||
377 | if (isset($arguments[$index])) { |
||
378 | continue; |
||
379 | } |
||
380 | |||
381 | // no default value? Then fail |
||
382 | if (!$parameter->isDefaultValueAvailable()) { |
||
383 | // For core classes, isDefaultValueAvailable() can |
||
384 | // be false when isOptional() returns true. If the |
||
385 | // argument *is* optional, allow it to be missing |
||
386 | if ($parameter->isOptional()) { |
||
387 | --$index; |
||
388 | break; |
||
389 | } |
||
390 | $type = ProxyHelper::exportType($parameter); |
||
391 | $type = $type ? \sprintf('is type-hinted "%s"', preg_replace('/(^|[(|&])\\\\|^\?\\\\?/', '\1', $type)) : 'has no type-hint'; |
||
392 | |||
393 | 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)); |
||
394 | } |
||
395 | |||
396 | // specifically pass the default value |
||
397 | $arguments[$index] = $defaultArgument->withValue($parameter); |
||
398 | |||
399 | continue; |
||
400 | } |
||
401 | |||
402 | if ($this->decoratedClass && is_a($this->decoratedClass, $type, true)) { |
||
403 | if ($this->restorePreviousValue) { |
||
404 | // The inner service is injected only if there is only 1 argument matching the type of the decorated class |
||
405 | // across all arguments of all autowired methods. |
||
406 | // If a second matching argument is found, the default behavior is restored. |
||
407 | ($this->restorePreviousValue)(); |
||
408 | $this->decoratedClass = $this->restorePreviousValue = null; // Prevent further checks |
||
409 | } else { |
||
410 | $arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass); |
||
411 | $argumentAtIndex = &$arguments[$index]; |
||
412 | $this->restorePreviousValue = static function () use (&$argumentAtIndex, $getValue) { |
||
413 | $argumentAtIndex = $getValue(); |
||
414 | }; |
||
415 | |||
416 | continue; |
||
417 | } |
||
418 | } |
||
419 | |||
420 | $arguments[$index] = $getValue(); |
||
421 | } |
||
422 | |||
423 | if ($parameters && !isset($arguments[++$index])) { |
||
424 | while (0 <= --$index) { |
||
425 | if (!$arguments[$index] instanceof $defaultArgument) { |
||
426 | break; |
||
427 | } |
||
428 | unset($arguments[$index]); |
||
429 | } |
||
430 | } |
||
431 | |||
432 | // it's possible index 1 was set, then index 0, then 2, etc |
||
433 | // make sure that we re-order so they're injected as expected |
||
434 | ksort($arguments, \SORT_NATURAL); |
||
435 | |||
436 | return $arguments; |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Returns a reference to the service matching the given type, if any. |
||
441 | */ |
||
442 | private function getAutowiredReference(TypedReference $reference, bool $filterType): ?TypedReference |
||
443 | { |
||
444 | $this->lastFailure = null; |
||
445 | $type = $reference->getType(); |
||
446 | |||
447 | if ($type !== (string) $reference) { |
||
448 | return $reference; |
||
449 | } |
||
450 | |||
451 | if ($filterType && false !== $m = strpbrk($type, '&|')) { |
||
452 | $types = array_diff(explode($m[0], $type), ['int', 'string', 'array', 'bool', 'float', 'iterable', 'object', 'callable', 'null']); |
||
453 | |||
454 | sort($types); |
||
455 | |||
456 | $type = implode($m[0], $types); |
||
457 | } |
||
458 | |||
459 | $name = $target = (array_filter($reference->getAttributes(), static fn ($a) => $a instanceof Target)[0] ?? null)?->name; |
||
460 | |||
461 | if (null !== $name ??= $reference->getName()) { |
||
462 | if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) { |
||
463 | return new TypedReference($alias, $type, $reference->getInvalidBehavior()); |
||
464 | } |
||
465 | |||
466 | if (null !== ($alias = $this->getCombinedAlias($type, $name)) && !$this->container->findDefinition($alias)->isAbstract()) { |
||
467 | return new TypedReference($alias, $type, $reference->getInvalidBehavior()); |
||
468 | } |
||
469 | |||
470 | $parsedName = (new Target($name))->getParsedName(); |
||
471 | |||
472 | if ($this->container->has($alias = $type.' $'.$parsedName) && !$this->container->findDefinition($alias)->isAbstract()) { |
||
473 | return new TypedReference($alias, $type, $reference->getInvalidBehavior()); |
||
474 | } |
||
475 | |||
476 | if (null !== ($alias = $this->getCombinedAlias($type, $parsedName)) && !$this->container->findDefinition($alias)->isAbstract()) { |
||
477 | return new TypedReference($alias, $type, $reference->getInvalidBehavior()); |
||
478 | } |
||
479 | |||
480 | if (($this->container->has($n = $name) && !$this->container->findDefinition($n)->isAbstract()) |
||
481 | || ($this->container->has($n = $parsedName) && !$this->container->findDefinition($n)->isAbstract()) |
||
482 | ) { |
||
483 | foreach ($this->container->getAliases() as $id => $alias) { |
||
484 | if ($n === (string) $alias && str_starts_with($id, $type.' $')) { |
||
485 | return new TypedReference($n, $type, $reference->getInvalidBehavior()); |
||
486 | } |
||
487 | } |
||
488 | } |
||
489 | |||
490 | if (null !== $target) { |
||
491 | return null; |
||
492 | } |
||
493 | } |
||
494 | |||
495 | if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) { |
||
496 | return new TypedReference($type, $type, $reference->getInvalidBehavior()); |
||
497 | } |
||
498 | |||
499 | if (null !== ($alias = $this->getCombinedAlias($type)) && !$this->container->findDefinition($alias)->isAbstract()) { |
||
500 | return new TypedReference($alias, $type, $reference->getInvalidBehavior()); |
||
501 | } |
||
502 | |||
503 | return null; |
||
504 | } |
||
505 | |||
506 | /** |
||
507 | * Populates the list of available types. |
||
508 | */ |
||
509 | private function populateAvailableTypes(ContainerBuilder $container): void |
||
510 | { |
||
511 | $this->types = []; |
||
512 | $this->ambiguousServiceTypes = []; |
||
513 | $this->autowiringAliases = []; |
||
514 | |||
515 | foreach ($container->getDefinitions() as $id => $definition) { |
||
516 | $this->populateAvailableType($container, $id, $definition); |
||
517 | } |
||
518 | |||
519 | $prev = null; |
||
520 | foreach ($container->getAliases() as $id => $alias) { |
||
521 | $this->populateAutowiringAlias($id, $prev); |
||
522 | $prev = $id; |
||
523 | } |
||
524 | } |
||
525 | |||
526 | /** |
||
527 | * Populates the list of available types for a given definition. |
||
528 | */ |
||
529 | private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition): void |
||
530 | { |
||
531 | // Never use abstract services |
||
532 | if ($definition->isAbstract()) { |
||
533 | return; |
||
534 | } |
||
535 | |||
536 | if ('' === $id || '.' === $id[0] || $definition->isDeprecated() || !$reflectionClass = $container->getReflectionClass($definition->getClass(), false)) { |
||
537 | return; |
||
538 | } |
||
539 | |||
540 | foreach ($reflectionClass->getInterfaces() as $reflectionInterface) { |
||
541 | $this->set($reflectionInterface->name, $id); |
||
542 | } |
||
543 | |||
544 | do { |
||
545 | $this->set($reflectionClass->name, $id); |
||
546 | } while ($reflectionClass = $reflectionClass->getParentClass()); |
||
547 | |||
548 | $this->populateAutowiringAlias($id); |
||
549 | } |
||
550 | |||
551 | /** |
||
552 | * Associates a type and a service id if applicable. |
||
553 | */ |
||
554 | private function set(string $type, string $id): void |
||
555 | { |
||
556 | // is this already a type/class that is known to match multiple services? |
||
557 | if (isset($this->ambiguousServiceTypes[$type])) { |
||
558 | $this->ambiguousServiceTypes[$type][] = $id; |
||
559 | |||
560 | return; |
||
561 | } |
||
562 | |||
563 | // check to make sure the type doesn't match multiple services |
||
564 | if (!isset($this->types[$type]) || $this->types[$type] === $id) { |
||
565 | $this->types[$type] = $id; |
||
566 | |||
567 | return; |
||
568 | } |
||
569 | |||
570 | // keep an array of all services matching this type |
||
571 | if (!isset($this->ambiguousServiceTypes[$type])) { |
||
572 | $this->ambiguousServiceTypes[$type] = [$this->types[$type]]; |
||
573 | unset($this->types[$type]); |
||
574 | } |
||
575 | $this->ambiguousServiceTypes[$type][] = $id; |
||
576 | } |
||
577 | |||
578 | private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label): \Closure |
||
579 | { |
||
580 | if (!isset($this->typesClone->container)) { |
||
581 | $this->typesClone->container = new ContainerBuilder($this->container->getParameterBag()); |
||
582 | $this->typesClone->container->setAliases($this->container->getAliases()); |
||
583 | $this->typesClone->container->setDefinitions($this->container->getDefinitions()); |
||
584 | $this->typesClone->container->setResourceTracking(false); |
||
585 | } |
||
586 | $currentId = $this->currentId; |
||
587 | |||
588 | return (fn () => $this->createTypeNotFoundMessage($reference, $label, $currentId))->bindTo($this->typesClone); |
||
589 | } |
||
590 | |||
591 | private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId): string |
||
592 | { |
||
593 | $type = $reference->getType(); |
||
594 | |||
595 | $i = null; |
||
596 | $namespace = $type; |
||
597 | do { |
||
598 | $namespace = substr($namespace, 0, $i); |
||
599 | |||
600 | if ($this->container->hasDefinition($namespace) && $tag = $this->container->getDefinition($namespace)->getTag('container.excluded')) { |
||
601 | return \sprintf('Cannot autowire service "%s": %s needs an instance of "%s" but this type has been excluded %s.', $currentId, $label, $type, $tag[0]['source'] ?? 'from autowiring'); |
||
602 | } |
||
603 | } while (false !== $i = strrpos($namespace, '\\')); |
||
604 | |||
605 | if (!$r = $this->container->getReflectionClass($type, false)) { |
||
606 | // either $type does not exist or a parent class does not exist |
||
607 | try { |
||
608 | if (class_exists(ClassExistenceResource::class)) { |
||
609 | $resource = new ClassExistenceResource($type, false); |
||
610 | // isFresh() will explode ONLY if a parent class/trait does not exist |
||
611 | $resource->isFresh(0); |
||
612 | $parentMsg = false; |
||
613 | } else { |
||
614 | $parentMsg = "couldn't be loaded. Either it was not found or it is missing a parent class or a trait"; |
||
615 | } |
||
616 | } catch (\ReflectionException $e) { |
||
617 | $parentMsg = \sprintf('is missing a parent class (%s)', $e->getMessage()); |
||
618 | } |
||
619 | |||
620 | $message = \sprintf('has type "%s" but this class %s.', $type, $parentMsg ?: 'was not found'); |
||
621 | } else { |
||
622 | $alternatives = $this->createTypeAlternatives($this->container, $reference); |
||
623 | |||
624 | if (null !== $target = (array_filter($reference->getAttributes(), static fn ($a) => $a instanceof Target)[0] ?? null)) { |
||
625 | $target = null !== $target->name ? "('{$target->name}')" : ''; |
||
626 | $message = \sprintf('has "#[Target%s]" but no such target exists.%s', $target, $alternatives); |
||
627 | } else { |
||
628 | $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; |
||
629 | $message = \sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); |
||
630 | } |
||
631 | |||
632 | if ($r->isInterface() && !$alternatives) { |
||
633 | $message .= ' Did you create an instantiable class that implements this interface?'; |
||
634 | } |
||
635 | } |
||
636 | |||
637 | $message = \sprintf('Cannot autowire service "%s": %s %s', $currentId, $label, $message); |
||
638 | |||
639 | if (null !== $this->lastFailure) { |
||
640 | $message = $this->lastFailure."\n".$message; |
||
641 | $this->lastFailure = null; |
||
642 | } |
||
643 | |||
644 | return $message; |
||
645 | } |
||
646 | |||
647 | private function createTypeAlternatives(ContainerBuilder $container, TypedReference $reference): string |
||
648 | { |
||
649 | // try suggesting available aliases first |
||
650 | if ($message = $this->getAliasesSuggestionForType($container, $type = $reference->getType())) { |
||
651 | return ' '.$message; |
||
652 | } |
||
653 | if (!isset($this->ambiguousServiceTypes)) { |
||
654 | $this->populateAvailableTypes($container); |
||
655 | } |
||
656 | |||
657 | $servicesAndAliases = $container->getServiceIds(); |
||
658 | $autowiringAliases = $this->autowiringAliases[$type] ?? []; |
||
659 | unset($autowiringAliases['']); |
||
660 | |||
661 | if ($autowiringAliases) { |
||
662 | return \sprintf(' Did you mean to target%s "%s" instead?', 1 < \count($autowiringAliases) ? ' one of' : '', implode('", "', $autowiringAliases)); |
||
663 | } |
||
664 | |||
665 | if (!$container->has($type) && false !== $key = array_search(strtolower($type), array_map('strtolower', $servicesAndAliases))) { |
||
666 | return \sprintf(' Did you mean "%s"?', $servicesAndAliases[$key]); |
||
667 | } elseif (isset($this->ambiguousServiceTypes[$type])) { |
||
668 | $message = \sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type])); |
||
669 | } elseif (isset($this->types[$type])) { |
||
670 | $message = \sprintf('the existing "%s" service', $this->types[$type]); |
||
671 | } else { |
||
672 | return ''; |
||
673 | } |
||
674 | |||
675 | return \sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message); |
||
676 | } |
||
677 | |||
678 | private function getAliasesSuggestionForType(ContainerBuilder $container, string $type): ?string |
||
679 | { |
||
680 | $aliases = []; |
||
681 | foreach (class_parents($type) + class_implements($type) as $parent) { |
||
682 | if ($container->has($parent) && !$container->findDefinition($parent)->isAbstract()) { |
||
683 | $aliases[] = $parent; |
||
684 | } |
||
685 | } |
||
686 | |||
687 | if (1 < $len = \count($aliases)) { |
||
688 | $message = 'Try changing the type-hint to one of its parents: '; |
||
689 | for ($i = 0, --$len; $i < $len; ++$i) { |
||
690 | $message .= \sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]); |
||
691 | } |
||
692 | |||
693 | return $message.\sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]); |
||
694 | } |
||
695 | |||
696 | if ($aliases) { |
||
697 | return \sprintf('Try changing the type-hint to "%s" instead.', $aliases[0]); |
||
698 | } |
||
699 | |||
700 | return null; |
||
701 | } |
||
702 | |||
703 | private function populateAutowiringAlias(string $id, ?string $target = null): void |
||
720 | } |
||
721 | } |
||
722 | |||
723 | private function getCombinedAlias(string $type, ?string $name = null): ?string |
||
724 | { |
||
725 | if (str_contains($type, '&')) { |
||
726 | $types = explode('&', $type); |
||
727 | } elseif (str_contains($type, '|')) { |
||
728 | $types = explode('|', $type); |
||
729 | } else { |
||
749 | } |
||
750 | } |
||
751 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths