Handle Signal in Console Command in Symfony 8

Handle Signal in Console Command in Symfony 8

Symfony console commands are often used for long-running tasks such as batch processing, data imports, or communication with external services. In these scenarios, it's important to stop execution gracefully when the operating system sends termination signals like SIGINT or SIGTERM. This tutorial shows how to handle signal in console command in Symfony 8 application.

Signals are low-level notifications sent by the operating system to a running process. A common example is pressing CTRL+C in the terminal, which sends a SIGINT signal to the command. PHP exposes these signals through the PCNTL extension, where each signal is available as a predefined constant. Make sure the PCNTL extension is enabled, otherwise signal handling will not work.

Symfony allows console commands to listen for signals by implementing two methods: getSubscribedSignals and handleSignal. The getSubscribedSignals method declares which signals the command should listen to. Once a signal is received, Symfony calls handleSignal, and after that continues the command's execution, giving the opportunity to shut down cleanly and safely.

Below is a simple command that simulates invoices processing one by one. When the user interrupts execution (for example with CTRL+C), the command finishes the current invoice and exits the loop cleanly.

src/Command/TestCommand.php

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:test')]
class TestCommand extends Command
{
    private bool $stop = false;

    public function getSubscribedSignals(): array
    {
        return [SIGINT, SIGTERM];
    }

    public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
    {
        $this->stop = true;

        return false;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        foreach ($this->getInvoiceIds() as $invoiceId) {
            if ($this->stop) {
                $output->writeln('Stopped');

                break;
            }

            sleep(3); // Simulate processing time
            $output->writeln('Invoice processed: '.$invoiceId);
        }

        return self::SUCCESS;
    }

    private function getInvoiceIds(): array
    {
        return [10, 20, 30, 40]; // Typically fetched from a database
    }
}

Leave a Comment

Cancel reply

Your email address will not be published.