Skip to content

Commit 459f055

Browse files
committed
feature #54365 [DependencyInjection] Apply attribute configurator to child classes (GromNaN)
This PR was squashed before being merged into the 7.1 branch. Discussion ---------- [DependencyInjection] Apply attribute configurator to child classes | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix #52898 | License | MIT This allows extending attribute classes registered for autoconfiguration. Use-case: Share configuration between several classes with pre-configured attribute classes. As described in #52898 (bus name for `AsMessageHandler`, schedule name for `AsCronTask` and `AsPeriodicTask`) The child-class attribute is handled by the same function as it's parent class that is registered for autoconfiguration. This means any additional property will be ignored (unless supported, which could be new feature for the `AsTaggedItem` attribute configurator). If there is a configurator for the child class, the configurator for the parent class is not called. Commits ------- 69dc71be6d [DependencyInjection] Apply attribute configurator to child classes
2 parents 08f82c7 + 0ce8d13 commit 459f055

File tree

6 files changed

+83
-6
lines changed

6 files changed

+83
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Have `ServiceLocator` implement `ServiceCollectionInterface`
1010
* Add `#[Lazy]` attribute as shortcut for `#[Autowire(lazy: [bool|string])]` and `#[Autoconfigure(lazy: [bool|string])]`
1111
* Add `#[AutowireMethodOf]` attribute to autowire a method of a service as a callable
12+
* Make `ContainerBuilder::registerAttributeForAutoconfiguration()` propagate to attribute classes that extend the registered class
1213

1314
7.0
1415
---

Compiler/AttributeAutoconfigurationPass.php

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
9696

9797
if ($this->classAttributeConfigurators) {
9898
foreach ($classReflector->getAttributes() as $attribute) {
99-
if ($configurator = $this->classAttributeConfigurators[$attribute->getName()] ?? null) {
99+
if ($configurator = $this->findConfigurator($this->classAttributeConfigurators, $attribute->getName())) {
100100
$configurator($conditionals, $attribute->newInstance(), $classReflector);
101101
}
102102
}
@@ -112,7 +112,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
112112
if ($constructorReflector) {
113113
foreach ($constructorReflector->getParameters() as $parameterReflector) {
114114
foreach ($parameterReflector->getAttributes() as $attribute) {
115-
if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) {
115+
if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) {
116116
$configurator($conditionals, $attribute->newInstance(), $parameterReflector);
117117
}
118118
}
@@ -128,7 +128,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
128128

129129
if ($this->methodAttributeConfigurators) {
130130
foreach ($methodReflector->getAttributes() as $attribute) {
131-
if ($configurator = $this->methodAttributeConfigurators[$attribute->getName()] ?? null) {
131+
if ($configurator = $this->findConfigurator($this->methodAttributeConfigurators, $attribute->getName())) {
132132
$configurator($conditionals, $attribute->newInstance(), $methodReflector);
133133
}
134134
}
@@ -137,7 +137,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
137137
if ($this->parameterAttributeConfigurators) {
138138
foreach ($methodReflector->getParameters() as $parameterReflector) {
139139
foreach ($parameterReflector->getAttributes() as $attribute) {
140-
if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) {
140+
if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) {
141141
$configurator($conditionals, $attribute->newInstance(), $parameterReflector);
142142
}
143143
}
@@ -153,7 +153,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
153153
}
154154

155155
foreach ($propertyReflector->getAttributes() as $attribute) {
156-
if ($configurator = $this->propertyAttributeConfigurators[$attribute->getName()] ?? null) {
156+
if ($configurator = $this->findConfigurator($this->propertyAttributeConfigurators, $attribute->getName())) {
157157
$configurator($conditionals, $attribute->newInstance(), $propertyReflector);
158158
}
159159
}
@@ -167,4 +167,20 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
167167

168168
return parent::processValue($value, $isRoot);
169169
}
170+
171+
/**
172+
* Find the first configurator for the given attribute name, looking up the class hierarchy.
173+
*/
174+
private function findConfigurator(array &$configurators, string $attributeName): ?callable
175+
{
176+
if (\array_key_exists($attributeName, $configurators)) {
177+
return $configurators[$attributeName];
178+
}
179+
180+
if (class_exists($attributeName) && $parent = get_parent_class($attributeName)) {
181+
return $configurators[$attributeName] = self::findConfigurator($configurators, $parent);
182+
}
183+
184+
return $configurators[$attributeName] = null;
185+
}
170186
}

Tests/Compiler/IntegrationTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
5858
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3Configurator;
5959
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService4;
60+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService5;
6061
use Symfony\Contracts\Service\Attribute\SubscribedService;
6162
use Symfony\Contracts\Service\ServiceProviderInterface;
6263
use Symfony\Contracts\Service\ServiceSubscriberInterface;
@@ -993,6 +994,10 @@ static function (ChildDefinition $definition, CustomAnyAttribute $attribute, \Re
993994
->setPublic(true)
994995
->setAutoconfigured(true);
995996

997+
$container->register(TaggedService5::class)
998+
->setPublic(true)
999+
->setAutoconfigured(true);
1000+
9961001
$container->register('failing_factory', \stdClass::class);
9971002
$container->register('ccc', TaggedService4::class)
9981003
->setFactory([new Reference('failing_factory'), 'create'])
@@ -1018,6 +1023,12 @@ static function (ChildDefinition $definition, CustomAnyAttribute $attribute, \Re
10181023
['property' => 'name'],
10191024
['someAttribute' => 'on name', 'priority' => 0, 'property' => 'name'],
10201025
],
1026+
TaggedService5::class => [
1027+
['class' => TaggedService5::class],
1028+
['parameter' => 'param1'],
1029+
['method' => 'fooAction'],
1030+
['property' => 'name'],
1031+
],
10211032
'ccc' => [
10221033
['class' => TaggedService4::class],
10231034
['method' => 'fooAction'],

Tests/Fixtures/Attribute/CustomAnyAttribute.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute;
1313

1414
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)]
15-
final class CustomAnyAttribute
15+
class CustomAnyAttribute
1616
{
1717
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)]
15+
class CustomChildAttribute extends CustomAnyAttribute
16+
{
17+
}

Tests/Fixtures/TaggedService5.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
13+
14+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomChildAttribute;
15+
16+
#[CustomChildAttribute]
17+
final class TaggedService5
18+
{
19+
#[CustomChildAttribute]
20+
public string $name;
21+
22+
public function __construct(
23+
#[CustomChildAttribute]
24+
private string $param1,
25+
) {}
26+
27+
#[CustomChildAttribute]
28+
public function fooAction(
29+
#[CustomChildAttribute]
30+
string $param1
31+
) {}
32+
}

0 commit comments

Comments
 (0)