From 5b066d629a77b03ba804f4030a0f13f83665886b Mon Sep 17 00:00:00 2001 From: Gwendolen Lynch Date: Mon, 7 Feb 2022 10:53:23 +0100 Subject: [PATCH 1/4] Extract dispatching console signal handling and include subscribers --- Application.php | 35 +++++---- Tests/ApplicationTest.php | 161 ++++++++++++++++++++++++++++++++------ 2 files changed, 158 insertions(+), 38 deletions(-) diff --git a/Application.php b/Application.php index a81cfdcbb..bb6b29edd 100644 --- a/Application.php +++ b/Application.php @@ -974,22 +974,31 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI } } - if ($command instanceof SignalableCommandInterface && ($this->signalsToDispatchEvent || $command->getSubscribedSignals())) { - if (!$this->signalRegistry) { - 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.'); - } + if ($this->signalsToDispatchEvent) { + $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; + $dispatchSignals = $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::SIGNAL); - if (Terminal::hasSttyAvailable()) { - $sttyMode = shell_exec('stty -g'); + if ($commandSignals || $dispatchSignals) { + if (!$this->signalRegistry) { + 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.'); + } - foreach ([\SIGINT, \SIGTERM] as $signal) { - $this->signalRegistry->register($signal, static function () use ($sttyMode) { - shell_exec('stty '.$sttyMode); - }); + if (Terminal::hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + foreach ([\SIGINT, \SIGTERM] as $signal) { + $this->signalRegistry->register($signal, static function () use ($sttyMode) { + shell_exec('stty '.$sttyMode); + }); + } + } + + foreach ($commandSignals as $signal) { + $this->signalRegistry->register($signal, [$command, 'handleSignal']); } } - if ($this->dispatcher) { + if ($dispatchSignals) { foreach ($this->signalsToDispatchEvent as $signal) { $event = new ConsoleSignalEvent($command, $input, $output, $signal); @@ -1005,10 +1014,6 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI }); } } - - foreach ($command->getSubscribedSignals() as $signal) { - $this->signalRegistry->register($signal, [$command, 'handleSignal']); - } } if (null === $this->dispatcher) { diff --git a/Tests/ApplicationTest.php b/Tests/ApplicationTest.php index a5918aa3f..08e795b5e 100644 --- a/Tests/ApplicationTest.php +++ b/Tests/ApplicationTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; 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\NamespaceNotFoundException; @@ -42,6 +43,8 @@ use Symfony\Component\Console\Tester\ApplicationTester; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Process\Process; class ApplicationTest extends TestCase @@ -1843,9 +1846,9 @@ public function testCommandNameMismatchWithCommandLoaderKeyThrows() /** * @requires extension pcntl */ - public function testSignal() + public function testSignalListenerNotCalledByDefault() { - $command = new SignableCommand(); + $command = new SignableCommand(false); $dispatcherCalled = false; $dispatcher = new EventDispatcher(); @@ -1853,29 +1856,97 @@ public function testSignal() $dispatcherCalled = true; }); - $application = new Application(); - $application->setAutoExit(false); - $application->setDispatcher($dispatcher); - $application->setSignalsToDispatchEvent(\SIGALRM); - $application->add(new LazyCommand('signal', [], '', false, function () use ($command) { return $command; }, true)); - - $this->assertFalse($command->signaled); - $this->assertFalse($dispatcherCalled); + $application = $this->createSignalableApplication($command, $dispatcher); $this->assertSame(0, $application->run(new ArrayInput(['signal']))); $this->assertFalse($command->signaled); $this->assertFalse($dispatcherCalled); + } + + /** + * @requires extension pcntl + */ + public function testSignalListener() + { + $command = new SignableCommand(); + + $dispatcherCalled = false; + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('console.signal', function () use (&$dispatcherCalled) { + $dispatcherCalled = true; + }); + + $application = $this->createSignalableApplication($command, $dispatcher); - $command->loop = 100000; - pcntl_alarm(1); $this->assertSame(1, $application->run(new ArrayInput(['signal']))); - $this->assertTrue($command->signaled); $this->assertTrue($dispatcherCalled); + $this->assertTrue($command->signaled); + } + + /** + * @requires extension pcntl + */ + public function testSignalSubscriberNotCalledByDefault() + { + $command = new BaseSignableCommand(false); + + $subscriber = new SignalEventSubscriber(); + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber($subscriber); + + $application = $this->createSignalableApplication($command, $dispatcher); + + $this->assertSame(0, $application->run(new ArrayInput(['signal']))); + $this->assertFalse($subscriber->signaled); + } + + /** + * @requires extension pcntl + */ + public function testSignalSubscriber() + { + $command = new BaseSignableCommand(); + + $subscriber1 = new SignalEventSubscriber(); + $subscriber2 = new SignalEventSubscriber(); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber($subscriber1); + $dispatcher->addSubscriber($subscriber2); + + $application = $this->createSignalableApplication($command, $dispatcher); + + $this->assertSame(1, $application->run(new ArrayInput(['signal']))); + $this->assertTrue($subscriber1->signaled); + $this->assertTrue($subscriber2->signaled); + } + + /** + * @requires extension pcntl + */ + public function testSetSignalsToDispatchEvent() + { + $command = new BaseSignableCommand(); + + $subscriber = new SignalEventSubscriber(); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber($subscriber); + + $application = $this->createSignalableApplication($command, $dispatcher); + $application->setSignalsToDispatchEvent(\SIGUSR2); + $this->assertSame(0, $application->run(new ArrayInput(['signal']))); + $this->assertFalse($subscriber->signaled); + + $application = $this->createSignalableApplication($command, $dispatcher); + $application->setSignalsToDispatchEvent(\SIGUSR1); + $this->assertSame(1, $application->run(new ArrayInput(['signal']))); + $this->assertTrue($subscriber->signaled); } public function testSignalableCommandInterfaceWithoutSignals() { - $command = new SignableCommand(); + $command = new SignableCommand(false); $dispatcher = new EventDispatcher(); $application = new Application(); @@ -1917,6 +1988,18 @@ public function testSignalableRestoresStty() $this->assertSame($previousSttyMode, $sttyMode); } + + private function createSignalableApplication(Command $command, ?EventDispatcherInterface $dispatcher): Application + { + $application = new Application(); + $application->setAutoExit(false); + if ($dispatcher) { + $application->setDispatcher($dispatcher); + } + $application->add(new LazyCommand('signal', [], '', false, function () use ($command) { return $command; }, true)); + + return $application; + } } class CustomApplication extends Application @@ -1971,25 +2054,26 @@ public function isEnabled(): bool } } -class SignableCommand extends Command implements SignalableCommandInterface +class BaseSignableCommand extends Command { public $signaled = false; - public $loop = 100; + public $loop = 1000; + private $emitsSignal; protected static $defaultName = 'signal'; - public function getSubscribedSignals(): array + public function __construct(bool $emitsSignal = true) { - return SignalRegistry::isSupported() ? [\SIGALRM] : []; - } - - public function handleSignal(int $signal): void - { - $this->signaled = true; + parent::__construct(); + $this->emitsSignal = $emitsSignal; } protected function execute(InputInterface $input, OutputInterface $output): int { + if ($this->emitsSignal) { + posix_kill(posix_getpid(), SIGUSR1); + } + for ($i = 0; $i < $this->loop; ++$i) { usleep(100); if ($this->signaled) { @@ -2000,3 +2084,34 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } } + +class SignableCommand extends BaseSignableCommand implements SignalableCommandInterface +{ + protected static $defaultName = 'signal'; + + public function getSubscribedSignals(): array + { + return SignalRegistry::isSupported() ? [\SIGUSR1] : []; + } + + public function handleSignal(int $signal): void + { + $this->signaled = true; + } +} + +class SignalEventSubscriber implements EventSubscriberInterface +{ + public $signaled = false; + + public function onSignal(ConsoleSignalEvent $event): void + { + $this->signaled = true; + $event->getCommand()->signaled = true; + } + + public static function getSubscribedEvents(): array + { + return ['console.signal' => 'onSignal']; + } +} From 8fe565067c4e8786d10e6ba36b161fc10e4c5224 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 8 Aug 2022 09:18:14 +0200 Subject: [PATCH 2/4] fix dispatch signal event check for compatibility with the contract interface --- Application.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Application.php b/Application.php index 82f75d4fc..991c94d95 100644 --- a/Application.php +++ b/Application.php @@ -985,9 +985,8 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI if ($this->signalsToDispatchEvent) { $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; - $dispatchSignals = $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::SIGNAL); - if ($commandSignals || $dispatchSignals) { + if ($commandSignals || null !== $this->dispatcher) { if (!$this->signalRegistry) { 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.'); } @@ -1007,7 +1006,7 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI } } - if ($dispatchSignals) { + if (null !== $this->dispatcher) { foreach ($this->signalsToDispatchEvent as $signal) { $event = new ConsoleSignalEvent($command, $input, $output, $signal); From c072aa8f724c3af64e2c7a96b796a4863d24dba1 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 17 Aug 2022 15:14:42 +0200 Subject: [PATCH 3/4] fix expected command name order with mixed integer and string namespaces --- Descriptor/ApplicationDescription.php | 2 +- Tests/Descriptor/ApplicationDescriptionTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Descriptor/ApplicationDescription.php b/Descriptor/ApplicationDescription.php index fac01ad37..2a3acc99b 100644 --- a/Descriptor/ApplicationDescription.php +++ b/Descriptor/ApplicationDescription.php @@ -131,7 +131,7 @@ private function sortCommands(array $commands): array } if ($namespacedCommands) { - ksort($namespacedCommands); + ksort($namespacedCommands, \SORT_STRING); foreach ($namespacedCommands as $key => $commandsSet) { ksort($commandsSet); $sortedCommands[$key] = $commandsSet; diff --git a/Tests/Descriptor/ApplicationDescriptionTest.php b/Tests/Descriptor/ApplicationDescriptionTest.php index b3ba9d848..da64dca00 100644 --- a/Tests/Descriptor/ApplicationDescriptionTest.php +++ b/Tests/Descriptor/ApplicationDescriptionTest.php @@ -36,7 +36,7 @@ public function getNamespacesProvider() return [ [['_global'], ['foobar']], [['a', 'b'], ['b:foo', 'a:foo', 'b:bar']], - [['_global', 'b', 'z', 22, 33], ['z:foo', '1', '33:foo', 'b:foo', '22:foo:bar']], + [['_global', 22, 33, 'b', 'z'], ['z:foo', '1', '33:foo', 'b:foo', '22:foo:bar']], ]; } } From c5c2e313aa682530167c25077d6bdff36346251e Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Tue, 23 Aug 2022 22:52:30 +0200 Subject: [PATCH 4/4] [Console] Fix OutputFormatterStyleStack::getCurrent return type --- Formatter/OutputFormatterStyleStack.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Formatter/OutputFormatterStyleStack.php b/Formatter/OutputFormatterStyleStack.php index b425449ef..66f86a5f7 100644 --- a/Formatter/OutputFormatterStyleStack.php +++ b/Formatter/OutputFormatterStyleStack.php @@ -77,7 +77,7 @@ public function pop(OutputFormatterStyleInterface $style = null): OutputFormatte /** * Computes current style with stacks top codes. */ - public function getCurrent(): OutputFormatterStyle + public function getCurrent(): OutputFormatterStyleInterface { if (empty($this->styles)) { return $this->emptyStyle; 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