Skip to content

Conversation

weitzman
Copy link
Contributor

@weitzman weitzman commented Jul 9, 2025

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

I'm playing with Invokable Commands and am seeing a limitation which is bothersome. Attributes attached to the invokable class are lost by the time we have a Command added to the Application. Consider an invokable class like below. The #[CLI\FieldLabels] and #[CLI\DefaultFields] Attributes are not retrievable from the Command. That is, there is no easy way to access the actual InvokableCommand object instead of the wrapping Command.

With this PR, Attributes become accessible via $command->getCode()->getCallable()

namespace Drush\Commands\core;

#[AsCommand(
    name: 'twig:unused',
    description: 'Find potentially unused Twig templates.',
    aliases: ['twu'],
)]
#[CLI\FieldLabels(labels: ['template' => 'Template', 'compiled' => 'Compiled'])]
#[CLI\DefaultFields(fields: ['template', 'compiled'])]
final class TwigUnusedCommand
{
    public function __invoke(
        #[Argument(description: 'A comma delimited list of paths to recursively search')] string $searchpaths,
        InputInterface $input,
        OutputInterface $output
    ): int
    {
        $data = $this->doExecute($searchpaths);
        $this->writeFormattedOutput($input, $output, $data);
        return Command::SUCCESS;
    }
}

@weitzman weitzman requested a review from chalasr as a code owner July 9, 2025 11:31
@carsonbot carsonbot added this to the 7.4 milestone Jul 9, 2025
@carsonbot
Copy link

Hey!

Thanks for your PR. You are targeting branch "7.4" but it seems your PR description refers to branch "7.4 for features".
Could you update the PR description or change target branch? This helps core maintainers a lot.

Cheers!

Carsonbot

@derrabus
Copy link
Member

derrabus commented Jul 9, 2025

Attributes attached to the invokable class are lost by the time we have a Command added to the Application.

Can you elaborate on this? Why are they lost?

Also: Please always provide tests that cover your change.

@weitzman
Copy link
Contributor Author

weitzman commented Jul 9, 2025

@derrabus - I added a hyperlink to the OP explaining whats meant by "wrapping command". Let me know if its still unclear.

If anyone can suggest an existing test to modify, I will do so.

@GromNaN
Copy link
Member

GromNaN commented Jul 9, 2025

I don't understand why you need to access attributes of the wrapped invokable command from the actual Command class. Attributes should not be read at runtime (for performance). You can create an autoconfiguration mechanism to read the attributes during container compilation and inject them into a service.

@chalasr
Copy link
Member

chalasr commented Jul 9, 2025

Jérôme has a good point about manipulating the command at compile time instead of runtime. When accessing the service you get the real instance .

@derrabus
Copy link
Member

derrabus commented Jul 9, 2025

Let me know if its still unclear.

It is. Your link is pointing to some line in our codebase, but that line does not explain your use-case. It neither tells me what you're trying to achieve, nor why you want to do that, nor how you tried to do that.

If anyone can suggest an existing test to modify, I will do so.

This is a new feature, which means we need new tests. You can have a look at the existing test suite for inspiration, but we won't write those tests for you. And we won't merge a new feature without any tests.

@OskarStark OskarStark changed the title Make Attributes accessible on Invokable Commands Make attributes accessible on invokable commands Jul 9, 2025
@weitzman
Copy link
Contributor Author

weitzman commented Jul 9, 2025

Apologies for not including my use case originally. I'm no Symfony expert, so your expertise here is much appreciated.

I'm the maintainer of Drush, the CLI for Drupal. Drush is a happy user of the standalone Console component.

I am exploring using Invokable Commands in our next version. Symfony made this possible in May in #60394. Drush has no command services at this time, so the suggestion to read Attributes at compile time won't work. We need a runtime solution, and so will other standalone Console users. We could read the Attributes as we add commands to the Application, but then we would have to store them separate from the Command object. Or we would have to extend Command and add a method to get the callable with its Attributes. That feels awkward enough that we wouldn't support Invokable Commands which is a shame IMO - they are great.

@GromNaN
Copy link
Member

GromNaN commented Jul 9, 2025

Thanks for the context.

I see that Drush is built with consolidation/annotated-command. Looking at its documentation for the FieldLabels and DefaultFields attributes, it looks very close to what we built with invokable commands. I do think that the two attribute systems (Symfony and Consolidation) are mutually exclusive to configure the command.

@weitzman
Copy link
Contributor Author

weitzman commented Jul 9, 2025

We are working to remove the dependency on consolidation/annotated-command in favor native Symfony Console conventions. See drush-ops/drush#6135. This Console PR is part of the effort.

Symfony Console supports a few Attributes like AsCommand and Argument and Option - we need a lot more in Drush. We can add declare Attribute classes and add them to our Invokable Commands but we can't cleanly read the Attributes - thus this PR.

FYI, I removed the return type hint to an Internal class.

@weitzman
Copy link
Contributor Author

weitzman commented Jul 10, 2025

I have added tests to the PR. I'm getting a failure because I removed the return type hint to an internal class. How can we best resolve this?

So, do we think this is solvable for us and other standalone Console users? I hope so.

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.

Here is a solution so keep the InvokableCommand internal.

Do you want to read the Consolidation attributes for backward compatibility? I still don't get how you will proceed.

@carsonbot carsonbot changed the title Make attributes accessible on invokable commands [Console] Make attributes accessible on invokable commands Jul 10, 2025
@weitzman
Copy link
Contributor Author

I think I've addressed all feedback. The one test failure is unrelated to this PR.

@GromNaN GromNaN changed the title [Console] Make attributes accessible on invokable commands [Console] add getter for the original command "code" object Jul 14, 2025
@GromNaN GromNaN changed the title [Console] add getter for the original command "code" object [Console] Add getter for the original command "code" object Jul 14, 2025
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.

Please add an entry in the changelog of the console component.

@weitzman
Copy link
Contributor Author

Changelog entry added.

@GromNaN
Copy link
Member

GromNaN commented Jul 17, 2025

Getter name getCode looks like status code. Those were my initial thoughts when I read the PR title.

This name matches the existing setCode. If it was returning the status code, we would name it getStatusCode.

@weitzman
Copy link
Contributor Author

Anything else needed here before merge? Thanks all.

@weitzman weitzman force-pushed the feature/get-invokable-command-attributes branch from 4161145 to cc9aaa9 Compare August 5, 2025 13:06
@GromNaN
Copy link
Member

GromNaN commented Aug 5, 2025

@symfony/mergers Could you review this PR please?

weitzman and others added 2 commits August 22, 2025 13:57
…code" object (weitzman)

This PR was squashed before being merged into the 7.4 branch.

Discussion
----------

[Console] Add getter for the original command "code" object

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

I'm playing with Invokable Commands and am seeing a limitation which is bothersome. Attributes attached to the invokable class are lost by the time we have a  `Command` added to the Application. Consider an invokable class like below. The #[CLI\FieldLabels] and #[CLI\DefaultFields] Attributes are not retrievable from the Command. That is, there is no easy way to access the actual InvokableCommand object instead of the [wrapping Command](https://github.com/symfony/symfony/blob/a384c231a0051c0ac10e89c2a0516343f9fd3187/src/Symfony/Component/Console/Application.php#L553).

With this PR, Attributes become accessible via `$command->getCode()->getCallable()`

```php
namespace Drush\Commands\core;

#[AsCommand(
    name: 'twig:unused',
    description: 'Find potentially unused Twig templates.',
    aliases: ['twu'],
)]
#[CLI\FieldLabels(labels: ['template' => 'Template', 'compiled' => 'Compiled'])]
#[CLI\DefaultFields(fields: ['template', 'compiled'])]
final class TwigUnusedCommand
{
    public function __invoke(
        #[Argument(description: 'A comma delimited list of paths to recursively search')] string $searchpaths,
        InputInterface $input,
        OutputInterface $output
    ): int
    {
        $data = $this->doExecute($searchpaths);
        $this->writeFormattedOutput($input, $output, $data);
        return Command::SUCCESS;
    }
}
```

Commits
-------

b57d275 [Console] Add getter for the original command "code" object
@GromNaN GromNaN force-pushed the feature/get-invokable-command-attributes branch from 71a85eb to b57d275 Compare August 22, 2025 11:57
@GromNaN
Copy link
Member

GromNaN commented Aug 22, 2025

It took time, but here we go, this is in now. Thank you very much @weitzman.

@GromNaN GromNaN merged commit 01f1ab1 into symfony:7.4 Aug 22, 2025
3 of 4 checks passed
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