Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. AutowirePass.php

function AutowirePass::autowireMethod

Autowires the constructor or a method.

Throws

AutowiringFailedException

1 call to AutowirePass::autowireMethod()
AutowirePass::autowireCalls in vendor/symfony/dependency-injection/Compiler/AutowirePass.php

File

vendor/symfony/dependency-injection/Compiler/AutowirePass.php, line 257

Class

AutowirePass
Inspects existing service definitions and wires the autowired ones using the type hints of their classes.

Namespace

Symfony\Component\DependencyInjection\Compiler

Code

private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes) : array {
    $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
    $method = $reflectionMethod->name;
    $parameters = $reflectionMethod->getParameters();
    if ($reflectionMethod->isVariadic()) {
        array_pop($parameters);
    }
    $defaultArgument = clone $this->defaultArgument;
    $defaultArgument->names = new \ArrayObject();
    foreach ($parameters as $index => $parameter) {
        $defaultArgument->names[$index] = $parameter->name;
        if (\array_key_exists($parameter->name, $arguments)) {
            $arguments[$index] = $arguments[$parameter->name];
            unset($arguments[$parameter->name]);
        }
        if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
            continue;
        }
        $type = ProxyHelper::exportType($parameter, true);
        $target = null;
        $name = Target::parseName($parameter, $target);
        $target = $target ? [
            $target,
        ] : [];
        $currentId = $this->currentId;
        $getValue = function () use ($type, $parameter, $class, $method, $name, $target, $defaultArgument, $currentId) {
            if (!($value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target), false))) {
                $failureMessage = $this->createTypeNotFoundMessageCallback($ref, \sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $currentId ? $class . '::' . $method : $method));
                if ($parameter->isDefaultValueAvailable()) {
                    $value = $defaultArgument->withValue($parameter);
                }
                elseif (!$parameter->allowsNull()) {
                    throw new AutowiringFailedException($currentId, $failureMessage);
                }
            }
            return $value;
        };
        if ($checkAttributes) {
            $attributes = array_merge($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF), $parameter->getAttributes(Lazy::class, \ReflectionAttribute::IS_INSTANCEOF));
            if (1 < \count($attributes)) {
                throw new AutowiringFailedException($this->currentId, 'Using both attributes #[Lazy] and #[Autowire] on an argument is not allowed; use the "lazy" parameter of #[Autowire] instead.');
            }
            foreach ($attributes as $attribute) {
                $attribute = $attribute->newInstance();
                $value = $attribute instanceof Autowire ? $attribute->value : null;
                if (\is_string($value) && str_starts_with($value, '%env(') && str_ends_with($value, ')%')) {
                    if ($parameter->getType() instanceof \ReflectionNamedType && 'bool' === $parameter->getType()
                        ->getName() && !str_starts_with($value, '%env(bool:')) {
                        $attribute = new Autowire(substr_replace($value, 'bool:', 5, 0));
                    }
                    if ($parameter->isDefaultValueAvailable() && $parameter->allowsNull() && null === $parameter->getDefaultValue() && !preg_match('/(^|:)default:/', $value)) {
                        $attribute = new Autowire(substr_replace($value, 'default::', 5, 0));
                    }
                }
                $invalidBehavior = $parameter->allowsNull() ? ContainerInterface::NULL_ON_INVALID_REFERENCE : ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE;
                try {
                    $value = $this->processValue(new TypedReference($type ?: '?', $type ?: 'mixed', $invalidBehavior, $name, [
                        $attribute,
                        $target,
                    ]));
                } catch (ParameterNotFoundException $e) {
                    if (!$parameter->isDefaultValueAvailable()) {
                        throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
                    }
                    $arguments[$index] = clone $defaultArgument;
                    $arguments[$index]->value = $parameter->getDefaultValue();
                    continue 2;
                }
                if ($attribute instanceof AutowireInline) {
                    $value = $attribute->buildDefinition($value, $type, $parameter);
                    $value = $this->doProcessValue($value);
                }
                elseif ($lazy = $attribute->lazy) {
                    $definition = (new Definition($type))->setFactory('current')
                        ->setArguments([
                        [
                            $value ??= $getValue(),
                        ],
                    ])
                        ->setLazy(true);
                    if (!\is_array($lazy)) {
                        if (str_contains($type, '|')) {
                            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));
                        }
                        $lazy = str_contains($type, '&') ? explode('&', $type) : [];
                    }
                    if ($lazy) {
                        if (!class_exists($type) && !interface_exists($type, false)) {
                            $definition->setClass('object');
                        }
                        foreach ($lazy as $v) {
                            $definition->addTag('proxy', [
                                'interface' => $v,
                            ]);
                        }
                    }
                    if ($definition->getClass() !== (string) $value || $definition->getTag('proxy')) {
                        $value .= '.' . $this->container
                            ->hash([
                            $definition->getClass(),
                            $definition->getTag('proxy'),
                        ]);
                    }
                    $this->container
                        ->setDefinition($value = '.lazy.' . $value, $definition);
                    $value = new Reference($value);
                }
                $arguments[$index] = $value;
                continue 2;
            }
            foreach ($parameter->getAttributes(AutowireDecorated::class) as $attribute) {
                $arguments[$index] = $this->processValue($attribute->newInstance());
                continue 2;
            }
        }
        if (!$type) {
            if (isset($arguments[$index])) {
                continue;
            }
            // no default value? Then fail
            if (!$parameter->isDefaultValueAvailable()) {
                // For core classes, isDefaultValueAvailable() can
                // be false when isOptional() returns true. If the
                // argument *is* optional, allow it to be missing
                if ($parameter->isOptional()) {
                    --$index;
                    break;
                }
                $type = ProxyHelper::exportType($parameter);
                $type = $type ? \sprintf('is type-hinted "%s"', preg_replace('/(^|[(|&])\\\\|^\\?\\\\?/', '\\1', $type)) : 'has no type-hint';
                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));
            }
            // specifically pass the default value
            $arguments[$index] = $defaultArgument->withValue($parameter);
            continue;
        }
        if ($this->decoratedClass && is_a($this->decoratedClass, $type, true)) {
            if ($this->restorePreviousValue) {
                // The inner service is injected only if there is only 1 argument matching the type of the decorated class
                // across all arguments of all autowired methods.
                // If a second matching argument is found, the default behavior is restored.
                ($this->restorePreviousValue)();
                $this->decoratedClass = $this->restorePreviousValue = null;
                // Prevent further checks
            }
            else {
                $arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass);
                $argumentAtIndex =& $arguments[$index];
                $this->restorePreviousValue = static function () use (&$argumentAtIndex, $getValue) {
                    $argumentAtIndex = $getValue();
                };
                continue;
            }
        }
        $arguments[$index] = $getValue();
    }
    if ($parameters && !isset($arguments[++$index])) {
        while (0 <= --$index) {
            if (!$arguments[$index] instanceof $defaultArgument) {
                break;
            }
            unset($arguments[$index]);
        }
    }
    // it's possible index 1 was set, then index 0, then 2, etc
    // make sure that we re-order so they're injected as expected
    ksort($arguments, \SORT_NATURAL);
    return $arguments;
}
RSS feed
Powered by Drupal