vendor/symfony/dependency-injection/Compiler/AutowirePass.php line 625

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\DependencyInjection\Compiler;
  11. use Symfony\Component\Config\Resource\ClassExistenceResource;
  12. use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
  13. use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
  14. use Symfony\Component\DependencyInjection\Attribute\Autowire;
  15. use Symfony\Component\DependencyInjection\Attribute\MapDecorated;
  16. use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
  17. use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
  18. use Symfony\Component\DependencyInjection\Attribute\Target;
  19. use Symfony\Component\DependencyInjection\ContainerBuilder;
  20. use Symfony\Component\DependencyInjection\ContainerInterface;
  21. use Symfony\Component\DependencyInjection\Definition;
  22. use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
  23. use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
  24. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  25. use Symfony\Component\DependencyInjection\Reference;
  26. use Symfony\Component\DependencyInjection\TypedReference;
  27. use Symfony\Component\VarExporter\ProxyHelper;
  28. use Symfony\Contracts\Service\Attribute\SubscribedService;
  29. /**
  30.  * Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
  31.  *
  32.  * @author Kévin Dunglas <dunglas@gmail.com>
  33.  * @author Nicolas Grekas <p@tchwork.com>
  34.  */
  35. class AutowirePass extends AbstractRecursivePass
  36. {
  37.     private array $types;
  38.     private array $ambiguousServiceTypes;
  39.     private array $autowiringAliases;
  40.     private ?string $lastFailure null;
  41.     private bool $throwOnAutowiringException;
  42.     private ?string $decoratedClass null;
  43.     private ?string $decoratedId null;
  44.     private ?array $methodCalls null;
  45.     private object $defaultArgument;
  46.     private ?\Closure $getPreviousValue null;
  47.     private ?int $decoratedMethodIndex null;
  48.     private ?int $decoratedMethodArgumentIndex null;
  49.     private ?self $typesClone null;
  50.     public function __construct(bool $throwOnAutowireException true)
  51.     {
  52.         $this->throwOnAutowiringException $throwOnAutowireException;
  53.         $this->defaultArgument = new class() {
  54.             public $value;
  55.             public $names;
  56.         };
  57.     }
  58.     public function process(ContainerBuilder $container)
  59.     {
  60.         try {
  61.             $this->typesClone = clone $this;
  62.             parent::process($container);
  63.         } finally {
  64.             $this->decoratedClass null;
  65.             $this->decoratedId null;
  66.             $this->methodCalls null;
  67.             $this->defaultArgument->names null;
  68.             $this->getPreviousValue null;
  69.             $this->decoratedMethodIndex null;
  70.             $this->decoratedMethodArgumentIndex null;
  71.             $this->typesClone null;
  72.         }
  73.     }
  74.     protected function processValue(mixed $valuebool $isRoot false): mixed
  75.     {
  76.         try {
  77.             return $this->doProcessValue($value$isRoot);
  78.         } catch (AutowiringFailedException $e) {
  79.             if ($this->throwOnAutowiringException) {
  80.                 throw $e;
  81.             }
  82.             $this->container->getDefinition($this->currentId)->addError($e->getMessageCallback() ?? $e->getMessage());
  83.             return parent::processValue($value$isRoot);
  84.         }
  85.     }
  86.     private function doProcessValue(mixed $valuebool $isRoot false): mixed
  87.     {
  88.         if ($value instanceof TypedReference) {
  89.             if ($attributes $value->getAttributes()) {
  90.                 $attribute array_pop($attributes);
  91.                 if ($attributes) {
  92.                     throw new AutowiringFailedException($this->currentIdsprintf('Using multiple attributes with "%s" is not supported.'SubscribedService::class));
  93.                 }
  94.                 if (!$attribute instanceof Target) {
  95.                     return $this->processAttribute($attributeContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior());
  96.                 }
  97.                 $value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name);
  98.             }
  99.             if ($ref $this->getAutowiredReference($valuetrue)) {
  100.                 return $ref;
  101.             }
  102.             if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
  103.                 $message $this->createTypeNotFoundMessageCallback($value'it');
  104.                 // since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition
  105.                 $this->container->register($id sprintf('.errored.%s.%s'$this->currentId, (string) $value), $value->getType())
  106.                     ->addError($message);
  107.                 return new TypedReference($id$value->getType(), $value->getInvalidBehavior(), $value->getName());
  108.             }
  109.         }
  110.         $value parent::processValue($value$isRoot);
  111.         if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
  112.             return $value;
  113.         }
  114.         if (!$reflectionClass $this->container->getReflectionClass($value->getClass(), false)) {
  115.             $this->container->log($thissprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.'$this->currentId$value->getClass()));
  116.             return $value;
  117.         }
  118.         $this->methodCalls $value->getMethodCalls();
  119.         try {
  120.             $constructor $this->getConstructor($valuefalse);
  121.         } catch (RuntimeException $e) {
  122.             throw new AutowiringFailedException($this->currentId$e->getMessage(), 0$e);
  123.         }
  124.         if ($constructor) {
  125.             array_unshift($this->methodCalls, [$constructor$value->getArguments()]);
  126.         }
  127.         $checkAttributes = !$value->hasTag('container.ignore_attributes');
  128.         $this->methodCalls $this->autowireCalls($reflectionClass$isRoot$checkAttributes);
  129.         if ($constructor) {
  130.             [, $arguments] = array_shift($this->methodCalls);
  131.             if ($arguments !== $value->getArguments()) {
  132.                 $value->setArguments($arguments);
  133.             }
  134.         }
  135.         if ($this->methodCalls !== $value->getMethodCalls()) {
  136.             $value->setMethodCalls($this->methodCalls);
  137.         }
  138.         return $value;
  139.     }
  140.     private function processAttribute(object $attributebool $isOptional false): mixed
  141.     {
  142.         switch (true) {
  143.             case $attribute instanceof Autowire:
  144.                 $value $this->container->getParameterBag()->resolveValue($attribute->value);
  145.                 return $value instanceof Reference && $isOptional ? new Reference($valueContainerInterface::NULL_ON_INVALID_REFERENCE) : $value;
  146.             case $attribute instanceof TaggedIterator:
  147.                 return new TaggedIteratorArgument($attribute->tag$attribute->indexAttribute$attribute->defaultIndexMethodfalse$attribute->defaultPriorityMethod, (array) $attribute->exclude);
  148.             case $attribute instanceof TaggedLocator:
  149.                 return new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag$attribute->indexAttribute$attribute->defaultIndexMethodtrue$attribute->defaultPriorityMethod, (array) $attribute->exclude));
  150.             case $attribute instanceof MapDecorated:
  151.                 $definition $this->container->getDefinition($this->currentId);
  152.                 return new Reference($definition->innerServiceId ?? $this->currentId.'.inner'$definition->decorationOnInvalid ?? ContainerInterface::NULL_ON_INVALID_REFERENCE);
  153.         }
  154.         throw new AutowiringFailedException($this->currentIdsprintf('"%s" is an unsupported attribute.'$attribute::class));
  155.     }
  156.     private function autowireCalls(\ReflectionClass $reflectionClassbool $isRootbool $checkAttributes): array
  157.     {
  158.         $this->decoratedId null;
  159.         $this->decoratedClass null;
  160.         $this->getPreviousValue null;
  161.         if ($isRoot && ($definition $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId $definition->innerServiceId) && $this->container->has($this->decoratedId)) {
  162.             $this->decoratedClass $this->container->findDefinition($this->decoratedId)->getClass();
  163.         }
  164.         $patchedIndexes = [];
  165.         foreach ($this->methodCalls as $i => $call) {
  166.             [$method$arguments] = $call;
  167.             if ($method instanceof \ReflectionFunctionAbstract) {
  168.                 $reflectionMethod $method;
  169.             } else {
  170.                 $definition = new Definition($reflectionClass->name);
  171.                 try {
  172.                     $reflectionMethod $this->getReflectionMethod($definition$method);
  173.                 } catch (RuntimeException $e) {
  174.                     if ($definition->getFactory()) {
  175.                         continue;
  176.                     }
  177.                     throw $e;
  178.                 }
  179.             }
  180.             $arguments $this->autowireMethod($reflectionMethod$arguments$checkAttributes$i);
  181.             if ($arguments !== $call[1]) {
  182.                 $this->methodCalls[$i][1] = $arguments;
  183.                 $patchedIndexes[] = $i;
  184.             }
  185.         }
  186.         // use named arguments to skip complex default values
  187.         foreach ($patchedIndexes as $i) {
  188.             $namedArguments null;
  189.             $arguments $this->methodCalls[$i][1];
  190.             foreach ($arguments as $j => $value) {
  191.                 if ($namedArguments && !$value instanceof $this->defaultArgument) {
  192.                     unset($arguments[$j]);
  193.                     $arguments[$namedArguments[$j]] = $value;
  194.                 }
  195.                 if ($namedArguments || !$value instanceof $this->defaultArgument) {
  196.                     continue;
  197.                 }
  198.                 if (\is_array($value->value) ? $value->value \is_object($value->value)) {
  199.                     unset($arguments[$j]);
  200.                     $namedArguments $value->names;
  201.                 } else {
  202.                     $arguments[$j] = $value->value;
  203.                 }
  204.             }
  205.             $this->methodCalls[$i][1] = $arguments;
  206.         }
  207.         return $this->methodCalls;
  208.     }
  209.     /**
  210.      * Autowires the constructor or a method.
  211.      *
  212.      * @throws AutowiringFailedException
  213.      */
  214.     private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $argumentsbool $checkAttributesint $methodIndex): array
  215.     {
  216.         $class $reflectionMethod instanceof \ReflectionMethod $reflectionMethod->class $this->currentId;
  217.         $method $reflectionMethod->name;
  218.         $parameters $reflectionMethod->getParameters();
  219.         if ($reflectionMethod->isVariadic()) {
  220.             array_pop($parameters);
  221.         }
  222.         $this->defaultArgument->names = new \ArrayObject();
  223.         foreach ($parameters as $index => $parameter) {
  224.             $this->defaultArgument->names[$index] = $parameter->name;
  225.             if (\array_key_exists($parameter->name$arguments)) {
  226.                 $arguments[$index] = $arguments[$parameter->name];
  227.                 unset($arguments[$parameter->name]);
  228.             }
  229.             if (\array_key_exists($index$arguments) && '' !== $arguments[$index]) {
  230.                 continue;
  231.             }
  232.             if ($checkAttributes) {
  233.                 foreach ($parameter->getAttributes() as $attribute) {
  234.                     if (\in_array($attribute->getName(), [TaggedIterator::class, TaggedLocator::class, Autowire::class, MapDecorated::class], true)) {
  235.                         try {
  236.                             $arguments[$index] = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull());
  237.                             continue 2;
  238.                         } catch (ParameterNotFoundException $e) {
  239.                             if (!$parameter->isDefaultValueAvailable()) {
  240.                                 throw new AutowiringFailedException($this->currentId$e->getMessage(), 0$e);
  241.                             }
  242.                             $arguments[$index] = clone $this->defaultArgument;
  243.                             $arguments[$index]->value $parameter->getDefaultValue();
  244.                         }
  245.                     }
  246.                 }
  247.             }
  248.             if (!$type ProxyHelper::exportType($parametertrue)) {
  249.                 if (isset($arguments[$index])) {
  250.                     continue;
  251.                 }
  252.                 // no default value? Then fail
  253.                 if (!$parameter->isDefaultValueAvailable()) {
  254.                     // For core classes, isDefaultValueAvailable() can
  255.                     // be false when isOptional() returns true. If the
  256.                     // argument *is* optional, allow it to be missing
  257.                     if ($parameter->isOptional()) {
  258.                         --$index;
  259.                         break;
  260.                     }
  261.                     $type ProxyHelper::exportType($parameter);
  262.                     $type $type sprintf('is type-hinted "%s"'preg_replace('/(^|[(|&])\\\\|^\?\\\\?/''\1'$type)) : 'has no type-hint';
  263.                     throw new AutowiringFailedException($this->currentIdsprintf('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));
  264.                 }
  265.                 // specifically pass the default value
  266.                 $arguments[$index] = clone $this->defaultArgument;
  267.                 $arguments[$index]->value $parameter->getDefaultValue();
  268.                 continue;
  269.             }
  270.             $getValue = function () use ($type$parameter$class$method) {
  271.                 if (!$value $this->getAutowiredReference($ref = new TypedReference($type$typeContainerBuilder::EXCEPTION_ON_INVALID_REFERENCETarget::parseName($parameter)), false)) {
  272.                     $failureMessage $this->createTypeNotFoundMessageCallback($refsprintf('argument "$%s" of method "%s()"'$parameter->name$class !== $this->currentId $class.'::'.$method $method));
  273.                     if ($parameter->isDefaultValueAvailable()) {
  274.                         $value = clone $this->defaultArgument;
  275.                         $value->value $parameter->getDefaultValue();
  276.                     } elseif (!$parameter->allowsNull()) {
  277.                         throw new AutowiringFailedException($this->currentId$failureMessage);
  278.                     }
  279.                 }
  280.                 return $value;
  281.             };
  282.             if ($this->decoratedClass && $isDecorated is_a($this->decoratedClass$typetrue)) {
  283.                 if ($this->getPreviousValue) {
  284.                     // The inner service is injected only if there is only 1 argument matching the type of the decorated class
  285.                     // across all arguments of all autowired methods.
  286.                     // If a second matching argument is found, the default behavior is restored.
  287.                     $getPreviousValue $this->getPreviousValue;
  288.                     $this->methodCalls[$this->decoratedMethodIndex][1][$this->decoratedMethodArgumentIndex] = $getPreviousValue();
  289.                     $this->decoratedClass null// Prevent further checks
  290.                 } else {
  291.                     $arguments[$index] = new TypedReference($this->decoratedId$this->decoratedClass);
  292.                     $this->getPreviousValue $getValue;
  293.                     $this->decoratedMethodIndex $methodIndex;
  294.                     $this->decoratedMethodArgumentIndex $index;
  295.                     continue;
  296.                 }
  297.             }
  298.             $arguments[$index] = $getValue();
  299.         }
  300.         if ($parameters && !isset($arguments[++$index])) {
  301.             while (<= --$index) {
  302.                 if (!$arguments[$index] instanceof $this->defaultArgument) {
  303.                     break;
  304.                 }
  305.                 unset($arguments[$index]);
  306.             }
  307.         }
  308.         // it's possible index 1 was set, then index 0, then 2, etc
  309.         // make sure that we re-order so they're injected as expected
  310.         ksort($arguments\SORT_NATURAL);
  311.         return $arguments;
  312.     }
  313.     /**
  314.      * Returns a reference to the service matching the given type, if any.
  315.      */
  316.     private function getAutowiredReference(TypedReference $referencebool $filterType): ?TypedReference
  317.     {
  318.         $this->lastFailure null;
  319.         $type $reference->getType();
  320.         if ($type !== (string) $reference) {
  321.             return $reference;
  322.         }
  323.         if ($filterType && false !== $m strpbrk($type'&|')) {
  324.             $types array_diff(explode($m[0], $type), ['int''string''array''bool''float''iterable''object''callable''null']);
  325.             sort($types);
  326.             $type implode($m[0], $types);
  327.         }
  328.         if (null !== $name $reference->getName()) {
  329.             if ($this->container->has($alias $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) {
  330.                 return new TypedReference($alias$type$reference->getInvalidBehavior());
  331.             }
  332.             if (null !== ($alias $this->getCombinedAlias($type$name) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
  333.                 return new TypedReference($alias$type$reference->getInvalidBehavior());
  334.             }
  335.             if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) {
  336.                 foreach ($this->container->getAliases() as $id => $alias) {
  337.                     if ($name === (string) $alias && str_starts_with($id$type.' $')) {
  338.                         return new TypedReference($name$type$reference->getInvalidBehavior());
  339.                     }
  340.                 }
  341.             }
  342.         }
  343.         if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) {
  344.             return new TypedReference($type$type$reference->getInvalidBehavior());
  345.         }
  346.         if (null !== ($alias $this->getCombinedAlias($type) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
  347.             return new TypedReference($alias$type$reference->getInvalidBehavior());
  348.         }
  349.         return null;
  350.     }
  351.     /**
  352.      * Populates the list of available types.
  353.      */
  354.     private function populateAvailableTypes(ContainerBuilder $container)
  355.     {
  356.         $this->types = [];
  357.         $this->ambiguousServiceTypes = [];
  358.         $this->autowiringAliases = [];
  359.         foreach ($container->getDefinitions() as $id => $definition) {
  360.             $this->populateAvailableType($container$id$definition);
  361.         }
  362.         foreach ($container->getAliases() as $id => $alias) {
  363.             $this->populateAutowiringAlias($id);
  364.         }
  365.     }
  366.     /**
  367.      * Populates the list of available types for a given definition.
  368.      */
  369.     private function populateAvailableType(ContainerBuilder $containerstring $idDefinition $definition)
  370.     {
  371.         // Never use abstract services
  372.         if ($definition->isAbstract()) {
  373.             return;
  374.         }
  375.         if ('' === $id || '.' === $id[0] || $definition->isDeprecated() || !$reflectionClass $container->getReflectionClass($definition->getClass(), false)) {
  376.             return;
  377.         }
  378.         foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
  379.             $this->set($reflectionInterface->name$id);
  380.         }
  381.         do {
  382.             $this->set($reflectionClass->name$id);
  383.         } while ($reflectionClass $reflectionClass->getParentClass());
  384.         $this->populateAutowiringAlias($id);
  385.     }
  386.     /**
  387.      * Associates a type and a service id if applicable.
  388.      */
  389.     private function set(string $typestring $id)
  390.     {
  391.         // is this already a type/class that is known to match multiple services?
  392.         if (isset($this->ambiguousServiceTypes[$type])) {
  393.             $this->ambiguousServiceTypes[$type][] = $id;
  394.             return;
  395.         }
  396.         // check to make sure the type doesn't match multiple services
  397.         if (!isset($this->types[$type]) || $this->types[$type] === $id) {
  398.             $this->types[$type] = $id;
  399.             return;
  400.         }
  401.         // keep an array of all services matching this type
  402.         if (!isset($this->ambiguousServiceTypes[$type])) {
  403.             $this->ambiguousServiceTypes[$type] = [$this->types[$type]];
  404.             unset($this->types[$type]);
  405.         }
  406.         $this->ambiguousServiceTypes[$type][] = $id;
  407.     }
  408.     private function createTypeNotFoundMessageCallback(TypedReference $referencestring $label): \Closure
  409.     {
  410.         if (null === $this->typesClone->container) {
  411.             $this->typesClone->container = new ContainerBuilder($this->container->getParameterBag());
  412.             $this->typesClone->container->setAliases($this->container->getAliases());
  413.             $this->typesClone->container->setDefinitions($this->container->getDefinitions());
  414.             $this->typesClone->container->setResourceTracking(false);
  415.         }
  416.         $currentId $this->currentId;
  417.         return (function () use ($reference$label$currentId) {
  418.             return $this->createTypeNotFoundMessage($reference$label$currentId);
  419.         })->bindTo($this->typesClone);
  420.     }
  421.     private function createTypeNotFoundMessage(TypedReference $referencestring $labelstring $currentId): string
  422.     {
  423.         $type $reference->getType();
  424.         $i null;
  425.         $namespace $type;
  426.         do {
  427.             $namespace substr($namespace0$i);
  428.             if ($this->container->hasDefinition($namespace) && $tag $this->container->getDefinition($namespace)->getTag('container.excluded')) {
  429.                 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');
  430.             }
  431.         } while (false !== $i strrpos($namespace'\\'));
  432.         if (!$r $this->container->getReflectionClass($typefalse)) {
  433.             // either $type does not exist or a parent class does not exist
  434.             try {
  435.                 $resource = new ClassExistenceResource($typefalse);
  436.                 // isFresh() will explode ONLY if a parent class/trait does not exist
  437.                 $resource->isFresh(0);
  438.                 $parentMsg false;
  439.             } catch (\ReflectionException $e) {
  440.                 $parentMsg $e->getMessage();
  441.             }
  442.             $message sprintf('has type "%s" but this class %s.'$type$parentMsg sprintf('is missing a parent class (%s)'$parentMsg) : 'was not found');
  443.         } else {
  444.             $alternatives $this->createTypeAlternatives($this->container$reference);
  445.             $message $this->container->has($type) ? 'this service is abstract' 'no such service exists';
  446.             $message sprintf('references %s "%s" but %s.%s'$r->isInterface() ? 'interface' 'class'$type$message$alternatives);
  447.             if ($r->isInterface() && !$alternatives) {
  448.                 $message .= ' Did you create a class that implements this interface?';
  449.             }
  450.         }
  451.         $message sprintf('Cannot autowire service "%s": %s %s'$currentId$label$message);
  452.         if (null !== $this->lastFailure) {
  453.             $message $this->lastFailure."\n".$message;
  454.             $this->lastFailure null;
  455.         }
  456.         return $message;
  457.     }
  458.     private function createTypeAlternatives(ContainerBuilder $containerTypedReference $reference): string
  459.     {
  460.         // try suggesting available aliases first
  461.         if ($message $this->getAliasesSuggestionForType($container$type $reference->getType())) {
  462.             return ' '.$message;
  463.         }
  464.         if (!isset($this->ambiguousServiceTypes)) {
  465.             $this->populateAvailableTypes($container);
  466.         }
  467.         $servicesAndAliases $container->getServiceIds();
  468.         if (null !== ($autowiringAliases $this->autowiringAliases[$type] ?? null) && !isset($autowiringAliases[''])) {
  469.             return sprintf(' Available autowiring aliases for this %s are: "$%s".'class_exists($typefalse) ? 'class' 'interface'implode('", "$'$autowiringAliases));
  470.         }
  471.         if (!$container->has($type) && false !== $key array_search(strtolower($type), array_map('strtolower'$servicesAndAliases))) {
  472.             return sprintf(' Did you mean "%s"?'$servicesAndAliases[$key]);
  473.         } elseif (isset($this->ambiguousServiceTypes[$type])) {
  474.             $message sprintf('one of these existing services: "%s"'implode('", "'$this->ambiguousServiceTypes[$type]));
  475.         } elseif (isset($this->types[$type])) {
  476.             $message sprintf('the existing "%s" service'$this->types[$type]);
  477.         } else {
  478.             return '';
  479.         }
  480.         return sprintf(' You should maybe alias this %s to %s.'class_exists($typefalse) ? 'class' 'interface'$message);
  481.     }
  482.     private function getAliasesSuggestionForType(ContainerBuilder $containerstring $type): ?string
  483.     {
  484.         $aliases = [];
  485.         foreach (class_parents($type) + class_implements($type) as $parent) {
  486.             if ($container->has($parent) && !$container->findDefinition($parent)->isAbstract()) {
  487.                 $aliases[] = $parent;
  488.             }
  489.         }
  490.         if ($len \count($aliases)) {
  491.             $message 'Try changing the type-hint to one of its parents: ';
  492.             for ($i 0, --$len$i $len; ++$i) {
  493.                 $message .= sprintf('%s "%s", 'class_exists($aliases[$i], false) ? 'class' 'interface'$aliases[$i]);
  494.             }
  495.             $message .= sprintf('or %s "%s".'class_exists($aliases[$i], false) ? 'class' 'interface'$aliases[$i]);
  496.             return $message;
  497.         }
  498.         if ($aliases) {
  499.             return sprintf('Try changing the type-hint to "%s" instead.'$aliases[0]);
  500.         }
  501.         return null;
  502.     }
  503.     private function populateAutowiringAlias(string $id): void
  504.     {
  505.         if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/'$id$m)) {
  506.             return;
  507.         }
  508.         $type $m[2];
  509.         $name $m[3] ?? '';
  510.         if (class_exists($typefalse) || interface_exists($typefalse)) {
  511.             $this->autowiringAliases[$type][$name] = $name;
  512.         }
  513.     }
  514.     private function getCombinedAlias(string $typestring $name null): ?string
  515.     {
  516.         if (str_contains($type'&')) {
  517.             $types explode('&'$type);
  518.         } elseif (str_contains($type'|')) {
  519.             $types explode('|'$type);
  520.         } else {
  521.             return null;
  522.         }
  523.         $alias null;
  524.         $suffix $name ' $'.$name '';
  525.         foreach ($types as $type) {
  526.             if (!$this->container->hasAlias($type.$suffix)) {
  527.                 return null;
  528.             }
  529.             if (null === $alias) {
  530.                 $alias = (string) $this->container->getAlias($type.$suffix);
  531.             } elseif ((string) $this->container->getAlias($type.$suffix) !== $alias) {
  532.                 return null;
  533.             }
  534.         }
  535.         return $alias;
  536.     }
  537. }