CakePHP 3 - 从 1 个命令执行多个命令并在发生错误时记录错误

CakePHP 3 - executing multiple Commands from 1 Command and logging errors if they occur

我正在 CakePHP 3.8 中构建一个应用程序,它使用 Console Commands 来执行多个进程。

这些进程非常占用资源,所以我用命令编写它们,因为如果在浏览器中执行它们很容易超时。

有 5 个不同的脚本执行不同的任务:src/Command/Stage1Command.php, ... src/Command/Stage5Command.php.

脚本正在按顺序执行(阶段 1 ... 阶段 5)手动,即 src/Command/Stage1Command.php 执行时:

$ php bin/cake.php stage1

所有 5 个命令都接受一个参数 - 一个 ID - 然后执行一些工作。已设置如下(每个命令中都存在buildOptionsParser()中的代码):

class Stage1Command extends Command
{
    protected function buildOptionParser(ConsoleOptionParser $parser)
    {
        $parser->addArgument('filter_id', [
            'help' => 'Filter ID must be passed as an argument',
            'required' => true
        ]);
        return $parser;
    }
}

所以我可以如下执行“阶段 1”,假设 428 是我要传递的 ID。

$ php bin/cake.php stage1 428

我想实现以下目标,而不是手动执行这些:

  1. 创建一个循环遍历一组过滤器 ID 的新命令,然后调用 5 个命令中的每一个,传递 ID。

  2. 更新一个table以显示每个命令的结果(成功、错误)。

对于 (1),我创建了 src/Command/RunAllCommand.php,然后在我的 table 过滤器上使用循环来生成 ID,然后执行 5 个命令,传递 ID。脚本如下所示:

namespace App\Command;
use Cake\ORM\TableRegistry;
// ...

class RunAllCommand extends Command
{
    
    public function execute(Arguments $args, ConsoleIo $io)
    {
        $FiltersTable = TableRegistry::getTableLocator()->get('Filters');

        $all_filters = $FiltersTable->find()->toArray();

        foreach ($all_filters as $k => $filter) {
            $io->out($filter['id']);
        
            // execute Stage1Command.php        
            $command = new Stage1Command(['filter_id' => $filter['id']]);
            $this->executeCommand($command);
     
            // ...

            // execute Stage5Command.php
            $command5 = new Stage5Command(['filter_id' => $filter['id']]);
            $this->executeCommand($command5);
        }
    }
}

这行不通。它给出了一个错误:

Filter ID must be passed as an argument

我可以看出正在调用命令,因为这些是我自己来自 buildOptionsParser() 的错误消息。

这没有意义,因为 RunAllCommand.php 中的行 $io->out($filter['id']) 显示正在从我的数据库中读取过滤器 ID。你如何以这种方式传递论点?我正在关注有关调用其他命令的文档 (https://book.cakephp.org/3/en/console-and-shells/commands.html#calling-other-commands)。

我不明白如何实现 (2)。在每个命令中,我都添加了这样的代码,当发生错误时停止执行该命令的其余部分。例如,如果它在 Stage1Command 中执行,它应该中止并移动到 Stage2Command:

// e.g. this code can be anywhere in execute() in any of the 5 commands where an error occurs.
$io->error('error message');
$this->abort();

如果 $this->abort() 在任何地方被调用,我需要将其登录到我的数据库中的另一个 table。我是否需要在 $this->abort() 之前添加代码才能将其写入数据库,或者是否有其他方式,例如try...catchRunAllCommand?

背景信息: 这个想法是 RunAllCommand.php 将通过 Cron 执行。这意味着每个阶段执行的过程将定期发生,无需手动执行任何脚本 - 或手动将 ID 作为命令参数传递。

发送到“主”命令的参数不会自动传递给您使用 executeCommand() 调用的“子”命令,原因是它们很可能不兼容, “main”命令无法知道应该或不应该传递哪些参数。你最不想要的是子命令做一些你没有要求它做的事情,只是因为主命令使用了一个参数。

所以你需要传递你希望你的子命令手动接收的参数,那将是 \Cake\Console\BaseCommand::executeCommand() 的第二个参数,而不是命令构造函数,它根本不接受任何参数 (除非你覆盖了基础构造函数)。

$this->executeCommand($stage1, [$filter['id']]);

请注意,参数数组不是关联的,值作为单值条目传递,就像 PHP 会在 $argv 变量中接收它们一样,即:

['positional argument value', '--named', 'named option value']

关于错误,executeCommand() return命令的退出代码。在您的子命令中调用 $this->abort() 将触发一个异常,该异常在 executeCommand() 中被捕获并且其代码 return 就像您的子命令 execute() 中的正常退出代码一样方法。

因此,如果您只需要记录失败,那么您可以简单地评估 return 代码,例如:

$result = $this->executeCommand($stage1, [$filter['id']]);
// assuming your sub commands do always return a code, and do not 
// rely on `null` (ie no return value) being treated as success too
if ($result !== static::CODE_SUCCESS) {
    $this->log('Stage 1 failed');
}

如果您需要记录其他信息,那么您当然可以在该信息可用的地方记录您的子命令,或者可能在命令中存储错误信息并公开一个方法来读取该信息,或者抛出您的主命令可以捕获和评估的错误详细信息异常。但是,当 运行 命令独立时抛出异常并不会太好,因此您必须弄清楚在您的情况下最好的选择是什么。