Skip to content

Commit e348b70

Browse files
committed
feature #59473 [Console] Add broader support for command "help" definition (yceruto)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [Console] Add broader support for command "help" definition | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Follow up #59340 Invokable and regular commands can now define the command `help` content via the `#[AsCommand]` attribute. This is particularly useful for invokable commands, as it avoids the need to extend the `Command` class. ```php #[AsCommand( name: 'user:create', description: 'Create a new user', help: <<<TXT The <info>%command.name%</info> command generates a new user class for security and updates your security.yaml file for it. It will also generate a user provider class if your situation needs a custom class. <info>php %command.full_name% email</info> If the argument is missing, the command will ask for the class name interactively. TXT )] class CreateUserCommand { public function __invoke(SymfonyStyle $io, #[Argument] string $email): int { // ... } } ``` Cheers! Commits ------- e9a6b0a [Console] Add broader support for command "help" definition
2 parents da818b7 + e9a6b0a commit e348b70

File tree

7 files changed

+33
-13
lines changed

7 files changed

+33
-13
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,11 @@ public function load(array $configs, ContainerBuilder $container): void
611611
$container->registerForAutoconfiguration(AssetCompilerInterface::class)
612612
->addTag('asset_mapper.compiler');
613613
$container->registerAttributeForAutoconfiguration(AsCommand::class, static function (ChildDefinition $definition, AsCommand $attribute, \ReflectionClass $reflector): void {
614-
$definition->addTag('console.command', ['command' => $attribute->name, 'description' => $attribute->description]);
614+
$definition->addTag('console.command', [
615+
'command' => $attribute->name,
616+
'description' => $attribute->description,
617+
'help' => $attribute->help,
618+
]);
615619
});
616620
$container->registerForAutoconfiguration(Command::class)
617621
->addTag('console.command');

src/Symfony/Component/Console/Attribute/AsCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ class AsCommand
2222
* @param string|null $description The description of the command, displayed with the help page
2323
* @param string[] $aliases The list of aliases of the command. The command will be executed when using one of them (i.e. "cache:clean")
2424
* @param bool $hidden If true, the command won't be shown when listing all the available commands, but it can still be run as any other command
25+
* @param string|null $help The help content of the command, displayed with the help page
2526
*/
2627
public function __construct(
2728
public string $name,
2829
public ?string $description = null,
2930
array $aliases = [],
3031
bool $hidden = false,
32+
public ?string $help = null,
3133
) {
3234
if (!$hidden && !$aliases) {
3335
return;

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add support for invokable commands and add `#[Argument]` and `#[Option]` attributes to define input arguments and options
88
* Deprecate not declaring the parameter type in callable commands defined through `setCode` method
9+
* Add support for help definition via `AsCommand` attribute
910

1011
7.2
1112
---

src/Symfony/Component/Console/Command/Command.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ public function __construct(?string $name = null)
100100
$this->setDescription(static::getDefaultDescription() ?? '');
101101
}
102102

103+
if ('' === $this->help && $attributes = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) {
104+
$this->setHelp($attributes[0]->newInstance()->help ?? '');
105+
}
106+
103107
if (\is_callable($this)) {
104108
$this->code = new InvokableCommand($this, $this(...));
105109
}

src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public function process(ContainerBuilder $container): void
5353
$invokableRef = new Reference($id);
5454
$definition = $container->register($id .= '.command', $class = Command::class)
5555
->addMethodCall('setCode', [$invokableRef]);
56+
} else {
57+
$invokableRef = null;
5658
}
5759

5860
$aliases = $tags[0]['command'] ?? str_replace('%', '%%', $class::getDefaultName() ?? '');
@@ -75,6 +77,7 @@ public function process(ContainerBuilder $container): void
7577
}
7678

7779
$description = $tags[0]['description'] ?? null;
80+
$help = $tags[0]['help'] ?? null;
7881

7982
unset($tags[0]);
8083
$lazyCommandMap[$commandName] = $id;
@@ -91,6 +94,7 @@ public function process(ContainerBuilder $container): void
9194
}
9295

9396
$description ??= $tag['description'] ?? null;
97+
$help ??= $tag['help'] ?? null;
9498
}
9599

96100
$definition->addMethodCall('setName', [$commandName]);
@@ -103,16 +107,12 @@ public function process(ContainerBuilder $container): void
103107
$definition->addMethodCall('setHidden', [true]);
104108
}
105109

106-
if (!$description) {
107-
if (!$r = $container->getReflectionClass($class)) {
108-
throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
109-
}
110-
if (!$r->isSubclassOf(Command::class)) {
111-
throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
112-
}
113-
$description = str_replace('%', '%%', $class::getDefaultDescription() ?? '');
110+
if ($help && $invokableRef) {
111+
$definition->addMethodCall('setHelp', [str_replace('%', '%%', $help)]);
114112
}
115113

114+
$description ??= str_replace('%', '%%', $class::getDefaultDescription() ?? '');
115+
116116
if ($description) {
117117
$definition->addMethodCall('setDescription', [$description]);
118118

src/Symfony/Component/Console/Tests/Command/CommandTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ public function testCommandAttribute()
434434

435435
$this->assertSame('foo', $command->getName());
436436
$this->assertSame('desc', $command->getDescription());
437+
$this->assertSame('help', $command->getHelp());
437438
$this->assertTrue($command->isHidden());
438439
$this->assertSame(['f'], $command->getAliases());
439440
}
@@ -473,7 +474,7 @@ function createClosure()
473474
};
474475
}
475476

476-
#[AsCommand(name: 'foo', description: 'desc', hidden: true, aliases: ['f'])]
477+
#[AsCommand(name: 'foo', description: 'desc', hidden: true, aliases: ['f'], help: 'help')]
477478
class Php8Command extends Command
478479
{
479480
}

src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public function testEscapesDefaultFromPhp()
176176
$this->assertSame('%cmd%', $command->getName());
177177
$this->assertSame(['%cmdalias%'], $command->getAliases());
178178
$this->assertSame('Creates a 80% discount', $command->getDescription());
179+
$this->assertSame('The %command.name% help content.', $command->getHelp());
179180
}
180181

181182
public function testProcessThrowAnExceptionIfTheServiceIsAbstract()
@@ -310,12 +311,19 @@ public function testProcessInvokableCommand()
310311
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
311312

312313
$definition = new Definition(InvokableCommand::class);
313-
$definition->addTag('console.command', ['command' => 'invokable', 'description' => 'Just testing']);
314+
$definition->addTag('console.command', [
315+
'command' => 'invokable',
316+
'description' => 'The command description',
317+
'help' => 'The %command.name% command help content.',
318+
]);
314319
$container->setDefinition('invokable_command', $definition);
315320

316321
$container->compile();
322+
$command = $container->get('console.command_loader')->get('invokable');
317323

318324
self::assertTrue($container->has('invokable_command.command'));
325+
self::assertSame('The command description', $command->getDescription());
326+
self::assertSame('The %command.name% command help content.', $command->getHelp());
319327
}
320328
}
321329

@@ -328,7 +336,7 @@ class NamedCommand extends Command
328336
{
329337
}
330338

331-
#[AsCommand(name: '%cmd%|%cmdalias%', description: 'Creates a 80% discount')]
339+
#[AsCommand(name: '%cmd%|%cmdalias%', description: 'Creates a 80% discount', help: 'The %command.name% help content.')]
332340
class EscapedDefaultsFromPhpCommand extends Command
333341
{
334342
}
@@ -346,7 +354,7 @@ public function __construct()
346354
}
347355
}
348356

349-
#[AsCommand(name: 'invokable', description: 'Just testing')]
357+
#[AsCommand(name: 'invokable', description: 'Just testing', help: 'The %command.name% help content.')]
350358
class InvokableCommand
351359
{
352360
public function __invoke(): void

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy