phpsymfonysymfony-consolesymfony-eventdispatcher

Catching terminal terminations/exits with Symfony Console (CTRL+C)


I have built a command which triggers file downloads from over the internet, however since these files need to be processed by another component, we need to make sure that every file that has been downloaded and has not been modified in the last 10 seconds, is a proper video and not corrupted/partially downloaded.

For this reason, we need to find a way to catch CTRL+C or command terminations and cleanup any applicable file that has not been successfully downloaded.

This is what I tried so far by using symfony/console and symfony/event-dispatcher:

#!/usr/bin/env php
<?php

require_once(__DIR__ . '/../vendor/autoload.php');

use Symfony\Component\Console\Application;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use ImportExport\Console\ImportCommand;
use Monolog\Logger;

$dotenv = new Dotenv\Dotenv(__DIR__ . '/../');
$dotenv->load();

$logger = new Logger('console');

$dispatcher = new EventDispatcher();
$dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) {
    // gets the command that has been executed
    $command = $event->getCommand();

    var_dump($command);
});

$application = new Application("Import-Export System", 'v0.1.0-ALPHA');
$application->add(new ImportCommand($logger));
$application->setDispatcher($dispatcher);
$application->run();

However the var_dump() is never shown in the console, if I do CTRL+C.

Suggestions?


Solution

  • When you do CTRL+C it is actually SIGINT that is being sent, not SIGTERM. The best way I can think of is to register handler with http://php.net/manual/en/function.pcntl-signal.php and dispatch the signal with pcntl_signal_dispatch, example:

    pcntl_signal(SIGINT,'sigIntHandler');
    
    function sigIntHandler() {
      // Do some stuff
    }
    

    Of course you need to adjust it to your needs. Note that you can also defer to class methods inside your commands, so you could for example create an AbstractCommand with generic sigIntHandler() and register it in the constructor:

    pcntl_signal(SIGINT, [$this, 'sigIntHandler']);
    

    Then use pcntl_signal_dispatch() for example in the loop of your command (it needs to be called in each iteration).