Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6911e60

Browse files
ruudknicolas-grekas
authored andcommitted
[DependencyInjection] Autoconfigurable attributes on methods, properties and parameters
1 parent0bf0278 commit6911e60

File tree

15 files changed

+393
-17
lines changed

15 files changed

+393
-17
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -555,9 +555,19 @@ public function load(array $configs, ContainerBuilder $container)
555555
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
556556
->addMethodCall('setLogger', [newReference('logger')]);
557557

558-
$container->registerAttributeForAutoconfiguration(AsEventListener::class,staticfunction (ChildDefinition$definition,AsEventListener$attribute):void {
559-
$definition->addTag('kernel.event_listener',get_object_vars($attribute));
560-
});
558+
if (\PHP_VERSION_ID >=80000) {
559+
$container->registerAttributeForAutoconfiguration(AsEventListener::class,staticfunction (ChildDefinition$definition,AsEventListener$attribute,\Reflector$reflector) {
560+
$tagAttributes =get_object_vars($attribute);
561+
if ($reflectorinstanceof \ReflectionMethod) {
562+
if (isset($tagAttributes['method'])) {
563+
thrownewLogicException(sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".',$reflector->class,$reflector->name));
564+
}
565+
$tagAttributes['method'] =$reflector->getName();
566+
}
567+
$definition->addTag('kernel.event_listener',$tagAttributes);
568+
});
569+
}
570+
561571
$container->registerAttributeForAutoconfiguration(AsController::class,staticfunction (ChildDefinition$definition,AsController$attribute):void {
562572
$definition->addTag('controller.service_arguments');
563573
});

‎src/Symfony/Component/DependencyInjection/CHANGELOG.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ CHANGELOG
44
5.4
55
---
66

7-
* Add`service_closure()` to the PHP-DSL
7+
* Add`service_closure()` to the PHP-DSL
8+
* Add support for autoconfigurable attributes on methods
89

910
5.3
1011
---

‎src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php‎

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,66 @@
1414
useSymfony\Component\DependencyInjection\ChildDefinition;
1515
useSymfony\Component\DependencyInjection\ContainerBuilder;
1616
useSymfony\Component\DependencyInjection\Definition;
17+
useSymfony\Component\DependencyInjection\Exception\LogicException;
1718

1819
/**
1920
* @author Alexander M. Turek <me@derrabus.de>
2021
*/
2122
finalclass AttributeAutoconfigurationPassextends AbstractRecursivePass
2223
{
24+
private$classAttributeConfigurators = [];
25+
private$methodAttributeConfigurators = [];
26+
private$propertyAttributeConfigurators = [];
27+
private$parameterAttributeConfigurators = [];
28+
2329
publicfunctionprocess(ContainerBuilder$container):void
2430
{
2531
if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) {
2632
return;
2733
}
2834

35+
foreach ($container->getAutoconfiguredAttributes()as$attributeName =>$callable) {
36+
$callableReflector =new \ReflectionFunction(\Closure::fromCallable($callable));
37+
if ($callableReflector->getNumberOfParameters() <=2) {
38+
$this->classAttributeConfigurators[$attributeName] =$callable;
39+
continue;
40+
}
41+
42+
$reflectorParameter =$callableReflector->getParameters()[2];
43+
$parameterType =$reflectorParameter->getType();
44+
$types = [];
45+
if ($parameterTypeinstanceof \ReflectionUnionType) {
46+
foreach ($parameterType->getTypes()as$type) {
47+
$types[] =$type->getName();
48+
}
49+
}elseif ($parameterTypeinstanceof \ReflectionNamedType) {
50+
$types[] =$parameterType->getName();
51+
}else {
52+
thrownewLogicException(sprintf('Argument "$%s" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in "%s" on line "%d".',$reflectorParameter->getName(),$callableReflector->getFileName(),$callableReflector->getStartLine()));
53+
}
54+
55+
try {
56+
$attributeReflector =new \ReflectionClass($attributeName);
57+
}catch (\ReflectionException$e) {
58+
continue;
59+
}
60+
61+
$targets =$attributeReflector->getAttributes(\Attribute::class)[0] ??0;
62+
$targets =$targets ?$targets->getArguments()[0] ?? -1 :0;
63+
64+
foreach (['class','method','property','parameter']as$symbol) {
65+
if (['Reflector'] !==$types) {
66+
if (!\in_array('Reflection'.ucfirst($symbol),$types,true)) {
67+
continue;
68+
}
69+
if (!($targets &\constant('Attribute::TARGET_'.strtoupper($symbol)))) {
70+
thrownewLogicException(sprintf('Invalid type "Reflection%s" on argument "$%s": attribute "%s" cannot target a'.$symbol.' in "%s" on line "%d".',ucfirst($symbol),$reflectorParameter->getName(),$attributeName,$callableReflector->getFileName(),$callableReflector->getStartLine()));
71+
}
72+
}
73+
$this->{$symbol.'AttributeConfigurators'}[$attributeName] =$callable;
74+
}
75+
}
76+
2977
parent::process($container);
3078
}
3179

@@ -35,21 +83,74 @@ protected function processValue($value, bool $isRoot = false)
3583
|| !$value->isAutoconfigured()
3684
||$value->isAbstract()
3785
||$value->hasTag('container.ignore_attributes')
38-
|| !($reflector =$this->container->getReflectionClass($value->getClass(),false))
86+
|| !($classReflector =$this->container->getReflectionClass($value->getClass(),false))
3987
) {
4088
returnparent::processValue($value,$isRoot);
4189
}
4290

43-
$autoconfiguredAttributes =$this->container->getAutoconfiguredAttributes();
4491
$instanceof =$value->getInstanceofConditionals();
45-
$conditionals =$instanceof[$reflector->getName()] ??newChildDefinition('');
46-
foreach ($reflector->getAttributes()as$attribute) {
47-
if ($configurator =$autoconfiguredAttributes[$attribute->getName()] ??null) {
48-
$configurator($conditionals,$attribute->newInstance(),$reflector);
92+
$conditionals =$instanceof[$classReflector->getName()] ??newChildDefinition('');
93+
94+
if ($this->classAttributeConfigurators) {
95+
foreach ($classReflector->getAttributes()as$attribute) {
96+
if ($configurator =$this->classAttributeConfigurators[$attribute->getName()] ??null) {
97+
$configurator($conditionals,$attribute->newInstance(),$classReflector);
98+
}
4999
}
50100
}
51-
if (!isset($instanceof[$reflector->getName()]) &&newChildDefinition('') !=$conditionals) {
52-
$instanceof[$reflector->getName()] =$conditionals;
101+
102+
if ($this->parameterAttributeConfigurators &&$constructorReflector =$this->getConstructor($value,false)) {
103+
foreach ($constructorReflector->getParameters()as$parameterReflector) {
104+
foreach ($parameterReflector->getAttributes()as$attribute) {
105+
if ($configurator =$this->parameterAttributeConfigurators[$attribute->getName()] ??null) {
106+
$configurator($conditionals,$attribute->newInstance(),$parameterReflector);
107+
}
108+
}
109+
}
110+
}
111+
112+
if ($this->methodAttributeConfigurators ||$this->parameterAttributeConfigurators) {
113+
foreach ($classReflector->getMethods(\ReflectionMethod::IS_PUBLIC)as$methodReflector) {
114+
if ($methodReflector->isStatic() ||$methodReflector->isConstructor() ||$methodReflector->isDestructor()) {
115+
continue;
116+
}
117+
118+
if ($this->methodAttributeConfigurators) {
119+
foreach ($methodReflector->getAttributes()as$attribute) {
120+
if ($configurator =$this->methodAttributeConfigurators[$attribute->getName()] ??null) {
121+
$configurator($conditionals,$attribute->newInstance(),$methodReflector);
122+
}
123+
}
124+
}
125+
126+
if ($this->parameterAttributeConfigurators) {
127+
foreach ($methodReflector->getParameters()as$parameterReflector) {
128+
foreach ($parameterReflector->getAttributes()as$attribute) {
129+
if ($configurator =$this->parameterAttributeConfigurators[$attribute->getName()] ??null) {
130+
$configurator($conditionals,$attribute->newInstance(),$parameterReflector);
131+
}
132+
}
133+
}
134+
}
135+
}
136+
}
137+
138+
if ($this->propertyAttributeConfigurators) {
139+
foreach ($classReflector->getProperties(\ReflectionProperty::IS_PUBLIC)as$propertyReflector) {
140+
if ($propertyReflector->isStatic()) {
141+
continue;
142+
}
143+
144+
foreach ($propertyReflector->getAttributes()as$attribute) {
145+
if ($configurator =$this->propertyAttributeConfigurators[$attribute->getName()] ??null) {
146+
$configurator($conditionals,$attribute->newInstance(),$propertyReflector);
147+
}
148+
}
149+
}
150+
}
151+
152+
if (!isset($instanceof[$classReflector->getName()]) &&newChildDefinition('') !=$conditionals) {
153+
$instanceof[$classReflector->getName()] =$conditionals;
53154
$value->setInstanceofConditionals($instanceof);
54155
}
55156

‎src/Symfony/Component/DependencyInjection/ContainerBuilder.php‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1309,7 +1309,14 @@ public function registerForAutoconfiguration(string $interface)
13091309
/**
13101310
* Registers an attribute that will be used for autoconfiguring annotated classes.
13111311
*
1312-
* The configurator will receive a ChildDefinition instance, an instance of the attribute and the corresponding \ReflectionClass, in that order.
1312+
* If an attribute can only be used on 1 target, the \Reflector type should be narrowed to
1313+
* one of: \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter
1314+
*
1315+
* If an attribute can be used on multiple targets, a union type can be used based on above types.
1316+
*
1317+
* @template T
1318+
* @param class-string<T> $attributeClass
1319+
* @param callable(ChildDefinition $definition, T $attribute, \Reflector $reflector): void $configurator
13131320
*/
13141321
publicfunctionregisterAttributeForAutoconfiguration(string$attributeClass,callable$configurator):void
13151322
{

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/AttributeAutoconfigurationPassTest.php‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
usePHPUnit\Framework\TestCase;
1515
useSymfony\Component\DependencyInjection\Attribute\AsTaggedItem;
16+
useSymfony\Component\DependencyInjection\ChildDefinition;
1617
useSymfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass;
1718
useSymfony\Component\DependencyInjection\ContainerBuilder;
19+
useSymfony\Component\DependencyInjection\Exception\LogicException;
1820

1921
/**
2022
* @requires PHP 8
@@ -33,4 +35,17 @@ public function testProcessAddsNoEmptyInstanceofConditionals()
3335

3436
$this->assertSame([],$container->getDefinition('foo')->getInstanceofConditionals());
3537
}
38+
39+
publicfunctiontestAttributeConfiguratorCallableMissingType()
40+
{
41+
$container =newContainerBuilder();
42+
$container->registerAttributeForAutoconfiguration(AsTaggedItem::class,staticfunction (ChildDefinition$definition,AsTaggedItem$attribute,$reflector) {});
43+
$container->register('foo', \stdClass::class)
44+
->setAutoconfigured(true)
45+
;
46+
47+
$this->expectException(LogicException::class);
48+
$this->expectExceptionMessage('Argument "$reflector" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in');
49+
(newAttributeAutoconfigurationPass())->process($container);
50+
}
3651
}

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
useSymfony\Component\DependencyInjection\Loader\YamlFileLoader;
2424
useSymfony\Component\DependencyInjection\Reference;
2525
useSymfony\Component\DependencyInjection\ServiceLocator;
26+
useSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAnyAttribute;
2627
useSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration;
28+
useSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomMethodAttribute;
29+
useSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomParameterAttribute;
30+
useSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomPropertyAttribute;
2731
useSymfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
2832
useSymfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
2933
useSymfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
@@ -37,6 +41,7 @@
3741
useSymfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
3842
useSymfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
3943
useSymfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3Configurator;
44+
useSymfony\Component\DependencyInjection\Tests\Fixtures\TaggedService4;
4045
useSymfony\Contracts\Service\ServiceProviderInterface;
4146
useSymfony\Contracts\Service\ServiceSubscriberInterface;
4247

@@ -729,6 +734,86 @@ static function (Definition $definition, CustomAutoconfiguration $attribute) {
729734
],$collector->collectedTags);
730735
}
731736

737+
/**
738+
* @requires PHP 8
739+
*/
740+
publicfunctiontestTagsViaAttributeOnPropertyMethodAndParameter()
741+
{
742+
$container =newContainerBuilder();
743+
$container->registerAttributeForAutoconfiguration(
744+
CustomMethodAttribute::class,
745+
staticfunction (ChildDefinition$definition,CustomMethodAttribute$attribute,\ReflectionMethod$reflector) {
746+
$tagAttributes =get_object_vars($attribute);
747+
$tagAttributes['method'] =$reflector->getName();
748+
749+
$definition->addTag('app.custom_tag',$tagAttributes);
750+
}
751+
);
752+
$container->registerAttributeForAutoconfiguration(
753+
CustomPropertyAttribute::class,
754+
staticfunction (ChildDefinition$definition,CustomPropertyAttribute$attribute,\ReflectionProperty$reflector) {
755+
$tagAttributes =get_object_vars($attribute);
756+
$tagAttributes['property'] =$reflector->getName();
757+
758+
$definition->addTag('app.custom_tag',$tagAttributes);
759+
}
760+
);
761+
$container->registerAttributeForAutoconfiguration(
762+
CustomParameterAttribute::class,
763+
staticfunction (ChildDefinition$definition,CustomParameterAttribute$attribute,\ReflectionParameter$reflector) {
764+
$tagAttributes =get_object_vars($attribute);
765+
$tagAttributes['parameter'] =$reflector->getName();
766+
767+
$definition->addTag('app.custom_tag',$tagAttributes);
768+
}
769+
);
770+
$container->registerAttributeForAutoconfiguration(
771+
CustomAnyAttribute::class,
772+
eval(<<<'PHP'
773+
return static function (\Symfony\Component\DependencyInjection\ChildDefinition $definition, \Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAnyAttribute $attribute, \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter $reflector) {
774+
$tagAttributes = get_object_vars($attribute);
775+
if ($reflector instanceof \ReflectionClass) {
776+
$tagAttributes['class'] = $reflector->getName();
777+
} elseif ($reflector instanceof \ReflectionMethod) {
778+
$tagAttributes['method'] = $reflector->getName();
779+
} elseif ($reflector instanceof \ReflectionProperty) {
780+
$tagAttributes['property'] = $reflector->getName();
781+
} elseif ($reflector instanceof \ReflectionParameter) {
782+
$tagAttributes['parameter'] = $reflector->getName();
783+
}
784+
785+
$definition->addTag('app.custom_tag', $tagAttributes);
786+
};
787+
PHP
788+
));
789+
790+
$container->register(TaggedService4::class)
791+
->setPublic(true)
792+
->setAutoconfigured(true);
793+
794+
$collector =newTagCollector();
795+
$container->addCompilerPass($collector);
796+
797+
$container->compile();
798+
799+
self::assertSame([
800+
TaggedService4::class => [
801+
['class' => TaggedService4::class],
802+
['parameter' =>'param1'],
803+
['someAttribute' =>'on param1 in constructor','priority' =>0,'parameter' =>'param1'],
804+
['parameter' =>'param2'],
805+
['someAttribute' =>'on param2 in constructor','priority' =>0,'parameter' =>'param2'],
806+
['method' =>'fooAction'],
807+
['someAttribute' =>'on fooAction','priority' =>0,'method' =>'fooAction'],
808+
['someAttribute' =>'on param1 in fooAction','priority' =>0,'parameter' =>'param1'],
809+
['method' =>'barAction'],
810+
['someAttribute' =>'on barAction','priority' =>0,'method' =>'barAction'],
811+
['property' =>'name'],
812+
['someAttribute' =>'on name','priority' =>0,'property' =>'name'],
813+
],
814+
],$collector->collectedTags);
815+
}
816+
732817
/**
733818
* @requires PHP 8
734819
*/
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+
namespaceSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)]
15+
finalclass CustomAnyAttribute
16+
{
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
namespaceSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_METHOD)]
15+
finalclass CustomMethodAttribute
16+
{
17+
publicfunction__construct(
18+
publicstring$someAttribute,
19+
publicint$priority =0,
20+
) {
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
namespaceSymfony\Component\DependencyInjection\Tests\Fixtures\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
15+
finalclass CustomParameterAttribute
16+
{
17+
publicfunction__construct(
18+
publicstring$someAttribute,
19+
publicint$priority =0,
20+
) {
21+
}
22+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp