Использование событий

Дата обновления перевода 2024-06-28

Использование событий

Класс Application компонента Console позволяет вам по желанию подключаться к жизненному циклу консольного приложения через события. Вместо того, чтобы изобретать велосипед, он использует компонент Symfony EventDispatcher, чтобы сделать работу:

1
2
3
4
5
6
7
8
use Symfony\Component\Console\Application;
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();

$application = new Application();
$application->setDispatcher($dispatcher);
$application->run();

Caution

События консоли вызываются только путём выполнения основной команды. Команды, вызванные основной командой, не запустят ни одно событие, если только не запускаются самим приложением, см. Как вызывать другие команды.

Событие ConsoleEvents::COMMAND

Типичное назначение: Сделать что-либо до запуска любой команды (вроде логирования, какая команда будет выполнена), или отобразить что-то о том событии, которое будет выполнено.

Событие ConsoleEvents::COMMAND запускается прямо перед вызовом любой команды. Слушатели получают событие ConsoleCommandEvent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleCommandEvent;

$dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event): void {
    // получает экземпляр ввода
    $input = $event->getInput();

    // получает экземпляр вывода
    $output = $event->getOutput();

    // получает команду, которая будет выполнена
    $command = $event->getCommand();

    // пишет что-то о команде
    $output->writeln(sprintf('Before running command <info>%s</info>', $command->getName()));

    // получает приложение
    $application = $command->getApplication();
});

Отключение команд внутри слушателей

Используя метод disableCommand(), вы можете отключать команды внутри слушателя. Тогда приложение не будет выполнять команду, а вместо этого вернёт код 113 (определённый в ConsoleCommandEvent::RETURN_CODE_DISABLED). Этот код является одним из зарезервированных кодов завершения для команд консоль, которые подчиняются стандарту C/C++.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleCommandEvent;

$dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event): void {
    // получает команду, которая будет выполнена
    $command = $event->getCommand();

    // ... проверить, можно ли выполнить команду

    // отключает команду, что приведёт к пропуску команды
    // и возвращению кода 113 из приложения
    $event->disableCommand();

    // возможно включить команду в более позднем слушателе
    if (!$event->commandShouldRun()) {
        $event->enableCommand();
    }
});

Событие ConsoleEvents::ERROR

Типичное назначение: Обрабатывать исключения, вызванные во время выполнения команды.

Каждый раз, когда команда вызывает исключение, включая те, что запускаются из слушателей событий, выполняется событиеConsoleEvents::ERROR. Слушатель может обернуть или изменить исключение или сделать что-либо полезное перед тем, как приложение вызовет исключение.

Слушатели получают событие ConsoleErrorEvent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;

$dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event): void {
    $output = $event->getOutput();

    $command = $event->getCommand();

    $output->writeln(sprintf('Oops, exception thrown while running command <info>%s</info>', $command->getName()));

    // получает текущий код завершения (код исключения)
    $exitCode = $event->getExitCode();

    // меняет исключение на другое
    $event->setException(new \LogicException('Caught exception', $exitCode, $event->getError()));
});

Событие ConsoleEvents::TERMINATE

Типичное назначение: Выполнит некоторые очищающие действия после выполнения команды.

После выполнени команды, вызывается событие ConsoleEvents::TERMINATE. Оно может быть использовано для выполнения любых действий, необходимых для всех команд, или для уборки того, что вы начали в слушателе ConsoleEvents::COMMAND (вроде отправки логов, закрытия соединения DB, отправки электронных писем, ...). Слушатель также может изменить код завершения.

Слушатели получают событие ConsoleTerminateEvent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

$dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) {
    // получает вывод
    $output = $event->getOutput();

    // получает выполненную команду
    $command = $event->getCommand();

    // отображает заданное содержание
    $output->writeln(sprintf('After running command <info>%s</info>', $command->getName()));

    // изменяет код завершения
    $event->setExitCode(128);
});

Tip

Это событие также выполняется когда команда вызывает исключение. Тогда оно выполняется прямо после события ConsoleEvents::ERROR. В этом случае, полученный код завершения является кодом исключения.

Кроме того, событие запускается, когда команда завершается по сигналу. Подробнее о сигналах вы можете узнать в специальном разделе .

Событие ConsoleEvents::SIGNAL

Типичное назначение*: Для выполнения некоторых действий после прерывания выполнения команды.

Сигналы - это асинхронные уведомления, отправленные процессу для того, чтобы уведоить его о произошедшем событии. Например, когда вы нажимаете Ctrl + C в команде, операционная система отправляет ей сигнал SIGINT.

Когда команда прерывается, Symfony запускает событие ConsoleEvents::SIGNAL. Слушайте это событие, чтобы вы могли выполнить какие-то действия (например, ведение логов каких-то результатов, очистку некоторых временных файлов и т.д.) до завершения выполнения команды.

Слушатели получают событие ConsoleSignalEvent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleSignalEvent;

$dispatcher->addListener(ConsoleEvents::SIGNAL, function (ConsoleSignalEvent $event): void {

    // получает число сигнала
    $signal = $event->getHandlingSignal();

    // устанавливает код выхода
    $event->setExitCode(0);

    if (\SIGINT === $signal) {
        echo "bye bye!";
    }
});

Также можно прервать выход, если вы хотите, чтобы команда продолжала выполняться даже после развёртывания события, благодаря методу abortExit()

use SymfonyComponentConsoleConsoleEvents; use SymfonyComponentConsoleEventConsoleSignalEvent;

$dispatcher->addListener(ConsoleEvents::SIGNAL, function (ConsoleSignalEvent $event) {
$event->abortExit();

});

Tip

Все доступные сигналы (SIGINT, SIGQUIT, и др.) определены как константы PHP-расширения PCNTL. Расширение должно быть установлено для того, чтобы эти константы были доступны.

Если вы используете компонент Console внутри приложения Symfony, команды могут обрабатывать сигналы самостоятельно. Чтобы сделать это, реализуйте SignalableCommandInterface, и подпишитесь на один или более сигналов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Command/SomeCommand.php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\SignalableCommandInterface;

class SomeCommand extends Command implements SignalableCommandInterface
{
    // ...

    public function getSubscribedSignals(): array
    {
        // здесь верните любое содержание, определенное расширением PCNTL
        return [\SIGINT, \SIGTERM];
    }

    public function handleSignal(int $signal): int|false
    {
        if (\SIGINT === $signal) {
            // ...
        }

        // ...

        // вернуть целое число, чтобы установить код выхода или
        // false, чтобы продолжить нормальное выполнение
        return 0;

    }
}

Symfony не обрабатывает ни один сигнал, полученный командой (даже SIGKILL, SIGTERM и т.д.). Такое поведение намеренное, так как оно дает вам гибкость, позволяющую обрабатывать все сигналы, например, для выполнения некоторых задач перед завершением команды.

Tip

Если вам необходимо получить имя сигнала из его целочисленного значения (например, для ведения логов), то можно использовать метод getSignalName()