Skip to content

Commit e4bf0cc

Browse files
committed
Rollback all workaround from #23 and go all-in with SignalableCommandInterface
1 parent a937917 commit e4bf0cc

File tree

6 files changed

+73
-213
lines changed

6 files changed

+73
-213
lines changed

phpunit.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
33
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
4-
bootstrap="tests/bootstrap.php"
4+
bootstrap="vendor/autoload.php"
55
executionOrder="depends,defects"
66
cacheResult="false"
77
colors="true"

psalm.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<directory name="src" />
1111
<ignoreFiles>
1212
<directory name="vendor" />
13-
<file name="src/AbstractTerminableCommandAfterSymfony7_3.php" />
1413
</ignoreFiles>
1514
</projectFiles>
1615
</psalm>

src/AbstractTerminableCommand.php

Lines changed: 72 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,113 +9,102 @@
99
use Symfony\Component\Console\Input\InputInterface;
1010
use Symfony\Component\Console\Output\OutputInterface;
1111

12-
if (
13-
PHP_VERSION_ID >= 8_02_00
14-
&& interface_exists(SignalableCommandInterface::class)
15-
&& in_array(
16-
SignalableCommandInterface::class,
17-
class_implements(Command::class),
18-
true
19-
)
20-
) {
21-
require_once __DIR__ . '/AbstractTerminableCommandAfterSymfony7_3.php';
22-
} else {
23-
abstract class AbstractTerminableCommand extends Command
24-
{
25-
private const REQUEST_TO_TERMINATE = 143;
26-
27-
/** @var int */
28-
private $sleepDuration;
29-
30-
/** @var bool */
31-
private $signalShutdownRequested;
12+
abstract class AbstractTerminableCommand extends Command implements SignalableCommandInterface
13+
{
14+
private const REQUEST_TO_TERMINATE = 143;
3215

33-
public function __construct(?string $name = null)
34-
{
35-
$this->sleepDuration = 0;
36-
$this->signalShutdownRequested = false;
16+
/** @var int */
17+
private $sleepDuration;
3718

38-
parent::__construct($name);
39-
}
19+
/** @var bool */
20+
private $signalShutdownRequested;
4021

41-
final protected function execute(InputInterface $input, OutputInterface $output): int
42-
{
43-
$this->trapSignals();
22+
public function __construct(?string $name = null)
23+
{
24+
$this->sleepDuration = 0;
25+
$this->signalShutdownRequested = false;
4426

45-
$output->writeln('Starting ' . ($this->getName() ?? static::class), OutputInterface::VERBOSITY_VERBOSE);
27+
parent::__construct($name);
28+
}
4629

47-
if ($this->signalShutdownRequested) {
48-
$output->writeln('Signal received, skipping execution', OutputInterface::VERBOSITY_NORMAL);
30+
final protected function execute(InputInterface $input, OutputInterface $output): int
31+
{
32+
$output->writeln('Starting ' . ($this->getName() ?? static::class), OutputInterface::VERBOSITY_VERBOSE);
4933

50-
return self::REQUEST_TO_TERMINATE;
51-
}
34+
if ($this->signalShutdownRequested) {
35+
$output->writeln('Signal received, skipping execution', OutputInterface::VERBOSITY_NORMAL);
5236

53-
$exitCode = $this->commandBody($input, $output);
37+
return self::REQUEST_TO_TERMINATE;
38+
}
5439

55-
$this->sleep($output);
40+
$exitCode = $this->commandBody($input, $output);
5641

57-
/** @psalm-suppress DocblockTypeContradiction */
58-
if ($this->signalShutdownRequested) {
59-
$output->writeln('Signal received, terminating with exit code ' . self::REQUEST_TO_TERMINATE, OutputInterface::VERBOSITY_NORMAL);
42+
$this->sleep($output);
6043

61-
return self::REQUEST_TO_TERMINATE;
62-
}
44+
/** @psalm-suppress DocblockTypeContradiction */
45+
if ($this->signalShutdownRequested) {
46+
$output->writeln('Signal received, terminating with exit code ' . self::REQUEST_TO_TERMINATE, OutputInterface::VERBOSITY_NORMAL);
6347

64-
return $exitCode;
48+
return self::REQUEST_TO_TERMINATE;
6549
}
6650

67-
abstract protected function commandBody(InputInterface $input, OutputInterface $output): int;
68-
69-
public function handleSignal(int $signal): void
70-
{
71-
switch ($signal) {
72-
// Shutdown signals
73-
case SIGTERM:
74-
case SIGINT:
75-
$this->signalShutdownRequested = true;
76-
break;
77-
}
78-
}
51+
return $exitCode;
52+
}
7953

80-
private function trapSignals(): void
81-
{
82-
pcntl_async_signals(true);
54+
abstract protected function commandBody(InputInterface $input, OutputInterface $output): int;
8355

84-
// Add the signal handler
85-
pcntl_signal(SIGTERM, [$this, 'handleSignal']);
86-
pcntl_signal(SIGINT, [$this, 'handleSignal']);
56+
public function handleSignal(int $signal, int|false $previousExitCode = 0): false
57+
{
58+
switch ($signal) {
59+
// Shutdown signals
60+
case SIGTERM:
61+
case SIGINT:
62+
$this->signalShutdownRequested = true;
8763
}
8864

89-
protected function getSleepDuration(): int
90-
{
91-
return $this->sleepDuration;
92-
}
65+
return false;
66+
}
67+
68+
/**
69+
* @return list<int>
70+
*/
71+
public function getSubscribedSignals(): array
72+
{
73+
return [
74+
SIGTERM,
75+
SIGINT,
76+
];
77+
}
9378

94-
protected function setSleepDuration(int $sleepDuration): void
95-
{
96-
if ($sleepDuration < 0) {
97-
throw new \InvalidArgumentException('Invalid timeout provided to ' . __METHOD__);
98-
}
79+
protected function getSleepDuration(): int
80+
{
81+
return $this->sleepDuration;
82+
}
9983

100-
$this->sleepDuration = $sleepDuration;
84+
protected function setSleepDuration(int $sleepDuration): void
85+
{
86+
if ($sleepDuration < 0) {
87+
throw new \InvalidArgumentException('Invalid timeout provided to ' . __METHOD__);
10188
}
10289

103-
private function sleep(OutputInterface $output): void
104-
{
105-
if (0 === $this->sleepDuration) {
106-
return;
107-
}
90+
$this->sleepDuration = $sleepDuration;
91+
}
10892

109-
$sleepCountDown = $this->sleepDuration;
93+
private function sleep(OutputInterface $output): void
94+
{
95+
if (0 === $this->sleepDuration) {
96+
return;
97+
}
11098

111-
while (! $this->signalShutdownRequested && --$sleepCountDown) {
112-
sleep(1);
113-
}
99+
$sleepCountDown = $this->sleepDuration;
114100

115-
$output->writeln(
116-
sprintf('Slept %d second(s)', $this->sleepDuration - $sleepCountDown),
117-
OutputInterface::VERBOSITY_DEBUG
118-
);
101+
while (! $this->signalShutdownRequested && --$sleepCountDown) {
102+
sleep(1);
119103
}
104+
105+
$output->writeln(
106+
sprintf('Slept %d second(s)', $this->sleepDuration - $sleepCountDown),
107+
OutputInterface::VERBOSITY_DEBUG
108+
);
120109
}
121110
}

src/AbstractTerminableCommandAfterSymfony7_3.php

Lines changed: 0 additions & 112 deletions
This file was deleted.

tests/Unit/AbstractTerminableCommandTest.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use Prophecy\Argument;
1010
use Prophecy\PhpUnit\ProphecyTrait;
1111
use Symfony\Bridge\PhpUnit\ClockMock;
12-
use Symfony\Component\Console\Command\SignalableCommandInterface;
1312
use Symfony\Component\Console\Input\ArrayInput;
1413
use Symfony\Component\Console\Input\InputInterface;
1514
use Symfony\Component\Console\Output\OutputInterface;
@@ -141,10 +140,6 @@ public function testGetSubscribedSignals(int $signal): void
141140
{
142141
$stubCommand = $this->createStubTerminableCommand();
143142

144-
if (! interface_exists(SignalableCommandInterface::class) || ! $stubCommand instanceof SignalableCommandInterface) {
145-
$this->markTestSkipped('This test requires the Symfony 7.3+ implementation');
146-
}
147-
148143
$this->assertContains($signal, $stubCommand->getSubscribedSignals(), 'Signal not subscribed to');
149144
}
150145

tests/bootstrap.php

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)