使用事件
Console 组件的 Application 类允许你通过事件选择性地hook到控制台应用程序的生命周期中。它没有重新发明轮子,而是使用 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();
警告
控制台事件仅由正在执行的主命令触发。由主命令调用的命令将不会触发任何事件,除非由应用程序本身运行,请参阅如何调用其他命令。
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\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
$dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event): void {
// gets the input instance
$input = $event->getInput();
// gets the output instance
$output = $event->getOutput();
// gets the command to be executed
$command = $event->getCommand();
// writes something about the command
$output->writeln(sprintf('Before running command <info>%s</info>', $command->getName()));
// gets the application
$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\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
$dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event): void {
// gets the command to be executed
$command = $event->getCommand();
// ... check if the command can be executed
// disables the command, this will result in the command being skipped
// and code 113 being returned from the Application
$event->disableCommand();
// it is possible to enable the command in a later listener
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\ConsoleEvents;
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()));
// gets the current exit code (the exception code)
$exitCode = $event->getExitCode();
// changes the exception to another one
$event->setError(new \LogicException('Caught exception', $exitCode, $event->getError()));
});
ConsoleEvents::TERMINATE
事件
典型用途:在命令执行后执行一些清理操作。
在命令执行后,会分发 ConsoleEvents::TERMINATE
事件。它可以用于执行所有命令都需要执行的任何操作,或者清理你在 ConsoleEvents::COMMAND
监听器中启动的操作(例如发送日志、关闭数据库连接、发送电子邮件等)。监听器也可能更改退出代码。
监听器接收 ConsoleTerminateEvent 事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
$dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event): void {
// gets the output
$output = $event->getOutput();
// gets the command that has been executed
$command = $event->getCommand();
// displays the given content
$output->writeln(sprintf('After running command <info>%s</info>', $command->getName()));
// changes the exit code
$event->setExitCode(128);
});
提示
当命令抛出异常时,也会分发此事件。它在 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 {
// gets the signal number
$signal = $event->getHandlingSignal();
// sets the exit code
$event->setExitCode(0);
if (\SIGINT === $signal) {
echo "bye bye!";
}
});
如果你希望命令即使在事件分发后仍继续执行,也可以使用 abortExit() 方法来中止退出
1 2 3 4 5 6
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
$dispatcher->addListener(ConsoleEvents::SIGNAL, function (ConsoleSignalEvent $event) {
$event->abortExit();
});
提示
所有可用的信号(SIGINT
、SIGQUIT
等)都定义为 PCNTL PHP 扩展的常量。必须安装该扩展才能使这些常量可用。
如果你在 Symfony 应用程序内部使用 Console 组件,则命令可以自行处理信号。为此,请实现 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
// 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
{
// return here any of the constants defined by PCNTL extension
return [\SIGINT, \SIGTERM];
}
public function handleSignal(int $signal): int|false
{
if (\SIGINT === $signal) {
// ...
}
// ...
// return an integer to set the exit code, or
// false to continue normal execution
return 0;
}
}
Symfony 不处理命令接收到的任何信号(甚至不处理 SIGKILL
、SIGTERM
等)。此行为是故意的,因为它使你可以灵活地处理所有信号,例如在终止命令之前执行一些任务。
提示
如果你需要从信号的整数值中获取信号名称(例如,用于日志记录),则可以使用 getSignalName() 方法。