0

我正在 CakePHP 3.8 中构建一个应用程序,它使用控制台命令来执行多个进程。

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

有 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;
    }
}

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

$ php bin/cake.php stage1 428

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

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

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

对于 (1) 我已经创建src/Command/RunAllCommand.php然后在我的过滤器表上使用一个循环来生成 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);
        }
    }
}

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

过滤器 ID 必须作为参数传递

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

这是没有意义的,因为输入的行$io->out($filter['id'])显示RunAllCommand.php过滤器 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()在任何地方被调用,我需要将其记录到数据库中的另一个表中。$this->abort()在将其写入数据库之前是否需要添加代码,或者是否有其他方式try...catch,例如RunAllCommand

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

4

1 回答 1

2

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

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

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

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

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

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

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

$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');
}

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

于 2020-07-10T14:59:59.843 回答