7

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?

GiamPy
  • 3,543
  • 3
  • 30
  • 51
  • You might want to check this question also https://stackoverflow.com/questions/23479107/aborting-and-resuming-a-symfony-console-command – etudor Feb 27 '18 at 10:38

2 Answers2

6

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).

Jakub Krawczyk
  • 950
  • 8
  • 16
  • This prevents me to actually use CTRL+C though. The command keeps running even though I interrupt it. – GiamPy Feb 27 '18 at 11:28
  • It is a handler, so it should actually handle the signal in the way you want. If you want to do something and then still stop the execution, you can use `exit()` or `die()` function. – Jakub Krawczyk Feb 27 '18 at 11:37
  • 1
    `pcntl_signal_dispatch()` needs to be ran after every "task" to trigger signal handlers, that means if a SIGINT command is sent, handlers will not be executed until `pcntl_signal_dispatch()` is ran. That means I am not able to interrupt a process in-the-middle-of-something. – GiamPy Feb 27 '18 at 11:39
  • It depends on what you are doing in the task, but if your operations block IO then of course you need to wait for the task to be finished before the handler is called. – Jakub Krawczyk Feb 27 '18 at 12:24
2

Since Symfony 5.2, it's a native feature: https://symfony.com/blog/new-in-symfony-5-2-console-signals

Implement an interface (SignalableCommandInterface), subscribe to signals, and handle them:

public function handleSignal(int $signal)
{
    if (SIGINT === $signal) {
        // ...
    }

    // ...
}
Gregoire
  • 3,735
  • 3
  • 25
  • 37
  • I'm guessing this requires that you're using the full Symfony framework. It's not working for me and we're on Symfony 6 components. – Jacob Thomason Oct 15 '22 at 04:15