From 69dc71be6d7716bc773e05d28784d45769944a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 21 Mar 2024 11:47:10 +0100 Subject: [PATCH] [DependencyInjection] Apply attribute configurator to child classes --- .../FrameworkExtension.php | 6 ++-- .../DependencyInjection/CHANGELOG.md | 1 + .../AttributeAutoconfigurationPass.php | 26 ++++++++++++--- .../Tests/Compiler/IntegrationTest.php | 11 +++++++ .../Fixtures/Attribute/CustomAnyAttribute.php | 2 +- .../Attribute/CustomChildAttribute.php | 17 ++++++++++ .../Tests/Fixtures/TaggedService5.php | 32 +++++++++++++++++++ 7 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomChildAttribute.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService5.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 911242bc2830..7d584af8b3ab 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -699,9 +699,9 @@ public function load(array $configs, ContainerBuilder $container): void $taskAttributeClass, static function (ChildDefinition $definition, AsPeriodicTask|AsCronTask $attribute, \ReflectionClass|\ReflectionMethod $reflector): void { $tagAttributes = get_object_vars($attribute) + [ - 'trigger' => match ($attribute::class) { - AsPeriodicTask::class => 'every', - AsCronTask::class => 'cron', + 'trigger' => match (true) { + $attribute instanceof AsPeriodicTask => 'every', + $attribute instanceof AsCronTask => 'cron', }, ]; if ($reflector instanceof \ReflectionMethod) { diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index a3f055088ec4..9413290cf615 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Have `ServiceLocator` implement `ServiceCollectionInterface` * Add `#[Lazy]` attribute as shortcut for `#[Autowire(lazy: [bool|string])]` and `#[Autoconfigure(lazy: [bool|string])]` * Add `#[AutowireMethodOf]` attribute to autowire a method of a service as a callable + * Make `ContainerBuilder::registerAttributeForAutoconfiguration()` propagate to attribute classes that extend the registered class 7.0 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php index cb428565e37b..22aaeddfcc51 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php @@ -96,7 +96,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($this->classAttributeConfigurators) { foreach ($classReflector->getAttributes() as $attribute) { - if ($configurator = $this->classAttributeConfigurators[$attribute->getName()] ?? null) { + if ($configurator = $this->findConfigurator($this->classAttributeConfigurators, $attribute->getName())) { $configurator($conditionals, $attribute->newInstance(), $classReflector); } } @@ -112,7 +112,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($constructorReflector) { foreach ($constructorReflector->getParameters() as $parameterReflector) { foreach ($parameterReflector->getAttributes() as $attribute) { - if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) { + if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) { $configurator($conditionals, $attribute->newInstance(), $parameterReflector); } } @@ -128,7 +128,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($this->methodAttributeConfigurators) { foreach ($methodReflector->getAttributes() as $attribute) { - if ($configurator = $this->methodAttributeConfigurators[$attribute->getName()] ?? null) { + if ($configurator = $this->findConfigurator($this->methodAttributeConfigurators, $attribute->getName())) { $configurator($conditionals, $attribute->newInstance(), $methodReflector); } } @@ -137,7 +137,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($this->parameterAttributeConfigurators) { foreach ($methodReflector->getParameters() as $parameterReflector) { foreach ($parameterReflector->getAttributes() as $attribute) { - if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) { + if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) { $configurator($conditionals, $attribute->newInstance(), $parameterReflector); } } @@ -153,7 +153,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed } foreach ($propertyReflector->getAttributes() as $attribute) { - if ($configurator = $this->propertyAttributeConfigurators[$attribute->getName()] ?? null) { + if ($configurator = $this->findConfigurator($this->propertyAttributeConfigurators, $attribute->getName())) { $configurator($conditionals, $attribute->newInstance(), $propertyReflector); } } @@ -167,4 +167,20 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed return parent::processValue($value, $isRoot); } + + /** + * Find the first configurator for the given attribute name, looking up the class hierarchy. + */ + private function findConfigurator(array &$configurators, string $attributeName): ?callable + { + if (\array_key_exists($attributeName, $configurators)) { + return $configurators[$attributeName]; + } + + if (class_exists($attributeName) && $parent = get_parent_class($attributeName)) { + return $configurators[$attributeName] = self::findConfigurator($configurators, $parent); + } + + return $configurators[$attributeName] = null; + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index cd0ac6973867..f68b546ab75c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -57,6 +57,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3Configurator; use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService4; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService5; use Symfony\Contracts\Service\Attribute\SubscribedService; use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -993,6 +994,10 @@ static function (ChildDefinition $definition, CustomAnyAttribute $attribute, \Re ->setPublic(true) ->setAutoconfigured(true); + $container->register(TaggedService5::class) + ->setPublic(true) + ->setAutoconfigured(true); + $container->register('failing_factory', \stdClass::class); $container->register('ccc', TaggedService4::class) ->setFactory([new Reference('failing_factory'), 'create']) @@ -1018,6 +1023,12 @@ static function (ChildDefinition $definition, CustomAnyAttribute $attribute, \Re ['property' => 'name'], ['someAttribute' => 'on name', 'priority' => 0, 'property' => 'name'], ], + TaggedService5::class => [ + ['class' => TaggedService5::class], + ['parameter' => 'param1'], + ['method' => 'fooAction'], + ['property' => 'name'], + ], 'ccc' => [ ['class' => TaggedService4::class], ['method' => 'fooAction'], diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAnyAttribute.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAnyAttribute.php index c9c59cb519b1..982adee62e8e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAnyAttribute.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomAnyAttribute.php @@ -12,6 +12,6 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)] -final class CustomAnyAttribute +class CustomAnyAttribute { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomChildAttribute.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomChildAttribute.php new file mode 100644 index 000000000000..f57bff8e3d8a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Attribute/CustomChildAttribute.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)] +class CustomChildAttribute extends CustomAnyAttribute +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService5.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService5.php new file mode 100644 index 000000000000..08258c5d38ec --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService5.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomChildAttribute; + +#[CustomChildAttribute] +final class TaggedService5 +{ + #[CustomChildAttribute] + public string $name; + + public function __construct( + #[CustomChildAttribute] + private string $param1, + ) {} + + #[CustomChildAttribute] + public function fooAction( + #[CustomChildAttribute] + string $param1 + ) {} +}