Skip to content

Commit 063f49d

Browse files
mariechalasr
authored andcommitted
[Console] add console.signal event
1 parent 6a6f478 commit 063f49d

File tree

6 files changed

+253
-0
lines changed

6 files changed

+253
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Config\ResourceCheckerConfigCacheFactory;
1717
use Symfony\Component\Console\Event\ConsoleCommandEvent;
1818
use Symfony\Component\Console\Event\ConsoleErrorEvent;
19+
use Symfony\Component\Console\Event\ConsoleSignalEvent;
1920
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2021
use Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker;
2122
use Symfony\Component\DependencyInjection\EnvVarProcessor;
@@ -68,6 +69,7 @@
6869
ConsoleCommandEvent::class => 'console.command',
6970
ConsoleErrorEvent::class => 'console.error',
7071
ConsoleTerminateEvent::class => 'console.terminate',
72+
ConsoleSignalEvent::class => 'console.signal',
7173
PreSubmitEvent::class => 'form.pre_submit',
7274
SubmitEvent::class => 'form.submit',
7375
PostSubmitEvent::class => 'form.post_submit',

src/Symfony/Component/Console/Application.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
1818
use Symfony\Component\Console\Event\ConsoleCommandEvent;
1919
use Symfony\Component\Console\Event\ConsoleErrorEvent;
20+
use Symfony\Component\Console\Event\ConsoleSignalEvent;
2021
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2122
use Symfony\Component\Console\Exception\CommandNotFoundException;
2223
use Symfony\Component\Console\Exception\ExceptionInterface;
@@ -39,6 +40,7 @@
3940
use Symfony\Component\Console\Output\ConsoleOutput;
4041
use Symfony\Component\Console\Output\ConsoleOutputInterface;
4142
use Symfony\Component\Console\Output\OutputInterface;
43+
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
4244
use Symfony\Component\Console\Style\SymfonyStyle;
4345
use Symfony\Component\ErrorHandler\ErrorHandler;
4446
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -76,6 +78,7 @@ class Application implements ResetInterface
7678
private $defaultCommand;
7779
private $singleCommand = false;
7880
private $initialized;
81+
private $signalRegistry;
7982

8083
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
8184
{
@@ -98,6 +101,11 @@ public function setCommandLoader(CommandLoaderInterface $commandLoader)
98101
$this->commandLoader = $commandLoader;
99102
}
100103

104+
public function setSignalRegistry(SignalRegistry $signalRegistry)
105+
{
106+
$this->signalRegistry = $signalRegistry;
107+
}
108+
101109
/**
102110
* Runs the current application.
103111
*
@@ -260,6 +268,17 @@ public function doRun(InputInterface $input, OutputInterface $output)
260268
$command = $this->find($alternative);
261269
}
262270

271+
if ($this->signalRegistry) {
272+
foreach ($this->signalRegistry->getHandlingSignals() as $handlingSignal) {
273+
$event = new ConsoleSignalEvent($command, $input, $output, $handlingSignal);
274+
$onSignalHandler = function () use ($event) {
275+
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
276+
};
277+
278+
$this->signalRegistry->register($handlingSignal, $onSignalHandler);
279+
}
280+
}
281+
263282
$this->runningCommand = $command;
264283
$exitCode = $this->doRunCommand($command, $input, $output);
265284
$this->runningCommand = null;

src/Symfony/Component/Console/ConsoleEvents.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ final class ConsoleEvents
2727
*/
2828
const COMMAND = 'console.command';
2929

30+
/**
31+
* The SIGNAL event allows you to perform some actions
32+
* after the command execution was interrupted.
33+
*
34+
* @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
35+
*/
36+
const SIGNAL = 'console.signal';
37+
3038
/**
3139
* The TERMINATE event allows you to attach listeners after a command is
3240
* executed by the console.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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\Event;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
18+
/**
19+
* @author marie <marie@users.noreply.github.com>
20+
*/
21+
final class ConsoleSignalEvent extends ConsoleEvent
22+
{
23+
private $handlingSignal;
24+
25+
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
26+
{
27+
parent::__construct($command, $input, $output);
28+
$this->handlingSignal = $handlingSignal;
29+
}
30+
31+
public function getHandlingSignal(): int
32+
{
33+
return $this->handlingSignal;
34+
}
35+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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\SignalRegistry;
13+
14+
final class SignalRegistry
15+
{
16+
private $registeredSignals = [];
17+
18+
private $handlingSignals = [];
19+
20+
public function __construct()
21+
{
22+
pcntl_async_signals(true);
23+
}
24+
25+
public function register(int $signal, callable $signalHandler): void
26+
{
27+
if (!isset($this->registeredSignals[$signal])) {
28+
$previousCallback = pcntl_signal_get_handler($signal);
29+
30+
if (\is_callable($previousCallback)) {
31+
$this->registeredSignals[$signal][] = $previousCallback;
32+
}
33+
}
34+
35+
$this->registeredSignals[$signal][] = $signalHandler;
36+
pcntl_signal($signal, [$this, 'handle']);
37+
}
38+
39+
/**
40+
* @internal
41+
*/
42+
public function handle(int $signal): void
43+
{
44+
foreach ($this->registeredSignals[$signal] as $signalHandler) {
45+
$signalHandler($signal);
46+
}
47+
}
48+
49+
public function addHandlingSignals(int ...$signals): void
50+
{
51+
foreach ($signals as $signal) {
52+
$this->handlingSignals[$signal] = true;
53+
}
54+
}
55+
56+
public function getHandlingSignals(): array
57+
{
58+
return array_keys($this->handlingSignals);
59+
}
60+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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\Tests\SignalRegistry;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
16+
17+
/**
18+
* @requires extension pcntl
19+
*/
20+
class SignalRegistryTest extends TestCase
21+
{
22+
public function tearDown(): void
23+
{
24+
pcntl_async_signals(false);
25+
pcntl_signal(SIGUSR1, SIG_DFL);
26+
pcntl_signal(SIGUSR2, SIG_DFL);
27+
}
28+
29+
public function testOneCallbackForASignal_signalIsHandled()
30+
{
31+
$signalRegistry = new SignalRegistry();
32+
33+
$isHandled = false;
34+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled) {
35+
$isHandled = true;
36+
});
37+
38+
posix_kill(posix_getpid(), SIGUSR1);
39+
40+
$this->assertTrue($isHandled);
41+
}
42+
43+
public function testTwoCallbacksForASignal_bothCallbacksAreCalled()
44+
{
45+
$signalRegistry = new SignalRegistry();
46+
47+
$isHandled1 = false;
48+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled1) {
49+
$isHandled1 = true;
50+
});
51+
52+
$isHandled2 = false;
53+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled2) {
54+
$isHandled2 = true;
55+
});
56+
57+
posix_kill(posix_getpid(), SIGUSR1);
58+
59+
$this->assertTrue($isHandled1);
60+
$this->assertTrue($isHandled2);
61+
}
62+
63+
public function testTwoSignals_signalsAreHandled()
64+
{
65+
$signalRegistry = new SignalRegistry();
66+
67+
$isHandled1 = false;
68+
$isHandled2 = false;
69+
70+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled1) {
71+
$isHandled1 = true;
72+
});
73+
74+
posix_kill(posix_getpid(), SIGUSR1);
75+
76+
$this->assertTrue($isHandled1);
77+
$this->assertFalse($isHandled2);
78+
79+
$signalRegistry->register(SIGUSR2, function () use (&$isHandled2) {
80+
$isHandled2 = true;
81+
});
82+
83+
posix_kill(posix_getpid(), SIGUSR2);
84+
85+
$this->assertTrue($isHandled2);
86+
}
87+
88+
public function testTwoCallbacksForASignal_previousAndRegisteredCallbacksWereCalled()
89+
{
90+
$signalRegistry = new SignalRegistry();
91+
92+
$isHandled1 = false;
93+
pcntl_signal(SIGUSR1, function () use (&$isHandled1) {
94+
$isHandled1 = true;
95+
});
96+
97+
$isHandled2 = false;
98+
$signalRegistry->register(SIGUSR1, function () use (&$isHandled2) {
99+
$isHandled2 = true;
100+
});
101+
102+
posix_kill(posix_getpid(), SIGUSR1);
103+
104+
$this->assertTrue($isHandled1);
105+
$this->assertTrue($isHandled2);
106+
}
107+
108+
public function testTwoCallbacksForASignal_previousCallbackFromAnotherRegistry()
109+
{
110+
$signalRegistry1 = new SignalRegistry();
111+
112+
$isHandled1 = false;
113+
$signalRegistry1->register(SIGUSR1, function () use (&$isHandled1) {
114+
$isHandled1 = true;
115+
});
116+
117+
$signalRegistry2 = new SignalRegistry();
118+
119+
$isHandled2 = false;
120+
$signalRegistry2->register(SIGUSR1, function () use (&$isHandled2) {
121+
$isHandled2 = true;
122+
});
123+
124+
posix_kill(posix_getpid(), SIGUSR1);
125+
126+
$this->assertTrue($isHandled1);
127+
$this->assertTrue($isHandled2);
128+
}
129+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy