Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 00a39e7

Browse filesBrowse files
committed
feature #60389 [Console] Add support for SignalableCommandInterface with invokable commands (HypeMC)
This PR was merged into the 7.3 branch. Discussion ---------- [Console] Add support for `SignalableCommandInterface` with invokable commands | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Currently, it's not possible to use the `SignalableCommandInterface` with invokable commands. This PR adds support for that. ```php #[AsCommand(name: 'app:example')] class ExampleCommand implements SignalableCommandInterface { public function __invoke(InputInterface $input, OutputInterface $output): int { // ... return Command::SUCCESS; } public function getSubscribedSignals(): array { return [\SIGINT, \SIGTERM]; } public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false { // handle signal return 0; } } ``` Commits ------- bb97a2a [Console] Add support for `SignalableCommandInterface` with invokable commands
2 parents fc40d62 + bb97a2a commit 00a39e7
Copy full SHA for 00a39e7

File tree

10 files changed

+141
-21
lines changed
Filter options

10 files changed

+141
-21
lines changed

‎src/Symfony/Component/Console/Application.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Application.php
+1-3Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use Symfony\Component\Console\Command\HelpCommand;
1818
use Symfony\Component\Console\Command\LazyCommand;
1919
use Symfony\Component\Console\Command\ListCommand;
20-
use Symfony\Component\Console\Command\SignalableCommandInterface;
2120
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
2221
use Symfony\Component\Console\Completion\CompletionInput;
2322
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -1005,8 +1004,7 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
10051004
}
10061005
}
10071006

1008-
$commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
1009-
if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
1007+
if (($commandSignals = $command->getSubscribedSignals()) || $this->dispatcher && $this->signalsToDispatchEvent) {
10101008
$signalRegistry = $this->getSignalRegistry();
10111009

10121010
if (Terminal::hasSttyAvailable()) {

‎src/Symfony/Component/Console/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CHANGELOG
1414
* Add support for `LockableTrait` in invokable commands
1515
* Deprecate returning a non-integer value from a `\Closure` function set via `Command::setCode()`
1616
* Mark `#[AsCommand]` attribute as `@final`
17+
* Add support for `SignalableCommandInterface` with invokable commands
1718

1819
7.2
1920
---

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Command/Command.php
+11-1Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
*
3333
* @author Fabien Potencier <fabien@symfony.com>
3434
*/
35-
class Command
35+
class Command implements SignalableCommandInterface
3636
{
3737
// see https://tldp.org/LDP/abs/html/exitcodes.html
3838
public const SUCCESS = 0;
@@ -674,6 +674,16 @@ public function getHelper(string $name): HelperInterface
674674
return $this->helperSet->get($name);
675675
}
676676

677+
public function getSubscribedSignals(): array
678+
{
679+
return $this->code?->getSubscribedSignals() ?? [];
680+
}
681+
682+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
683+
{
684+
return $this->code?->handleSignal($signal, $previousExitCode) ?? false;
685+
}
686+
677687
/**
678688
* Validates a command name.
679689
*

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Command/InvokableCommand.php
+13-1Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828
*
2929
* @internal
3030
*/
31-
class InvokableCommand
31+
class InvokableCommand implements SignalableCommandInterface
3232
{
3333
private readonly \Closure $code;
34+
private readonly ?SignalableCommandInterface $signalableCommand;
3435
private readonly \ReflectionFunction $reflection;
3536
private bool $triggerDeprecations = false;
3637

@@ -39,6 +40,7 @@ public function __construct(
3940
callable $code,
4041
) {
4142
$this->code = $this->getClosure($code);
43+
$this->signalableCommand = $code instanceof SignalableCommandInterface ? $code : null;
4244
$this->reflection = new \ReflectionFunction($this->code);
4345
}
4446

@@ -142,4 +144,14 @@ private function getParameters(InputInterface $input, OutputInterface $output):
142144

143145
return $parameters ?: [$input, $output];
144146
}
147+
148+
public function getSubscribedSignals(): array
149+
{
150+
return $this->signalableCommand?->getSubscribedSignals() ?? [];
151+
}
152+
153+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
154+
{
155+
return $this->signalableCommand?->handleSignal($signal, $previousExitCode) ?? false;
156+
}
145157
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Command/TraceableCommand.php
+2-6Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
*
2828
* @author Jules Pietri <jules@heahprod.com>
2929
*/
30-
final class TraceableCommand extends Command implements SignalableCommandInterface
30+
final class TraceableCommand extends Command
3131
{
3232
public readonly Command $command;
3333
public int $exitCode;
@@ -89,15 +89,11 @@ public function __call(string $name, array $arguments): mixed
8989

9090
public function getSubscribedSignals(): array
9191
{
92-
return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : [];
92+
return $this->command->getSubscribedSignals();
9393
}
9494

9595
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
9696
{
97-
if (!$this->command instanceof SignalableCommandInterface) {
98-
return false;
99-
}
100-
10197
$event = $this->stopwatch->start($this->getName().'.handle_signal');
10298

10399
$exit = $this->command->handleSignal($signal, $previousExitCode);

‎src/Symfony/Component/Console/Tests/ApplicationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/ApplicationTest.php
+70-4Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,6 +2255,41 @@ public function testSignalableRestoresStty()
22552255
$this->assertSame($previousSttyMode, $sttyMode);
22562256
}
22572257

2258+
/**
2259+
* @requires extension pcntl
2260+
*/
2261+
public function testSignalableInvokableCommand()
2262+
{
2263+
$command = new Command();
2264+
$command->setName('signal-invokable');
2265+
$command->setCode($invokable = new class implements SignalableCommandInterface {
2266+
use SignalableInvokableCommandTrait;
2267+
});
2268+
2269+
$application = $this->createSignalableApplication($command, null);
2270+
$application->setSignalsToDispatchEvent(\SIGUSR1);
2271+
2272+
$this->assertSame(1, $application->run(new ArrayInput(['signal-invokable'])));
2273+
$this->assertTrue($invokable->signaled);
2274+
}
2275+
2276+
/**
2277+
* @requires extension pcntl
2278+
*/
2279+
public function testSignalableInvokableCommandThatExtendsBaseCommand()
2280+
{
2281+
$command = new class extends Command implements SignalableCommandInterface {
2282+
use SignalableInvokableCommandTrait;
2283+
};
2284+
$command->setName('signal-invokable');
2285+
2286+
$application = $this->createSignalableApplication($command, null);
2287+
$application->setSignalsToDispatchEvent(\SIGUSR1);
2288+
2289+
$this->assertSame(1, $application->run(new ArrayInput(['signal-invokable'])));
2290+
$this->assertTrue($command->signaled);
2291+
}
2292+
22582293
/**
22592294
* @requires extension pcntl
22602295
*/
@@ -2514,7 +2549,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
25142549
}
25152550

25162551
#[AsCommand(name: 'signal')]
2517-
class SignableCommand extends BaseSignableCommand implements SignalableCommandInterface
2552+
class SignableCommand extends BaseSignableCommand
25182553
{
25192554
public function getSubscribedSignals(): array
25202555
{
@@ -2531,7 +2566,7 @@ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|
25312566
}
25322567

25332568
#[AsCommand(name: 'signal')]
2534-
class TerminatableCommand extends BaseSignableCommand implements SignalableCommandInterface
2569+
class TerminatableCommand extends BaseSignableCommand
25352570
{
25362571
public function getSubscribedSignals(): array
25372572
{
@@ -2548,7 +2583,7 @@ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|
25482583
}
25492584

25502585
#[AsCommand(name: 'signal')]
2551-
class TerminatableWithEventCommand extends Command implements SignalableCommandInterface, EventSubscriberInterface
2586+
class TerminatableWithEventCommand extends Command implements EventSubscriberInterface
25522587
{
25532588
private bool $shouldContinue = true;
25542589
private OutputInterface $output;
@@ -2615,8 +2650,39 @@ public static function getSubscribedEvents(): array
26152650
}
26162651
}
26172652

2653+
trait SignalableInvokableCommandTrait
2654+
{
2655+
public bool $signaled = false;
2656+
2657+
public function __invoke(): int
2658+
{
2659+
posix_kill(posix_getpid(), \SIGUSR1);
2660+
2661+
for ($i = 0; $i < 1000; ++$i) {
2662+
usleep(100);
2663+
if ($this->signaled) {
2664+
return 1;
2665+
}
2666+
}
2667+
2668+
return 0;
2669+
}
2670+
2671+
public function getSubscribedSignals(): array
2672+
{
2673+
return SignalRegistry::isSupported() ? [\SIGUSR1] : [];
2674+
}
2675+
2676+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
2677+
{
2678+
$this->signaled = true;
2679+
2680+
return false;
2681+
}
2682+
}
2683+
26182684
#[AsCommand(name: 'alarm')]
2619-
class AlarmableCommand extends BaseSignableCommand implements SignalableCommandInterface
2685+
class AlarmableCommand extends BaseSignableCommand
26202686
{
26212687
public function __construct(private int $alarmInterval)
26222688
{

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Console\Attribute\AsCommand;
1616
use Symfony\Component\Console\Command\Command;
1717
use Symfony\Component\Console\Command\LazyCommand;
18+
use Symfony\Component\Console\Command\SignalableCommandInterface;
1819
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
1920
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
2021
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -325,6 +326,27 @@ public function testProcessInvokableCommand()
325326
self::assertSame('The command description', $command->getDescription());
326327
self::assertSame('The %command.name% command help content.', $command->getHelp());
327328
}
329+
330+
public function testProcessInvokableSignalableCommand()
331+
{
332+
$container = new ContainerBuilder();
333+
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
334+
335+
$definition = new Definition(InvokableSignalableCommand::class);
336+
$definition->addTag('console.command', [
337+
'command' => 'invokable-signalable',
338+
'description' => 'The command description',
339+
'help' => 'The %command.name% command help content.',
340+
]);
341+
$container->setDefinition('invokable_signalable_command', $definition);
342+
343+
$container->compile();
344+
$command = $container->get('console.command_loader')->get('invokable-signalable');
345+
346+
self::assertTrue($container->has('invokable_signalable_command.command'));
347+
self::assertSame('The command description', $command->getDescription());
348+
self::assertSame('The %command.name% command help content.', $command->getHelp());
349+
}
328350
}
329351

330352
class MyCommand extends Command
@@ -361,3 +383,21 @@ public function __invoke(): void
361383
{
362384
}
363385
}
386+
387+
#[AsCommand(name: 'invokable-signalable', description: 'Just testing', help: 'The %command.name% help content.')]
388+
class InvokableSignalableCommand implements SignalableCommandInterface
389+
{
390+
public function __invoke(): void
391+
{
392+
}
393+
394+
public function getSubscribedSignals(): array
395+
{
396+
return [];
397+
}
398+
399+
public function handleSignal(int $signal, false|int $previousExitCode = 0): int|false
400+
{
401+
return false;
402+
}
403+
}

‎src/Symfony/Component/Console/Tests/Fixtures/application_signalable.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Fixtures/application_signalable.php
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<?php
22

3-
use Symfony\Component\Console\Command\SignalableCommandInterface;
43
use Symfony\Component\Console\Input\InputInterface;
54
use Symfony\Component\Console\Output\OutputInterface;
65
use Symfony\Component\Console\Question\ChoiceQuestion;
@@ -12,7 +11,7 @@
1211
}
1312
require $vendor.'/vendor/autoload.php';
1413

15-
(new class extends SingleCommandApplication implements SignalableCommandInterface {
14+
(new class extends SingleCommandApplication {
1615
public function getSubscribedSignals(): array
1716
{
1817
return [SIGINT];

‎src/Symfony/Component/Console/Tests/phpt/alarm/command_exit.phpt

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/phpt/alarm/command_exit.phpt
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Test command that exits
77

88
use Symfony\Component\Console\Application;
99
use Symfony\Component\Console\Command\Command;
10-
use Symfony\Component\Console\Command\SignalableCommandInterface;
1110
use Symfony\Component\Console\Helper\QuestionHelper;
1211
use Symfony\Component\Console\Input\InputInterface;
1312
use Symfony\Component\Console\Output\OutputInterface;
@@ -19,7 +18,7 @@ while (!file_exists($vendor.'/vendor')) {
1918
}
2019
require $vendor.'/vendor/autoload.php';
2120

22-
class MyCommand extends Command implements SignalableCommandInterface
21+
class MyCommand extends Command
2322
{
2423
protected function initialize(InputInterface $input, OutputInterface $output): void
2524
{

‎src/Symfony/Component/Console/Tests/phpt/signal/command_exit.phpt

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/phpt/signal/command_exit.phpt
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Test command that exits
77

88
use Symfony\Component\Console\Application;
99
use Symfony\Component\Console\Command\Command;
10-
use Symfony\Component\Console\Command\SignalableCommandInterface;
1110
use Symfony\Component\Console\Helper\QuestionHelper;
1211
use Symfony\Component\Console\Input\InputInterface;
1312
use Symfony\Component\Console\Output\OutputInterface;
@@ -19,7 +18,7 @@ while (!file_exists($vendor.'/vendor')) {
1918
}
2019
require $vendor.'/vendor/autoload.php';
2120

22-
class MyCommand extends Command implements SignalableCommandInterface
21+
class MyCommand extends Command
2322
{
2423
protected function execute(InputInterface $input, OutputInterface $output): int
2524
{

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.