-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DependencyInjection] Autoconfigurable attributes #39897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DependencyInjection] Autoconfigurable attributes #39897
Conversation
53cafd2
to
df793fe
Compare
Thanks for giving this a try. I can't have a look immediately, but I'm just thinking that we should make this ready to support all kind of attributes, eg on params, methods, etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like it.
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I really like how $container->registerAttributeForAutoconfiguration()
allows mapping random attributes to DI concepts.
There are some important design issues right now IMHO, let's resolve them :)
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Show resolved
Hide resolved
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/EventDispatcher/DependencyInjection/EventListenerAutoconfigurator.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Show resolved
Hide resolved
A wish from Twitter ;) public function __construct(
#[Tagged('my.tag')] ContainerInterface/iterable $tagged,
) |
I want to have this as well, but maybe as a follow-up to this PR. |
a904953
to
cbfcc0a
Compare
Sorry for stalling so long. I've had some rough weeks juggeling day job and homeschooling. 😞 I have updated the PR to address the comments:
Most importantly, the autoconfigurator now receives an empty |
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Outdated
Show resolved
Hide resolved
cbfcc0a
to
ba62349
Compare
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/DependencyInjection/Compiler/AttributeAutoconfigurationPass.php
Outdated
Show resolved
Hide resolved
f99223e
to
894d77d
Compare
bd6d13e
to
5af5333
Compare
✅ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this a lot! Thanks for the great and detailed explanation of this feature in the PR description.
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
Show resolved
Hide resolved
Regarding @javiereguiluz's comment: I wonder how autocompletion works here. That's not a reason to block merging this PR, but something to keep an eye out for. E.g. even though it has this special Btw, I quite like the more action oriented |
I really love this PR - congrats @derrabus!
This is a good catch... and it may be a perfect opportunity to use a different naming convention for these attributes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it!
I also like @weaverryan's suggestion. However, it's just a naming thing, so here is an approval for all code changes (we can also make the rename in a separate PR, I think we should come up with a general attribute-naming strategy: action based or object based?)
On my side, I'd vote for keeping |
5744933
to
2ab3caf
Compare
I mean, we could also |
Thank you @derrabus. |
Based on the implementation of symfony#39897 is made it possible to autoconfigure method attributes.
Based on the implementation of symfony#39897 I made it possible to autoconfigure method attributes.
Based on the implementation of symfony#39897 I made it possible to autoconfigure method attributes.
Based on the implementation of symfony#39897 I made it possible to autoconfigure method attributes.
Based on the implementation of symfony#39897 I made it possible to autoconfigure method attributes.
Based on the implementation of symfony#39897 I made it possible to autoconfigure method attributes.
…ethods, properties and parameters (ruudk) This PR was merged into the 5.4 branch. Discussion ---------- [DependencyInjection] Autoconfigurable attributes on methods, properties and parameters | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? |no | New feature? | yes | Deprecations? |no | Tickets | | License | MIT | Doc PR | ## Introduction #39897 introduced the possibility auto configure classes that were annotated with attributes: ```php $container->registerAttributeForAutoconfiguration( MyAttribute::class, static function (ChildDefinition $definition, MyAttribute $attribute, \ReflectionClass $reflector): void { $definition->addTag('my_tag', ['some_property' => $attribute->someProperty]); } ); ``` This works great. But it only works when the attribute is added on the class. With this PR, it's now possible to also auto configure methods, properties and parameters. ## How does it work? Let's say you have an attribute that targets classes and methods like this: ```php #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)] final class MyAttribute { } ``` You have two services that use them: ```php #[MyAttribute] class MyService { } class MyOtherService { #[MyAttribute] public function myMethod() {} } ``` You can now use `registerAttributeForAutoconfiguration` in your extension, together with a union of the types that you want to seach for. In this example, the extension only cares for classes and methods, so it uses `\ReflectionClass|\ReflectionMethod $reflector`: ```php final class MyBundleExtension extends Extension { public function load(array $configs, ContainerBuilder $container) : void { $container->registerAttributeForAutoconfiguration( MyAttribute::class, static function (ChildDefinition $definition, MyAttribute $attribute, \ReflectionClass|\ReflectionMethod $reflector) : void { $args = []; if ($reflector instanceof \ReflectionMethod) { $args['method'] = $reflector->getName(); } $definition->addTag('my.tag', $args); } ); } } ``` This will tag `MyService` with `my.tag` and it will tag `MyOtherService` with `my.tag, method: myMethod` If the extension also wants to target the properties that are annotated with attributes, it can either change the union to `\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector` or it can just use `\Reflector $reflector` and do the switching in the callable. ## Another example Let's say you have an attribute like this: ```php #[Attribute(Attribute::TARGET_CLASS)] final class MyAttribute { } ``` and you use it like this: ```php $container->registerAttributeForAutoconfiguration( MyAttribute::class, static function (ChildDefinition $definition, MyAttribute $attribute, \ReflectionClass|\ReflectionMethod $reflector) : void { $definition->addTag('my.tag'); } ); ``` you'll get an error saying that `ReflectionMethod` is not possible as the attribute only targets classes. Commits ------- 917fcc0 [DependencyInjection] Autoconfigurable attributes on methods, properties and parameters
…ethods, properties and parameters (ruudk) This PR was merged into the 5.4 branch. Discussion ---------- [DependencyInjection] Autoconfigurable attributes on methods, properties and parameters | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? |no | New feature? | yes | Deprecations? |no | Tickets | | License | MIT | Doc PR | ## Introduction symfony/symfony#39897 introduced the possibility auto configure classes that were annotated with attributes: ```php $container->registerAttributeForAutoconfiguration( MyAttribute::class, static function (ChildDefinition $definition, MyAttribute $attribute, \ReflectionClass $reflector): void { $definition->addTag('my_tag', ['some_property' => $attribute->someProperty]); } ); ``` This works great. But it only works when the attribute is added on the class. With this PR, it's now possible to also auto configure methods, properties and parameters. ## How does it work? Let's say you have an attribute that targets classes and methods like this: ```php #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)] final class MyAttribute { } ``` You have two services that use them: ```php #[MyAttribute] class MyService { } class MyOtherService { #[MyAttribute] public function myMethod() {} } ``` You can now use `registerAttributeForAutoconfiguration` in your extension, together with a union of the types that you want to seach for. In this example, the extension only cares for classes and methods, so it uses `\ReflectionClass|\ReflectionMethod $reflector`: ```php final class MyBundleExtension extends Extension { public function load(array $configs, ContainerBuilder $container) : void { $container->registerAttributeForAutoconfiguration( MyAttribute::class, static function (ChildDefinition $definition, MyAttribute $attribute, \ReflectionClass|\ReflectionMethod $reflector) : void { $args = []; if ($reflector instanceof \ReflectionMethod) { $args['method'] = $reflector->getName(); } $definition->addTag('my.tag', $args); } ); } } ``` This will tag `MyService` with `my.tag` and it will tag `MyOtherService` with `my.tag, method: myMethod` If the extension also wants to target the properties that are annotated with attributes, it can either change the union to `\ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector` or it can just use `\Reflector $reflector` and do the switching in the callable. ## Another example Let's say you have an attribute like this: ```php #[Attribute(Attribute::TARGET_CLASS)] final class MyAttribute { } ``` and you use it like this: ```php $container->registerAttributeForAutoconfiguration( MyAttribute::class, static function (ChildDefinition $definition, MyAttribute $attribute, \ReflectionClass|\ReflectionMethod $reflector) : void { $definition->addTag('my.tag'); } ); ``` you'll get an error saying that `ReflectionMethod` is not possible as the attribute only targets classes. Commits ------- 917fcc09f7 [DependencyInjection] Autoconfigurable attributes on methods, properties and parameters
Alternative to #39776. Please have a look at that PR as well to see the full discussion.
With this PR, I propose to introduce a way to autoconfigure services by using PHP Attributes. The feature is enabled on all autoconfigured service definitions. The heart of this feature is a new way to register autoconfiguration rules:
An example for such an attribute is shipped with this PR with the
EventListener
attribute. This piece of code is a fully functional autoconfigurable event listener:What makes attributes interesting for this kind of configuration is that they can transport meta information that can be evaluated during autoconfiguration. For instance, if we wanted to change the priority of the listener, we can just pass it to the attribute.
#[EventListener(priority: 42)]
The attribute itself is a dumb data container and is unaware of the DI component.
This PR provides applications and bundles with the necessary tools to build own attributes and autoconfiguration rules.