Skip to content

Commit 6ffff9d

Browse files
committed
Duplicate class to fix support for Symfony 7.3
1 parent 2f4e269 commit 6ffff9d

3 files changed

Lines changed: 194 additions & 70 deletions

File tree

psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<directory name="src" />
1111
<ignoreFiles>
1212
<directory name="vendor" />
13+
<file name="AbstractTerminableCommandAfterSymfony7_3.php" />
1314
</ignoreFiles>
1415
</projectFiles>
1516
</psalm>

src/AbstractTerminableCommand.php

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,104 +5,117 @@
55
namespace Facile\TerminableLoop;
66

77
use Symfony\Component\Console\Command\Command;
8+
use Symfony\Component\Console\Command\SignalableCommandInterface;
89
use Symfony\Component\Console\Input\InputInterface;
910
use Symfony\Component\Console\Output\OutputInterface;
1011

11-
abstract class AbstractTerminableCommand extends Command
12-
{
13-
private const REQUEST_TO_TERMINATE = 143;
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;
1426

15-
/** @var int */
16-
private $sleepDuration;
27+
/** @var int */
28+
private $sleepDuration;
1729

18-
/** @var bool */
19-
private $signalShutdownRequested;
30+
/** @var bool */
31+
private $signalShutdownRequested;
2032

21-
public function __construct(?string $name = null)
22-
{
23-
$this->sleepDuration = 0;
24-
$this->signalShutdownRequested = false;
33+
public function __construct(?string $name = null)
34+
{
35+
$this->sleepDuration = 0;
36+
$this->signalShutdownRequested = false;
2537

26-
parent::__construct($name);
27-
}
38+
parent::__construct($name);
39+
}
2840

29-
final protected function execute(InputInterface $input, OutputInterface $output): int
30-
{
31-
$this->trapSignals();
41+
final protected function execute(InputInterface $input, OutputInterface $output): int
42+
{
43+
$this->trapSignals();
3244

33-
$output->writeln('Starting ' . ($this->getName() ?? static::class), OutputInterface::VERBOSITY_VERBOSE);
45+
$output->writeln('Starting ' . ($this->getName() ?? static::class), OutputInterface::VERBOSITY_VERBOSE);
3446

35-
if ($this->signalShutdownRequested) {
36-
$output->writeln('Signal received, skipping execution', OutputInterface::VERBOSITY_NORMAL);
47+
if ($this->signalShutdownRequested) {
48+
$output->writeln('Signal received, skipping execution', OutputInterface::VERBOSITY_NORMAL);
3749

38-
return self::REQUEST_TO_TERMINATE;
39-
}
50+
return self::REQUEST_TO_TERMINATE;
51+
}
4052

41-
$exitCode = $this->commandBody($input, $output);
53+
$exitCode = $this->commandBody($input, $output);
4254

43-
$this->sleep($output);
55+
$this->sleep($output);
4456

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

49-
return self::REQUEST_TO_TERMINATE;
61+
return self::REQUEST_TO_TERMINATE;
62+
}
63+
64+
return $exitCode;
5065
}
5166

52-
return $exitCode;
53-
}
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+
}
5479

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

57-
public function handleSignal(int $signal): void
58-
{
59-
switch ($signal) {
60-
// Shutdown signals
61-
case SIGTERM:
62-
case SIGINT:
63-
$this->signalShutdownRequested = true;
64-
break;
84+
// Add the signal handler
85+
pcntl_signal(SIGTERM, [$this, 'handleSignal']);
86+
pcntl_signal(SIGINT, [$this, 'handleSignal']);
6587
}
66-
}
6788

68-
private function trapSignals(): void
69-
{
70-
pcntl_async_signals(true);
71-
72-
// Add the signal handler
73-
pcntl_signal(SIGTERM, [$this, 'handleSignal']);
74-
pcntl_signal(SIGINT, [$this, 'handleSignal']);
75-
}
89+
protected function getSleepDuration(): int
90+
{
91+
return $this->sleepDuration;
92+
}
7693

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

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

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

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

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

99-
while (! $this->signalShutdownRequested && --$sleepCountDown) {
100-
sleep(1);
115+
$output->writeln(
116+
sprintf('Slept %d second(s)', $this->sleepDuration - $sleepCountDown),
117+
OutputInterface::VERBOSITY_DEBUG
118+
);
101119
}
102-
103-
$output->writeln(
104-
sprintf('Slept %d second(s)', $this->sleepDuration - $sleepCountDown),
105-
OutputInterface::VERBOSITY_DEBUG
106-
);
107120
}
108121
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Facile\TerminableLoop;
6+
7+
use Symfony\Component\Console\Command\Command;
8+
use Symfony\Component\Console\Command\SignalableCommandInterface;
9+
use Symfony\Component\Console\Input\InputInterface;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
12+
abstract class AbstractTerminableCommand extends Command implements SignalableCommandInterface
13+
{
14+
private const REQUEST_TO_TERMINATE = 143;
15+
16+
/** @var int */
17+
private $sleepDuration;
18+
19+
/** @var bool */
20+
private $signalShutdownRequested;
21+
22+
public function __construct(?string $name = null)
23+
{
24+
$this->sleepDuration = 0;
25+
$this->signalShutdownRequested = false;
26+
27+
parent::__construct($name);
28+
}
29+
30+
final protected function execute(InputInterface $input, OutputInterface $output): int
31+
{
32+
$output->writeln('Starting ' . ($this->getName() ?? static::class), OutputInterface::VERBOSITY_VERBOSE);
33+
34+
if ($this->signalShutdownRequested) {
35+
$output->writeln('Signal received, skipping execution', OutputInterface::VERBOSITY_NORMAL);
36+
37+
return self::REQUEST_TO_TERMINATE;
38+
}
39+
40+
$exitCode = $this->commandBody($input, $output);
41+
42+
$this->sleep($output);
43+
44+
/** @psalm-suppress DocblockTypeContradiction */
45+
if ($this->signalShutdownRequested) {
46+
$output->writeln('Signal received, terminating with exit code ' . self::REQUEST_TO_TERMINATE, OutputInterface::VERBOSITY_NORMAL);
47+
48+
return self::REQUEST_TO_TERMINATE;
49+
}
50+
51+
return $exitCode;
52+
}
53+
54+
abstract protected function commandBody(InputInterface $input, OutputInterface $output): int;
55+
56+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
57+
{
58+
switch ($signal) {
59+
// Shutdown signals
60+
case SIGTERM:
61+
case SIGINT:
62+
$this->signalShutdownRequested = true;
63+
}
64+
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+
}
78+
79+
protected function getSleepDuration(): int
80+
{
81+
return $this->sleepDuration;
82+
}
83+
84+
protected function setSleepDuration(int $sleepDuration): void
85+
{
86+
if ($sleepDuration < 0) {
87+
throw new \InvalidArgumentException('Invalid timeout provided to ' . __METHOD__);
88+
}
89+
90+
$this->sleepDuration = $sleepDuration;
91+
}
92+
93+
private function sleep(OutputInterface $output): void
94+
{
95+
if (0 === $this->sleepDuration) {
96+
return;
97+
}
98+
99+
$sleepCountDown = $this->sleepDuration;
100+
101+
while (!$this->signalShutdownRequested && --$sleepCountDown) {
102+
sleep(1);
103+
}
104+
105+
$output->writeln(
106+
sprintf('Slept %d second(s)', $this->sleepDuration - $sleepCountDown),
107+
OutputInterface::VERBOSITY_DEBUG
108+
);
109+
}
110+
}

0 commit comments

Comments
 (0)