| 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