我正在用CakePHP3.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;
}
}
所以我可以如下执行“阶段1”,假设428
是我要传递的ID。
$ php bin/cake.php stage1 428
我希望实现以下目标,而不是手动执行这些目标:
>
创建一个新命令,该命令循环遍历一组筛选器ID,然后调用5个命令中的每一个,传递ID。
更新一个表以显示每个命令的结果(成功,错误)。
对于(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()
的错误消息。
这是没有意义的,因为runallcommand.php
中的$io->out($filter['id'])
行显示正在从我的数据库中读取过滤器ID。 如何以这种方式传递一个参数? 我正在阅读有关调用其他命令的文档(https://book.cakephp.org/3/en/console-and-shell/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()
之前添加代码才能将其写入数据库,或者是否有其他方法,例如runallcommand
中的try...catch
?
背景信息:这样做的想法是,runallcommand.php
将通过cron执行。 这意味着每个阶段执行的过程将定期进行,而不需要手动执行任何脚本,也不需要手动将ID作为命令参数传递。
发送到“main”命令的参数不会自动传递给您用executeCommand()
调用的“sub”命令,原因是它们很可能不兼容,“main”命令无法知道哪些参数应该传递或不应该传递。 您最不希望的是一个sub命令做一些您没有要求它做的事情,仅仅是因为main命令使用了一个参数。
因此,您需要传递希望您的sub命令手动接收的参数,这将是\cake\console\baseCommand::executeCommand()
的第二个参数,而不是命令构造函数,它根本不需要任何参数(除非您覆盖了基构造函数)。
$this->executeCommand($stage1, [$filter['id']]);
注意,参数数组不是关联的,这些值作为单值条目传递,就像PHP在$argv
变量中接收它们一样,即:
['positional argument value', '--named', 'named option value']
关于错误,executeCommand()
返回命令的退出代码。 在sub命令中调用$this->abort()
将触发异常,该异常将在executeCommand()
中捕获,并返回其代码,就像从sub命令的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');
}
如果您需要记录其他信息,那么您当然可以在子命令内部记录这些信息,或者抛出一个包含错误细节的异常,主命令可以捕获和评估这些错误细节。 但是,当单独运行命令时,抛出异常并不是太好,因此您必须考虑在您的情况下什么是最好的选项。