如何通过交互调用 artisan 控制台命令
How to call an artisan console command with an interaction
我目前正在 Laravel 5.1 项目中创建 php artisan 控制台命令,并且想从我的控制台命令调用另一个控制台命令。我要调用的这个第三方命令不接受任何选项或参数,而是通过交互式问题接收其输入。
我知道我可以调用带有如下选项和参数的命令:
$this->call('command:name', ['argument' => 'foo', '--option' => 'bar']);
我也知道我可以调用交互式命令而无需像这样从命令行进行交互:
php artisan command:name --no-interaction
但是我怎样才能在我的命令中回答这些互动问题呢?
我想做类似下面的事情(伪代码)。
$this->call('command:name', [
'argument' => 'foo',
'--option' => 'bar'
], function($console) {
$console->writeln('Yes'); //answer an interactive question
$console-writeln('No'); //answer an interactive question
$console->writeln(''); //skip answering an interactive question
} );
当然上面不行,因为$this->call($command, $arguments)
不接受第三个回调参数。
从控制台命令调用控制台命令时如何回答交互式问题?
我是这样做的。
注意:这会修补核心 Symfony class QuestionHelper@doAsk
,尽管这段代码 运行 对我来说很好(我目前只是在做概念验证),这段代码在任何生产环境中都不应该 运行。
我还没有接受我自己的答案,想知道是否有更好的方法来做到这一点。
以下假定安装 Laravel 5.1。
首先 composer-require Patchwork 包。 我用它来增强 Symfony class 方法的功能.
composer require antecedent/patchwork
编辑 bootstrap/app.php
并在创建应用程序后立即添加以下内容。(不会自动加载 Patchwork)
if($app->runningInConsole()) {
require_once(__DIR__ . '/../vendor/antecedent/patchwork/Patchwork.php');
};
将以下两个 use 语句添加到控制台命令的顶部 class
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
augment/patch QuestionHelper@doAsk
通过在控制台命令中使用这些辅助方法 class
public function __construct() {
parent::__construct();
$this->patchAskingQuestion();
}
/**
* Patch QuestionHelper@doAsk
* When a key 'qh-patch-answers' is found in the $_REQUEST superglobal,
* We assume this is an array which holds the answers for our interactive questions.
* shift each answer off the array, before answering the corresponding question.
* When an answer has a NULL value, we will just provide the default answer (= skip question)
*/
private function patchAskingQuestion() {
\Patchwork\replace('Symfony\Component\Console\Helper\QuestionHelper::doAsk', function(OutputInterface $output, Question $question) {
$answers = &$_REQUEST['qh-patch-answers'];
//No predefined answer found? Just call the original method
if(empty($answers)) {
return \Patchwork\callOriginal([$output, $question]);
}
//using the next predefined answer, or the default if the predefined answer was NULL
$answer = array_shift($answers);
return ($answer === null) ? $question->getDefault() : $answer;
});
}
private function setPredefinedAnswers($answers) {
$_REQUEST['qh-patch-answers'] = $answers;
}
private function clearPredefinedAnswers() {
unset($_REQUEST['qh-patch-answers']);
}
您现在可以像这样回答互动问题
public function fire() {
//predefine the answers to the interactive questions
$this->setPredefinedAnswers([
'Yes', //first question will be answered with 'Yes'
'No', //second question will be answered with 'No'
null, //third question will be skipped (using the default answer)
null, //fourth question will be skipped (using the default answer)
]);
//call the interactive command
$this->call('command:name');
//clean up, so future calls to QuestionHelper@doAsk will definitely call the original method
$this->clearPredefinedAnswers();
}
我有另一个解决方案,它是调用 symfony 命令执行 'php artisan' 而不是使用 artisan 子命令。我认为这比修补第 3 方代码更好。
这是管理这个的特征。
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
trait ArtisanCommandTrait{
public function executeArtisanCommand($command, $options){
$stmt = 'php artisan '. $command . ' ' . $this->prepareOptions($options);
$process = new Process($stmt);
$process->run();
// executes after the command finishes
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process->getOutput();
}
public function prepareOptions($options){
$args = [];
$opts = [];
$flags = [];
foreach ($options as $key => $value) {
if(ctype_alpha(substr($key, 0, 1)))
$args[] = $value;
else if(starts_with($key, '--')){
$opts[] = $key. (is_null($value) ? '' : '=' . $value) ;
}
else if(starts_with($key, '-')){
$flags[] = $key;
}
}
return implode(' ', $args) . ' '
.implode(' ', $opts). ' '
.implode(' ', $flags);
}
}
现在,您应该可以传递任何 artisan 特殊选项,例如无交互。
public function handle(){
$options = [
'argument' => $argument,
'--option' => $options, // options should be preceded by --
'-n' => null // no-interaction option
];
$command = 'your:command';
$output = $this->executeArtisanCommand($command, $options);
echo $output;
}
你可以从这里下载特征gist
与mpyw/streamable-console: Call interactive artisan command using arbitrary stream:
$this->usingInputStream("yes\nno\n")->call('command:name');
我目前正在 Laravel 5.1 项目中创建 php artisan 控制台命令,并且想从我的控制台命令调用另一个控制台命令。我要调用的这个第三方命令不接受任何选项或参数,而是通过交互式问题接收其输入。
我知道我可以调用带有如下选项和参数的命令:
$this->call('command:name', ['argument' => 'foo', '--option' => 'bar']);
我也知道我可以调用交互式命令而无需像这样从命令行进行交互:
php artisan command:name --no-interaction
但是我怎样才能在我的命令中回答这些互动问题呢?
我想做类似下面的事情(伪代码)。
$this->call('command:name', [
'argument' => 'foo',
'--option' => 'bar'
], function($console) {
$console->writeln('Yes'); //answer an interactive question
$console-writeln('No'); //answer an interactive question
$console->writeln(''); //skip answering an interactive question
} );
当然上面不行,因为$this->call($command, $arguments)
不接受第三个回调参数。
从控制台命令调用控制台命令时如何回答交互式问题?
我是这样做的。
注意:这会修补核心 Symfony class QuestionHelper@doAsk
,尽管这段代码 运行 对我来说很好(我目前只是在做概念验证),这段代码在任何生产环境中都不应该 运行。
我还没有接受我自己的答案,想知道是否有更好的方法来做到这一点。
以下假定安装 Laravel 5.1。
首先 composer-require Patchwork 包。 我用它来增强 Symfony class 方法的功能.
composer require antecedent/patchwork
编辑
bootstrap/app.php
并在创建应用程序后立即添加以下内容。(不会自动加载 Patchwork)if($app->runningInConsole()) { require_once(__DIR__ . '/../vendor/antecedent/patchwork/Patchwork.php'); };
将以下两个 use 语句添加到控制台命令的顶部 class
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
augment/patch
QuestionHelper@doAsk
通过在控制台命令中使用这些辅助方法 classpublic function __construct() { parent::__construct(); $this->patchAskingQuestion(); } /** * Patch QuestionHelper@doAsk * When a key 'qh-patch-answers' is found in the $_REQUEST superglobal, * We assume this is an array which holds the answers for our interactive questions. * shift each answer off the array, before answering the corresponding question. * When an answer has a NULL value, we will just provide the default answer (= skip question) */ private function patchAskingQuestion() { \Patchwork\replace('Symfony\Component\Console\Helper\QuestionHelper::doAsk', function(OutputInterface $output, Question $question) { $answers = &$_REQUEST['qh-patch-answers']; //No predefined answer found? Just call the original method if(empty($answers)) { return \Patchwork\callOriginal([$output, $question]); } //using the next predefined answer, or the default if the predefined answer was NULL $answer = array_shift($answers); return ($answer === null) ? $question->getDefault() : $answer; }); } private function setPredefinedAnswers($answers) { $_REQUEST['qh-patch-answers'] = $answers; } private function clearPredefinedAnswers() { unset($_REQUEST['qh-patch-answers']); }
您现在可以像这样回答互动问题
public function fire() { //predefine the answers to the interactive questions $this->setPredefinedAnswers([ 'Yes', //first question will be answered with 'Yes' 'No', //second question will be answered with 'No' null, //third question will be skipped (using the default answer) null, //fourth question will be skipped (using the default answer) ]); //call the interactive command $this->call('command:name'); //clean up, so future calls to QuestionHelper@doAsk will definitely call the original method $this->clearPredefinedAnswers(); }
我有另一个解决方案,它是调用 symfony 命令执行 'php artisan' 而不是使用 artisan 子命令。我认为这比修补第 3 方代码更好。
这是管理这个的特征。
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
trait ArtisanCommandTrait{
public function executeArtisanCommand($command, $options){
$stmt = 'php artisan '. $command . ' ' . $this->prepareOptions($options);
$process = new Process($stmt);
$process->run();
// executes after the command finishes
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process->getOutput();
}
public function prepareOptions($options){
$args = [];
$opts = [];
$flags = [];
foreach ($options as $key => $value) {
if(ctype_alpha(substr($key, 0, 1)))
$args[] = $value;
else if(starts_with($key, '--')){
$opts[] = $key. (is_null($value) ? '' : '=' . $value) ;
}
else if(starts_with($key, '-')){
$flags[] = $key;
}
}
return implode(' ', $args) . ' '
.implode(' ', $opts). ' '
.implode(' ', $flags);
}
}
现在,您应该可以传递任何 artisan 特殊选项,例如无交互。
public function handle(){
$options = [
'argument' => $argument,
'--option' => $options, // options should be preceded by --
'-n' => null // no-interaction option
];
$command = 'your:command';
$output = $this->executeArtisanCommand($command, $options);
echo $output;
}
你可以从这里下载特征gist
与mpyw/streamable-console: Call interactive artisan command using arbitrary stream:
$this->usingInputStream("yes\nno\n")->call('command:name');