function AutowirePass::autowireMethod
Autowires the constructor or a method.
Throws
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\CompilerCode
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;
}