diff --git a/src/Symfony/Component/DependencyInjection/Attribute/Target.php b/src/Symfony/Component/DependencyInjection/Attribute/Target.php index 7751b3813bada..b935500e9737d 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/Target.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/Target.php @@ -28,13 +28,15 @@ public function __construct(string $name) $this->name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name)))); } - public static function parseName(\ReflectionParameter $parameter): string + public static function parseName(\ReflectionParameter $parameter, self &$attribute = null): string { + $attribute = null; if (!$target = $parameter->getAttributes(self::class)[0] ?? null) { return $parameter->name; } - $name = $target->newInstance()->name; + $attribute = $target->newInstance(); + $name = $attribute->name; if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) { if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index e1d0dda20d6ff..81dec56e681f3 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add `RemoveBuildParametersPass`, which removes parameters starting with a dot during compilation * Add support for nesting autowiring-related attributes into `#[Autowire(...)]` * Deprecate undefined and numeric keys with `service_locator` config + * Fail if Target attribute does not exist during compilation 6.2 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 09b0490adf4c8..66a175d76c267 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -128,7 +128,7 @@ private function doProcessValue(mixed $value, bool $isRoot = false): mixed return $this->processAttribute($attribute, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior()); } - $value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name); + $value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name, [$attribute]); } if ($ref = $this->getAutowiredReference($value, true)) { return $ref; @@ -332,7 +332,8 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a } $getValue = function () use ($type, $parameter, $class, $method) { - if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), false)) { + $name = Target::parseName($parameter, $target); + if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target ? [$target] : []), false)) { $failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); if ($parameter->isDefaultValueAvailable()) { @@ -420,6 +421,10 @@ private function getAutowiredReference(TypedReference $reference, bool $filterTy } } } + + if ($reference->getAttributes()) { + return null; + } } if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) { @@ -544,6 +549,9 @@ private function createTypeNotFoundMessage(TypedReference $reference, string $la } $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); + } elseif ($reference->getAttributes()) { + $message = $label; + $label = sprintf('"#[Target(\'%s\')" on', $reference->getName()); } else { $alternatives = $this->createTypeAlternatives($this->container, $reference); $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index c766bed6aefa4..8224150357dda 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -1142,6 +1142,21 @@ public function testArgumentWithTarget() $this->assertSame(BarInterface::class.' $imageStorage', (string) $container->getDefinition('with_target')->getArgument(0)); } + public function testArgumentWithTypoTarget() + { + $container = new ContainerBuilder(); + + $container->register(BarInterface::class, BarInterface::class); + $container->register(BarInterface::class.' $iamgeStorage', BarInterface::class); + $container->register('with_target', WithTarget::class) + ->setAutowired(true); + + $this->expectException(AutowiringFailedException::class); + $this->expectExceptionMessage('Cannot autowire service "with_target": "#[Target(\'imageStorage\')" on argument "$bar" of method "Symfony\Component\DependencyInjection\Tests\Fixtures\WithTarget::__construct()"'); + + (new AutowirePass())->process($container); + } + public function testDecorationWithServiceAndAliasedInterface() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php index 0f8fff4eed26f..ba979be80bc04 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php @@ -463,7 +463,7 @@ public static function getSubscribedServices(): array 'autowired.nullable' => new ServiceClosureArgument(new Reference('service.id', ContainerInterface::NULL_ON_INVALID_REFERENCE)), 'autowired.parameter' => new ServiceClosureArgument('foobar'), 'map.decorated' => new ServiceClosureArgument(new Reference('.service_locator.oZHAdom.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)), - 'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'someTarget')), + 'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'someTarget', [new Target('someTarget')])), ]; $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); }