Skip to content

Commit

Permalink
minor #54047 [DependencyInjection] Make AutowirePass reentrant (nicol…
Browse files Browse the repository at this point in the history
…as-grekas)

This PR was merged into the 7.1 branch.

Discussion
----------

[DependencyInjection] Make AutowirePass reentrant

| Q             | A
| ------------- | ---
| Branch?       | 7.1
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

This PR is making some ground work to unlock #52820.

It makes AutowirePass not rely on properties for passing state between its methods, thus making the pass reentrant (aka able to analyze nested Definition objects).

Commits
-------

02bf53b [DependencyInjection] Make AutowirePass reentrant
  • Loading branch information
nicolas-grekas committed Feb 24, 2024
2 parents f78f932 + 02bf53b commit 82acd7a
Showing 1 changed file with 42 additions and 44 deletions.
86 changes: 42 additions & 44 deletions src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
Expand Up @@ -44,11 +44,8 @@ class AutowirePass extends AbstractRecursivePass
private bool $throwOnAutowiringException;
private ?string $decoratedClass = null;
private ?string $decoratedId = null;
private ?array $methodCalls = null;
private object $defaultArgument;
private ?\Closure $getPreviousValue = null;
private ?int $decoratedMethodIndex = null;
private ?int $decoratedMethodArgumentIndex = null;
private ?\Closure $restorePreviousValue = null;
private ?self $typesClone = null;

public function __construct(bool $throwOnAutowireException = true)
Expand Down Expand Up @@ -79,12 +76,9 @@ public function process(ContainerBuilder $container): void
} finally {
$this->decoratedClass = null;
$this->decoratedId = null;
$this->methodCalls = null;
$this->defaultArgument->bag = null;
$this->defaultArgument->names = null;
$this->getPreviousValue = null;
$this->decoratedMethodIndex = null;
$this->decoratedMethodArgumentIndex = null;
$this->restorePreviousValue = null;
$this->typesClone = null;
}
}
Expand Down Expand Up @@ -155,7 +149,7 @@ private function doProcessValue(mixed $value, bool $isRoot = false): mixed
return $value;
}

$this->methodCalls = $value->getMethodCalls();
$methodCalls = $value->getMethodCalls();

try {
$constructor = $this->getConstructor($value, false);
Expand All @@ -164,40 +158,42 @@ private function doProcessValue(mixed $value, bool $isRoot = false): mixed
}

if ($constructor) {
array_unshift($this->methodCalls, [$constructor, $value->getArguments()]);
array_unshift($methodCalls, [$constructor, $value->getArguments()]);
}

$checkAttributes = !$value->hasTag('container.ignore_attributes');
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes);
$methodCalls = $this->autowireCalls($methodCalls, $reflectionClass, $isRoot, $checkAttributes);

if ($constructor) {
[, $arguments] = array_shift($this->methodCalls);
[, $arguments] = array_shift($methodCalls);

if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
}

if ($this->methodCalls !== $value->getMethodCalls()) {
$value->setMethodCalls($this->methodCalls);
if ($methodCalls !== $value->getMethodCalls()) {
$value->setMethodCalls($methodCalls);
}

return $value;
}

private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array
private function autowireCalls(array $methodCalls, \ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array
{
$this->decoratedId = null;
$this->decoratedClass = null;
$this->getPreviousValue = null;
if ($isRoot) {
$this->decoratedId = null;
$this->decoratedClass = null;
$this->restorePreviousValue = null;

if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId = $definition->innerServiceId) && $this->container->has($this->decoratedId)) {
$this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass();
if (($definition = $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId = $definition->innerServiceId) && $this->container->has($this->decoratedId)) {
$this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass();
}
}

$patchedIndexes = [];

foreach ($this->methodCalls as $i => $call) {
foreach ($methodCalls as $i => $call) {
[$method, $arguments] = $call;

if ($method instanceof \ReflectionFunctionAbstract) {
Expand All @@ -214,18 +210,18 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot,
}
}

$arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes, $i);
$arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes);

if ($arguments !== $call[1]) {
$this->methodCalls[$i][1] = $arguments;
$methodCalls[$i][1] = $arguments;
$patchedIndexes[] = $i;
}
}

// use named arguments to skip complex default values
foreach ($patchedIndexes as $i) {
$namedArguments = null;
$arguments = $this->methodCalls[$i][1];
$arguments = $methodCalls[$i][1];

foreach ($arguments as $j => $value) {
if ($namedArguments && !$value instanceof $this->defaultArgument) {
Expand All @@ -248,29 +244,30 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot,
}
}

$this->methodCalls[$i][1] = $arguments;
$methodCalls[$i][1] = $arguments;
}

return $this->methodCalls;
return $methodCalls;
}

/**
* Autowires the constructor or a method.
*
* @throws AutowiringFailedException
*/
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes, int $methodIndex): array
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);
}
$this->defaultArgument->names = new \ArrayObject();
$defaultArgument = clone $this->defaultArgument;
$defaultArgument->names = new \ArrayObject();

foreach ($parameters as $index => $parameter) {
$this->defaultArgument->names[$index] = $parameter->name;
$defaultArgument->names[$index] = $parameter->name;

if (\array_key_exists($parameter->name, $arguments)) {
$arguments[$index] = $arguments[$parameter->name];
Expand All @@ -284,15 +281,16 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
$target = null;
$name = Target::parseName($parameter, $target);
$target = $target ? [$target] : [];
$currentId = $this->currentId;

$getValue = function () use ($type, $parameter, $class, $method, $name, $target) {
$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 !== $this->currentId ? $class.'::'.$method : $method));
$failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $currentId ? $class.'::'.$method : $method));

if ($parameter->isDefaultValueAvailable()) {
$value = $this->defaultArgument->withValue($parameter);
$value = $defaultArgument->withValue($parameter);
} elseif (!$parameter->allowsNull()) {
throw new AutowiringFailedException($this->currentId, $failureMessage);
throw new AutowiringFailedException($currentId, $failureMessage);
}
}

Expand All @@ -316,14 +314,15 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
if (!$parameter->isDefaultValueAvailable()) {
throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
}
$arguments[$index] = clone $this->defaultArgument;
$arguments[$index] = clone $defaultArgument;
$arguments[$index]->value = $parameter->getDefaultValue();

continue 2;
}

if ($attribute instanceof AutowireCallable) {
$value = $attribute->buildDefinition($value, $type, $parameter);
$value = $this->doProcessValue($value);
} elseif ($lazy = $attribute->lazy) {
$definition = (new Definition($type))
->setFactory('current')
Expand Down Expand Up @@ -385,25 +384,24 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
}

// specifically pass the default value
$arguments[$index] = $this->defaultArgument->withValue($parameter);
$arguments[$index] = $defaultArgument->withValue($parameter);

continue;
}

if ($this->decoratedClass && is_a($this->decoratedClass, $type, true)) {
if ($this->getPreviousValue) {
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.

$getPreviousValue = $this->getPreviousValue;
$this->methodCalls[$this->decoratedMethodIndex][1][$this->decoratedMethodArgumentIndex] = $getPreviousValue();
$this->decoratedClass = null; // Prevent further checks
($this->restorePreviousValue)();
$this->decoratedClass = $this->restorePreviousValue = null; // Prevent further checks
} else {
$arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass);
$this->getPreviousValue = $getValue;
$this->decoratedMethodIndex = $methodIndex;
$this->decoratedMethodArgumentIndex = $index;
$argumentAtIndex = &$arguments[$index];
$this->restorePreviousValue = static function () use (&$argumentAtIndex, $getValue) {
$argumentAtIndex = $getValue();
};

continue;
}
Expand All @@ -414,7 +412,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a

if ($parameters && !isset($arguments[++$index])) {
while (0 <= --$index) {
if (!$arguments[$index] instanceof $this->defaultArgument) {
if (!$arguments[$index] instanceof $defaultArgument) {
break;
}
unset($arguments[$index]);
Expand Down

0 comments on commit 82acd7a

Please sign in to comment.