Skip to content

[Console] Annotate AsCommand attribute as @final #60066

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

Closed
wants to merge 0 commits into from

Conversation

Somrlik
Copy link
Contributor

@Somrlik Somrlik commented Mar 28, 2025

Q A
Branch? 7.3
Bug fix? no
New feature? yes
Deprecations? no
Issues no
License MIT

AsCommand attribute is not final and thus can be extended, but its inheritors don't work as expected when used.

The constructor of Command expects AsCommand attribute to be its base class and does not handle cases where AsCommand might be extended. This can be solved/fixed/improved by using \ReflectionAttribute::IS_INSTANCEOF when calling \ReflectionClass::getAttributes().

These extensions might provide useful in cases such as

final class PeriodicallyScheduledCommand extends AsCommand {...}

#[PeriodicallyScheduledCommand(name: 'name', period: '2 minutes')]
final class ExampleCommand extends Command {...}

or

final class RequiresAuthCommand extends AsCommand {...}

#[RequiresAuthCommand(name: 'name', authGroup: 'administrators')]
final class ExampleCommand extends Command {...}

where metaprogramming enabled by attributes could be useful.

You could of course implement another attribute, but since AsCommand is not final, one would expect its behaviour to be consistent when extended.

I welcome suggestions for more tests.

Don't know if this is a feature worth documenting in docs.

Copy link
Member

@GromNaN GromNaN left a comment

Choose a reason for hiding this comment

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

The example is not very accurate: if you add a property to the child class you would have to read the attribute on your own. Creating a dedicated attribute class is better.

There is actually an issue as the AsCommand class allows inheritance, but the we don't scan the children classes. An alternative fix could be to make AsCommand final.

But I see a use-cases for extending the AsCommand class: set one of its properties. Example: automatically add a static prefix to the command name.

@@ -61,7 +61,7 @@ public static function getDefaultName(): ?string
{
trigger_deprecation('symfony/console', '7.3', 'Method "%s()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', __METHOD__);

if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) {
if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class, \ReflectionAttribute::IS_INSTANCEOF)) {
Copy link
Member

Choose a reason for hiding this comment

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

Keep in mind that by using this option, PHP tries to load all the classes when reading the attributes.

Not a big deal for Symfony Framework, but that could impact standalone usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Of course, the example should have been clearer.

Given the arguments and concerns you raised, I think that making AsCommand final seems like a better approach. It will remove the unexpected/undocumented behaviour.

Of course, it's a breaking change, but cursory search of GitHub reveals just one usage.

If you feel okay with changing of the attribute to final, I can edit this PR or make a new one, your call.

Copy link
Member

Choose a reason for hiding this comment

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

In 7.3, we need to tag it as @final in phpdoc instead of making it actually final, so that we trigger a deprecation warning for classes extending it.

Copy link
Member

Choose a reason for hiding this comment

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

I also prefer the @final annotation on AsCommand. Can you update your PR?

@Somrlik Somrlik changed the title [Console] Add support for extension of AsCommand attribute [Console] Annotate AsCommand attribute as @final Apr 1, 2025
@Somrlik Somrlik closed this Apr 1, 2025
GromNaN added a commit that referenced this pull request Apr 1, 2025
…mrlik, GromNaN)

This PR was merged into the 7.3 branch.

Discussion
----------

[Console] Mark `AsCommand` attribute as ``@final``

| Q             | A
| ------------- | ---
| Branch?       | 7.3
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | yes
| License       | MIT

`AsCommand` attribute doesn't work as expected when extended and probably should be declared as final.

In order to not break BC and trigger a deprecation warning, I marked the attribute as ``@final`` as per suggestions in previous PR.

Superseeds #60066 as I closed it accidentally, sorry for confusion 😕

Commits
-------

1db80a5 [Console] Mark `AsCommand` attribute as ``@final``
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.

4 participants