Handling signal with Symfony Command
A few years ago, we wrote an article (in french) about how POSIX signals work in PHP.
Today, we want to share with you how to handle signals with Symfony Command.
⚠ This works only as of Symfony 6.3. Symfony 6.3 will be released in May 2023.
By default, Symfony Command does not handle signals. So when you start a command, and you hit CTRL+C it will immediately stop the process.
Most of the time it’s safe, but sometimes you want to handle signals to do some cleanup before stopping the command. For example if your command dialogs with a remote payment API, you want to write an atomic transaction: either the payment (remotely + locally) is done, or it is not.
Section intitulée how-to-handle-signalsHow to handle signals
To handle signals, you need to implement the SignalableCommandInterface
:
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalableCommand\SignalableCommandInterface;
class PaymentCommand extends Command implements SignalableCommandInterface
{
private bool $shouldStop = false;
protected function execute(InputInterface $input, OutputInterface $output): int
{
foreach ($this->getPayments() as $payment) {
if ($this->shouldStop) {
break;
}
$this->processPayment($payment);
}
return Command::SUCCESS;
}
public function getSubscribedSignals(): array
{
return [
SIGINT,
SIGTERM,
];
}
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
{
$this->logger->info('Signal received, stopping the command...', [
'signal' => $signal,
]);
$this->shouldStop = true;
return false;
}
}
As soon as the process is interrupted with SIGINT
or SIGTERM
, the handleSignal()
method is called. This method will toggle the shouldStop
property to true
. Then, the execute()
method is resumed and stops the loop, but only after the current payment has been fully processed fully.
The return false;
line allows to return a specific exit code when the command is signaled, or to not exit at all. In our case, we do not want to automatically exit on SIGINT
or SIGTERM
, so we return false
.
Section intitulée how-to-use-event-to-handle-signalsHow to use Event to handle signals
Symfony emits an event when a signal is received. By default the following signals are handled:
-
SIGINT
(Ctrl+C) -
SIGTERM
(kill
) -
SIGUSR1
(kill -USR1
) -
SIGUSR2
(kill -USR2
)
You can listen to the ConsoleEvents::SIGNAL
event to handle signals. The following example shows what you can do in a subscriber:
<?php
namespace App\Somewhere\In\Your\Application;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class SignalSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
ConsoleEvents::SIGNAL => 'handleSignal',
];
}
public function handleSignal(ConsoleSignalEvent $event): void
{
$signal = $event->getSignal();
// #1 Some log
$this->logger->info('Signal received'., [
'signal' => $signal,
]);
// #2 Stop the command if it implements StoppableCommandInterface
// StoppableCommandInterface is not part of the Symfony Console component
// It's up to you to implement it in your application
if (
$event->getCommand() instanceof StoppableCommandInterface)
&& in_array($signal, [SIGINT, SIGTERM], true)
) {
$event->getCommand()->stop();
$event->abortExit();
}
// #3 Do not stop on SIGUSR1 or SIGUSR2
// By default, PHP will stop on SIGUSR1, or SIGUSR2, let's change that
if (in_array($signal, [SIGINT, SIGTERM], true)) {
$event->setExitCode(null);
}
// #4 Set a custom status code on other signals
$event->setExitCode(128 + $signal);
}
}
If you want to dispatch more events when a signal is received, you can use the Application::setSignalsToDispatchEvent()
method:
// bin/console
$application = new Application($kernel);
$application->setSignalsToDispatchEvent([SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGALRM]);
Or you can configure it in the command itself:
class MyCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->getApplication()->setSignalsToDispatchEvent([SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGALRM]);
}
Section intitulée conclusionConclusion
Handling signals is very important when you want to write a long-running command, or when the command handles critical data like payments.
As you can see, as of Symfony 6.3 it is very easy to handle signals with Symfony Command. We invite you to always write this kind of code to ensure your application is safe.
Commentaires et discussions
Nos formations sur ce sujet
Notre expertise est aussi disponible sous forme de formations professionnelles !

Symfony avancée
Découvrez les fonctionnalités et concepts avancés de Symfony
Ces clients ont profité de notre expertise

Nous avons développé une plateforme de site génériques autour de l’API Phraseanet. À l’aide de Silex de composants Symfony2, nous avons accompagné Alchemy dans la réalisation d’un site déclinable pour leurs clients. Le produit est intégralement configurable et supporte de nombreux systèmes d’authentification (Ldap, OAuth2, Doctrine ou anonyme).

En tant que joaillier 100 % numérique, l’équipe de Courbet Paris a souhaité se doter d’une plateforme eCommerce, capable d’offrir une expérience moderne qui revalorise l’acte d’achat de produits de joaillerie sur internet. JoliCode a accompagné leur équipe en développant une plateforme robuste, mais aussi évolutive, afin de répondre aux enjeux business…

Pour La Plateforme du Bâtiment, le début d’année 2014 est le moment de mettre à jour le socle technique du site. JoliCode est donc intervenu aux côtés des équipes internes pour réaliser une migration de Symfony2 de la version 2.0 à la 2.3. Au programme : beaucoup de tests unitaires et fonctionnels, l’installation et l’utilisation de Composer, et une…