Skip to content

Commit 79bb9bd

Browse files
[Console] Make sure signals registered by SignalableCommandInterface commands are defined in the PCNTL extension
1 parent 38b5992 commit 79bb9bd

File tree

4 files changed

+68
-0
lines changed

4 files changed

+68
-0
lines changed

src/Symfony/Component/Console/Application.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2929
use Symfony\Component\Console\Exception\CommandNotFoundException;
3030
use Symfony\Component\Console\Exception\ExceptionInterface;
31+
use Symfony\Component\Console\Exception\InvalidArgumentException;
3132
use Symfony\Component\Console\Exception\LogicException;
3233
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
3334
use Symfony\Component\Console\Exception\RuntimeException;
@@ -1006,6 +1007,12 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
10061007
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
10071008
}
10081009

1010+
foreach (array_merge($commandSignals, $this->signalsToDispatchEvent) as $signal) {
1011+
if (!SignalRegistry::isValidSignal($signal)) {
1012+
throw new InvalidArgumentException(sprintf('Signal with code %d is not valid, make sure to use a signal defined in the `pcntl` extension by using a SIG* predefined constant.', $signal));
1013+
}
1014+
}
1015+
10091016
if (Terminal::hasSttyAvailable()) {
10101017
$sttyMode = shell_exec('stty -g');
10111018

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add support for choosing exit code while handling signal, or to not exit at all
88
* Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global.
99
* Add `ReStructuredTextDescriptor`
10+
* Make sure signals registered by `SignalableCommandInterface` commands are defined in the PCNTL extension
1011

1112
6.2
1213
---

src/Symfony/Component/Console/SignalRegistry/SignalRegistry.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ final class SignalRegistry
1515
{
1616
private array $signalHandlers = [];
1717

18+
private static ?array $availableSignals = null;
19+
1820
public function __construct()
1921
{
2022
if (\function_exists('pcntl_async_signals')) {
@@ -42,6 +44,15 @@ public static function isSupported(): bool
4244
return \function_exists('pcntl_signal');
4345
}
4446

47+
public static function isValidSignal(int $signal): bool
48+
{
49+
if (null === static::$availableSignals) {
50+
static::$availableSignals = get_defined_constants(true)['pcntl'];
51+
}
52+
53+
return \in_array($signal, static::$availableSignals, true);
54+
}
55+
4556
/**
4657
* @internal
4758
*/

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Console\Event\ConsoleSignalEvent;
2828
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2929
use Symfony\Component\Console\Exception\CommandNotFoundException;
30+
use Symfony\Component\Console\Exception\InvalidArgumentException;
3031
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
3132
use Symfony\Component\Console\Helper\FormatterHelper;
3233
use Symfony\Component\Console\Helper\HelperSet;
@@ -2137,6 +2138,37 @@ public function testSignalableWithEventCommandDoesNotInterruptedOnTermSignals()
21372138
$this->assertSame($expected, $tester->getDisplay(true));
21382139
}
21392140

2141+
/**
2142+
* @requires extension pcntl
2143+
*/
2144+
public function testSignalIsNotAvailableInPcntlExtension()
2145+
{
2146+
$this->expectException(InvalidArgumentException::class);
2147+
$this->expectExceptionMessage('Signal with code 40 is not valid, make sure to use a signal defined in the `pcntl` extension by using a SIG* predefined constant.');
2148+
2149+
$command = new InvalidSignalableCommand();
2150+
$application = $this->createSignalableApplication($command, null);
2151+
$application->setCatchExceptions(false);
2152+
2153+
$application->run(new ArrayInput(['signal']));
2154+
}
2155+
2156+
/**
2157+
* @requires extension pcntl
2158+
*/
2159+
public function testSignalToDispatchIsNotAvailableInPcntlExtension()
2160+
{
2161+
$this->expectException(InvalidArgumentException::class);
2162+
$this->expectExceptionMessage('Signal with code 40 is not valid, make sure to use a signal defined in the `pcntl` extension by using a SIG* predefined constant.');
2163+
2164+
$command = new SignableCommand();
2165+
$application = $this->createSignalableApplication($command, null);
2166+
$application->setSignalsToDispatchEvent(40);
2167+
$application->setCatchExceptions(false);
2168+
2169+
$application->run(new ArrayInput(['signal']));
2170+
}
2171+
21402172
/**
21412173
* @group tty
21422174
*/
@@ -2283,6 +2315,23 @@ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|
22832315
}
22842316
}
22852317

2318+
#[AsCommand(name: 'signal')]
2319+
class InvalidSignalableCommand extends BaseSignableCommand implements SignalableCommandInterface
2320+
{
2321+
public function getSubscribedSignals(): array
2322+
{
2323+
return [40];
2324+
}
2325+
2326+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
2327+
{
2328+
$this->signaled = true;
2329+
$this->signalHandlers[] = __CLASS__;
2330+
2331+
return false;
2332+
}
2333+
}
2334+
22862335
#[AsCommand(name: 'signal')]
22872336
class TerminatableCommand extends BaseSignableCommand implements SignalableCommandInterface
22882337
{

0 commit comments

Comments
 (0)