10000 [Console] Add support for `SignalableCommandInterface` with invokable… · symfony/symfony@9767999 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9767999

Browse files
committed
[Console] Add support for SignalableCommandInterface with invokable commands
1 parent 72c0a36 commit 9767999

File tree

5 files changed

+144
-1
lines changed

5 files changed

+144
-1
lines changed

src/Symfony/Component/Console/CHANGELOG.md

Lines 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
---
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Command;
13+
14+
use Symfony\Component\Console\Exception\InvalidArgumentException;
15+
use Symfony\Component\Console\Exception\LogicException;
16+
17+
/**
18+
* @internal
19+
*/
20+
class SignalableCommand extends Command implements SignalableCommandInterface
21+
{
22+
private ?SignalableCommandInterface $code = null;
23+
24+
public function setCode(callable $code): static
25+
{
26+
if (!$code instanceof SignalableCommandInterface) {
27+
throw new InvalidArgumentException(\sprintf('The callable must be an object that implements the "%s" interface.', SignalableCommandInterface::class));
28+
}
29+
30+
$this->code = $code;
31+
32+
return parent::setCode($code);
33+
}
34+
35+
public function getSubscribedSignals(): array
36+
{
37+
if (null === $this->code) {
38+
throw new LogicException('A callable must be set using the setCode() method.');
39+
}
40+
41+
return $this->code->getSubscribedSignals();
42+
}
43+
44+
public function handleSignal(int $signal, false|int $previousExitCode = 0): int|false
45+
{
46+
if (null === $this->code) {
47+
throw new LogicException('A callable must be set using the setCode() method.');
48+
}
49+
50+
return $this->code->handleSignal($signal, $previousExitCode);
51+
}
52+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Component\Console\Attribute\AsCommand;
1515
use Symfony\Component\Console\Command\Command;
1616
use Symfony\Component\Console\Command\LazyCommand;
17+
use Symfony\Component\Console\Command\SignalableCommand;
18+
use Symfony\Component\Console\Command\SignalableCommandInterface;
1719
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
1820
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1921
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -51,7 +53,7 @@ public function process(ContainerBuilder $container): void
5153
}
5254

5355
$invokableRef = new Reference($id);
54-
$definition = $container->register($id .= '.command', $class = Command::class)
56+
$definition = $container->register($id .= '.command', $class = $r->implementsInterface(SignalableCommandInterface::class) ? SignalableCommand::class : Command::class)
5557
->addMethodCall('setCode', [$invokableRef]);
5658
} else {
5759
$invokableRef = null;

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Console\Command\Command;
1818
use Symfony\Component\Console\Command\HelpCommand;
1919
use Symfony\Component\Console\Command\LazyCommand;
20+
use Symfony\Component\Console\Command\SignalableCommand;
2021
use Symfony\Component\Console\Command\SignalableCommandInterface;
2122
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
2223
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
@@ -2255,6 +2256,50 @@ public function testSignalableRestoresStty()
22552256
$this->assertSame($previousSttyMode, $sttyMode);
22562257
}
22572258

2259+
/**
2260+
* @requires extension pcntl
2261+
*/
2262+
public function testSignalableInvokableCommand()
2263+
{
2264+
$command = new SignalableCommand();
2265+
$command->setName('signal-invokable');
2266+
$command->setCode($invokable = new class implements SignalableCommandInterface {
2267+
public bool $signaled = false;
2268+
2269+
public function __invoke(): int
2270+
{
2271+
posix_kill(posix_getpid(), \SIGUSR1);
2272+
2273+
for ($i = 0; $i < 1000; ++$i) {
2274+
usleep(100);
2275+
if ($this->signaled) {
2276+
return 1;
2277+
}
2278+
}
2279+
2280+
return 0;
2281+
}
2282+
2283+
public function getSubscribedSignals(): array
2284+
{
2285+
return SignalRegistry::isSupported() ? [\SIGUSR1] : [];
2286+
}
2287+
2288+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
2289+
{
2290+
$this->signaled = true;
2291+
2292+
return false;
2293+
}
2294+
});
2295+
2296+
$application = $this->createSignalableApplication($command, null);
2297+
$application->setSignalsToDispatchEvent(\SIGUSR1);
2298+
2299+
$this->assertSame(1, $application->run(new ArrayInput(['signal-invokable'])));
2300+
$this->assertTrue($invokable->signaled);
2301+
}
2302+
22582303
/**
22592304
* @requires extension pcntl
22602305
*/

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
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\SignalableCommand;
19+
use Symfony\Component\Console\Command\SignalableCommandInterface;
1820
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
1921
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
2022
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -322,6 +324,29 @@ public function testProcessInvokableCommand()
322324
$command = $container->get('console.command_loader')->get('invokable');
323325

324326
self::assertTrue($container->has('invokable_command.command'));
327+
self::assertSame(Command::class, $container->getDefinition('invokable_command.command')->getClass());
328+
self::assertSame('The command description', $command->getDescription());
329+
self::assertSame('The %command.name% command help content.', $command->getHelp());
330+
}
331+
332+
public function testProcessInvokableSignalableCommand()
333+
{
334+
$container = new ContainerBuilder();
335+
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
336+
337+
$definition = new Definition(InvokableSignalableCommand::class);
338+
$definition->addTag('console.command', [
339+
'command' => 'invokable-signalable',
340+
'description' => 'The command description',
341+
'help' => 'The %command.name% command help content.',
342+
]);
343+
$container->setDefinition('invokable_signalable_command', $definition);
344+
345+
$container->compile();
346+
$command = $container->get('console.command_loader')->get('invokable-signalable');
347+
348+
self::assertTrue($container->has('invokable_signalable_command.command'));
349+
self::assertSame(SignalableCommand::class, $container->getDefinition('invokable_signalable_command.command')->getClass());
325350
self::assertSame('The command description', $command->getDescription());
326351
self::assertSame('The %command.name% command help content.', $command->getHelp());
327352
}
@@ -361,3 +386,21 @@ public function __invoke(): void
361386
{
362387
}
363388
}
389+
390+
#[AsCommand(name: 'invokable-signalable', description: 'Just testing', help: 'The %command.name% help content.')]
391+
class InvokableSignalableCommand implements SignalableCommandInterface
392+
{
393+
public function __invoke(): void
394+
{
395+
}
396+
397+
public function getSubscribedSignals(): array
398+
{
399+
return [];
400+
}
401+
402+
public function handleSignal(int $signal, false|int $previousExitCode = 0): int|false
403+
{
404+
return false;
405+
}
406+
}

0 commit comments

Comments
 (0)
0