Skip to content

[DependencyInjection] Add #[Autoconfigure] to help define autoconfiguration rules #39804

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

Merged
merged 1 commit into from
Feb 16, 2021

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Jan 12, 2021

Q A
Branch? 5.x
Bug fix? yes
New feature? no
Deprecations? no
Tickets -
License MIT
Doc PR -

Being inspired by the discussion with @derrabus in #39776.

This PR allows declaring autoconfiguration rules using an attribute on classes/interfaces, eg:
#[Autoconfigure(bind: ['$foo' => 'bar'], tags: [...], calls: [...])]

This should typically be added on a base class/interface to tell how implementations of such a base type should be autoconfigured. The attribute is parsed when autoconfiguration is enabled, except when a definition has the container.ignore_attributes tag, which allows opting out from this behavior.

As usual, the corresponding rules are applied only to services that have autoconfiguration enabled.

In practice, this means that this enables auto-tagging of all implementations of this interface:

#[Autoconfigure(tags: ['my_tag'])]
interface MyInterface {...}

Of course, all auto-configurable settings are handled (calls, bindings, etc.)

This PR adds another attribute: #[AutoconfigureTag()].

It extends #[Autoconfigure] and allows for specifically defining tags to attach by autoconfiguration.

The name of the tag is optional and defaults to the name of the tagged type (typically the FQCN of an interface). This should ease with writing locators/iterators of tagged services.

#[AutoconfigureTag()]
interface MyInterface {...}

@nicolas-grekas nicolas-grekas added this to the 5.x milestone Jan 12, 2021
@carsonbot carsonbot changed the title [DI] Add "#[Autoconfigure]" to tell how a discovered type should be autoconfigured [DependencyInjection] Add "#[Autoconfigure]" to tell how a discovered type should be autoconfigured Jan 12, 2021
@nicolas-grekas nicolas-grekas force-pushed the autoconf-attr branch 3 times, most recently from 8c5ba49 to 1e1b86e Compare January 12, 2021 19:11
@ro0NL
Copy link
Contributor

ro0NL commented Jan 12, 2021

is there any reason for the "auto discovered services only" actually?

@apfelbox
Copy link
Contributor

Quick nitpick question: should the attribute be named #[Autoconfigure] or #[Autoconfigured]? Because the class is autoconfigured, it does not do any configuring itself.

The recently proposed #[Deprecated] attribute in PHP itself is in passive voice as well.

@nicolas-grekas
Copy link
Member Author

Because the class is autoconfigured, it does not do any configuring itself.

I feel like I missed explaining correctly how this works. Yes, this attribute declares a configuration rule for autoconfiguration.

@nicolas-grekas nicolas-grekas changed the title [DependencyInjection] Add "#[Autoconfigure]" to tell how a discovered type should be autoconfigured [DependencyInjection] Add #[Autoconfigure] to help define autoconfiguration rules Jan 13, 2021
@nicolas-grekas nicolas-grekas force-pushed the autoconf-attr branch 3 times, most recently from 3a1e8b6 to 5b98e9d Compare January 14, 2021 00:00
@wouterj
Copy link
Member

wouterj commented Feb 4, 2021

IIUC, we now have 2 competing PRs here. As far as I know, it was decided that progress continues in #39897? Please correct me if I'm wrong, but otherwise this one should be closed I think.

@nicolas-grekas
Copy link
Member Author

@wouterj this one provides something else. Both should be merged to me in the end. This one before #39897 since it's ready and might be used by #39897 as explained in #39897 (comment)

@nicolas-grekas nicolas-grekas force-pushed the autoconf-attr branch 2 times, most recently from afe384e to e66a3ba Compare February 6, 2021 09:13
@nicolas-grekas
Copy link
Member Author

/cc @symfony/mergers anyone?

@wouterj
Copy link
Member

wouterj commented Feb 11, 2021

As requested, I'll post some of my comment in the other PR (ref: #39897 (comment) ) here:

😃 What I like about this PR is that it reuses the autoconfigure system (using the Yaml file loader seems a bit hacky, but I'm surely not the one to make a decision about that).
😐 What I'm not so sure about is why there is a need to have a userland Autoconfigure attribute. I personally don't see this need (especially if #39897 will make it, as that implements more specialized and nice userland attributes for this purpose).
I think such a generic attribute opens up a lot of misuse (e.g. in the other PR, it was shown that you can misuse attributes instead of implementing the ResetInterface contract).
I feel like it couples attributes directly to DI (which e.g. a @required annotation explicitly avoids).
At last, by having both a generic attribute and more specific attributes (from #39897), we provide more than 1 solution for the same problem. How should we document this? What are the advantages of one over the other?

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Feb 11, 2021

(using the Yaml file loader seems a bit hacky, but I'm surely not the one to make a decision about that).

What matters here is how one can describe something using an array data structure. YamlLoader does exactly this: turn arrays (from yaml) into actual calls. Here, we just reuse that logic, so that the same array-structures can also be used in attributes. E.g.:

#[Autoconfigure(
    calls: [
		'setFoo' => ['the-args']
	]
)]

What I'm not so sure about is why there is a need to have a userland Autoconfigure attribute. I personally don't see this need

All ppl that use _instanceof or $container->registerForAutoconfiguration() have a need for this.
It allows telling to the DI engine how a definition should be autoconfigured, depending on the base types of the class it describes.

especially if #39897 will make it, as that implements more specialized and nice userland attributes for this purpose.

There is a critical difference with #39897: the attribute in this PR allows defining rules that are inherited. #39897 defines attributes that are not inherited.

That's THE critical difference between both PRs, and why they fill a different need.

I think such a generic attribute opens up a lot of misuse (e.g. in the other PR, it was shown that you can misuse attributes instead of implementing the ResetInterface contract).

Can you clarify? To me, it looks like you're arguing against empowering ppl, which I'm sure you aren't.
I must miss something, or I just don't agree :)

I feel like it couples attributes directly to DI (which e.g. a @required annotation explicitly avoids).

I do not understand this "coupling" argument. To me, as I explained in #39776 (comment), there is zero difference between this PR, #39776 or #39897 in this regard.

Maybe you can help me understand what you mean with actual examples of when this "coupling" would happen?

At last, by having both a generic attribute and more specific attributes (from #39897), we provide more than 1 solution for the same problem. How should we document this? What are the advantages of one over the other?

See "the critical difference" I mention above.

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Feb 11, 2021

How should we document this?

TL;DR of my previous comment:

Note that these two lines are orthogonal.

Copy link
Member

@derrabus derrabus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the intention of this generic attribute as a nicer way to configure autoconfiguration rules for userland base types.

Instead of cluttering my kernel with…

$container->registerForAutoconfiguration(MyInterface::class) 
    ->addTag('my_tag');

… I annotate this rule to my interface instead.

#[Autoconfigure(tags: ['my_tag'])]
interface MyInterface {}

Did I get this right?

I think, this is the use case we can agree on. But since attributes are a new feature to most users, it is imperative that we distinguish the use-cases of this PR and #39897 in the documentation.

@nicolas-grekas
Copy link
Member Author

@derrabus you've got it right. I agree about the doc.

@nicolas-grekas nicolas-grekas merged commit 4d91b8f into symfony:5.x Feb 16, 2021
@nicolas-grekas nicolas-grekas deleted the autoconf-attr branch February 18, 2021 17:00
nicolas-grekas added a commit that referenced this pull request Mar 16, 2021
…r defining the index and priority of classes found in tagged iterators/locators (nicolas-grekas)

This PR was merged into the 5.3-dev branch.

Discussion
----------

[DependencyInjection] Add `#[TaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

Next to #39804, this PR adds a new `#[TaggedItem]` attribute that ppl can use to define the index of their service classes when they're used in tagged collections (iterators/locators.

This replaces the `public static getDefaultName()` and `getDefaultPriority()` methods that ppl could use for this purpose:
```php
#[TaggedItem(index: 'api.logger', priority: 123)]
class MyApiLogger implements LoggerInterface
{
}
```

This will ship the corresponding service at index `api.logger`, priority=123 when building locators/iterators.

Commits
-------

252f2ca [DependencyInjection] Add `#[TaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators
@fabpot fabpot mentioned this pull request Apr 18, 2021
@kozlice
Copy link

kozlice commented May 13, 2021

This is a really cool feature, but I've just tried it and there seems to be an issue:

#[Autoconfigure(tags: ['some.tag'])]
interface MyServiceInterface {}

class MyService implements MyServiceInterface {}

$class = new ReflectionClass(MyService::class);
// That's how RegisterAutoconfigureAttributesPass does it
$attributes = $class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF);
// Whoopsie, empty array
var_dump($attributes);

Running PHP 8.0.6. Looks like the IS_INSTANCEOF flag doesn't do what it is supposed to do.

@nicolas-grekas
Copy link
Member Author

You might have missed how this works. I can't really help because I don't know what you want todo, but of course, requesting the attributes on class MyService won't list you the attributes on interface MyServiceInterface...

@kozlice
Copy link

kozlice commented May 13, 2021

@nicolas-grekas Yeah, I might've misunderstood something.

The announcement in blog says that Autoconfigure can be used to replace registerForAutoconfiguration for auto-tagging by interface.

So, I've tried this:

#[Autoconfigure(tags: ['some.tag'])]
interface ServiceInterface {}

class ServiceA implements ServiceInterface {}
class ServiceB implements ServiceInterface {}
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\ServiceA: ~
    App\ServiceB: ~

And I was expecting both services to be tagged, but they are not.

@nicolas-grekas
Copy link
Member Author

Please open an issue if you think you found a bug, with a reproducer ideally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants