diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php index 4df97ed19c531..2f6d14cd650ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php @@ -15,6 +15,10 @@ use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; use Symfony\Component\Config\ResourceCheckerConfigCacheFactory; use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker; use Symfony\Component\DependencyInjection\EnvVarProcessor; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index f3914bb788ba8..d8ba39fd8f40b 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Exception\ExceptionInterface; @@ -39,6 +40,7 @@ use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\SignalRegistry\SignalRegistry; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -76,6 +78,7 @@ class Application implements ResetInterface private $defaultCommand; private $singleCommand = false; private $initialized; + private $signalRegistry; public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') { @@ -98,6 +101,11 @@ public function setCommandLoader(CommandLoaderInterface $commandLoader) $this->commandLoader = $commandLoader; } + public function setSignalRegistry(SignalRegistry $signalRegistry) + { + $this->signalRegistry = $signalRegistry; + } + /** * Runs the current application. * @@ -260,6 +268,17 @@ public function doRun(InputInterface $input, OutputInterface $output) $command = $this->find($alternative); } + if ($this->signalRegistry) { + foreach ($this->signalRegistry->getHandlingSignals() as $handlingSignal) { + $event = new ConsoleSignalEvent($command, $input, $output, $handlingSignal); + $onSignalHandler = function () use ($event) { + $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); + }; + + $this->signalRegistry->register($handlingSignal, $onSignalHandler); + } + } + $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; diff --git a/src/Symfony/Component/Console/ConsoleEvents.php b/src/Symfony/Component/Console/ConsoleEvents.php index 2c1bb46cdeefb..ac0d7da30101a 100644 --- a/src/Symfony/Component/Console/ConsoleEvents.php +++ b/src/Symfony/Component/Console/ConsoleEvents.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleSignalEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; /** @@ -31,6 +32,14 @@ final class ConsoleEvents */ const COMMAND = 'console.command'; + /** + * The SIGNAL event allows you to perform some actions + * after the command execution was interrupted. + * + * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent") + */ + const SIGNAL = 'console.signal'; + /** * The TERMINATE event allows you to attach listeners after a command is * executed by the console. @@ -57,6 +66,7 @@ final class ConsoleEvents const ALIASES = [ ConsoleCommandEvent::class => self::COMMAND, ConsoleErrorEvent::class => self::ERROR, + ConsoleSignalEvent::class => 'console.signal', ConsoleTerminateEvent::class => self::TERMINATE, ]; } diff --git a/src/Symfony/Component/Console/Event/ConsoleSignalEvent.php b/src/Symfony/Component/Console/Event/ConsoleSignalEvent.php new file mode 100644 index 0000000000000..ef13ed2f5d0b2 --- /dev/null +++ b/src/Symfony/Component/Console/Event/ConsoleSignalEvent.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author marie + */ +final class ConsoleSignalEvent extends ConsoleEvent +{ + private $handlingSignal; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal) + { + parent::__construct($command, $input, $output); + $this->handlingSignal = $handlingSignal; + } + + public function getHandlingSignal(): int + { + return $this->handlingSignal; + } +} diff --git a/src/Symfony/Component/Console/SignalRegistry/SignalRegistry.php b/src/Symfony/Component/Console/SignalRegistry/SignalRegistry.php new file mode 100644 index 0000000000000..c8114f29f84cc --- /dev/null +++ b/src/Symfony/Component/Console/SignalRegistry/SignalRegistry.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\SignalRegistry; + +final class SignalRegistry +{ + private $registeredSignals = []; + + private $handlingSignals = []; + + public function __construct() + { + pcntl_async_signals(true); + } + + public function register(int $signal, callable $signalHandler): void + { + if (!isset($this->registeredSignals[$signal])) { + $previousCallback = pcntl_signal_get_handler($signal); + + if (\is_callable($previousCallback)) { + $this->registeredSignals[$signal][] = $previousCallback; + } + } + + $this->registeredSignals[$signal][] = $signalHandler; + pcntl_signal($signal, [$this, 'handle']); + } + + /** + * @internal + */ + public function handle(int $signal): void + { + foreach ($this->registeredSignals[$signal] as $signalHandler) { + $signalHandler($signal); + } + } + + public function addHandlingSignals(int ...$signals): void + { + foreach ($signals as $signal) { + $this->handlingSignals[$signal] = true; + } + } + + public function getHandlingSignals(): array + { + return array_keys($this->handlingSignals); + } +} diff --git a/src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php b/src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php new file mode 100644 index 0000000000000..995b27bc0b0de --- /dev/null +++ b/src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\SignalRegistry; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\SignalRegistry\SignalRegistry; + +/** + * @requires extension pcntl + */ +class SignalRegistryTest extends TestCase +{ + public function tearDown(): void + { + pcntl_async_signals(false); + pcntl_signal(SIGUSR1, SIG_DFL); + pcntl_signal(SIGUSR2, SIG_DFL); + } + + public function testOneCallbackForASignal_signalIsHandled() + { + $signalRegistry = new SignalRegistry(); + + $isHandled = false; + $signalRegistry->register(SIGUSR1, function () use (&$isHandled) { + $isHandled = true; + }); + + posix_kill(posix_getpid(), SIGUSR1); + + $this->assertTrue($isHandled); + } + + public function testTwoCallbacksForASignal_bothCallbacksAreCalled() + { + $signalRegistry = new SignalRegistry(); + + $isHandled1 = false; + $signalRegistry->register(SIGUSR1, function () use (&$isHandled1) { + $isHandled1 = true; + }); + + $isHandled2 = false; + $signalRegistry->register(SIGUSR1, function () use (&$isHandled2) { + $isHandled2 = true; + }); + + posix_kill(posix_getpid(), SIGUSR1); + + $this->assertTrue($isHandled1); + $this->assertTrue($isHandled2); + } + + public function testTwoSignals_signalsAreHandled() + { + $signalRegistry = new SignalRegistry(); + + $isHandled1 = false; + $isHandled2 = false; + + $signalRegistry->register(SIGUSR1, function () use (&$isHandled1) { + $isHandled1 = true; + }); + + posix_kill(posix_getpid(), SIGUSR1); + + $this->assertTrue($isHandled1); + $this->assertFalse($isHandled2); + + $signalRegistry->register(SIGUSR2, function () use (&$isHandled2) { + $isHandled2 = true; + }); + + posix_kill(posix_getpid(), SIGUSR2); + + $this->assertTrue($isHandled2); + } + + public function testTwoCallbacksForASignal_previousAndRegisteredCallbacksWereCalled() + { + $signalRegistry = new SignalRegistry(); + + $isHandled1 = false; + pcntl_signal(SIGUSR1, function () use (&$isHandled1) { + $isHandled1 = true; + }); + + $isHandled2 = false; + $signalRegistry->register(SIGUSR1, function () use (&$isHandled2) { + $isHandled2 = true; + }); + + posix_kill(posix_getpid(), SIGUSR1); + + $this->assertTrue($isHandled1); + $this->assertTrue($isHandled2); + } + + public function testTwoCallbacksForASignal_previousCallbackFromAnotherRegistry() + { + $signalRegistry1 = new SignalRegistry(); + + $isHandled1 = false; + $signalRegistry1->register(SIGUSR1, function () use (&$isHandled1) { + $isHandled1 = true; + }); + + $signalRegistry2 = new SignalRegistry(); + + $isHandled2 = false; + $signalRegistry2->register(SIGUSR1, function () use (&$isHandled2) { + $isHandled2 = true; + }); + + posix_kill(posix_getpid(), SIGUSR1); + + $this->assertTrue($isHandled1); + $this->assertTrue($isHandled2); + } +} 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