Как вызвать консольную команду artisan с взаимодействием
В настоящее время я создаю консольную команду php artisan в проекте Laravel 5.1 и хочу вызвать другую консольную команду из своей консольной команды. Эта сторонняя команда, которую я хочу вызвать, не принимает никаких параметров или аргументов, а скорее получает ввод с помощью интерактивных вопросов.
Я знаю, что могу вызвать команду с параметрами и аргументами, подобными этому:
$это->вызов ('команда:имя', ['аргумент'=> 'foo', '--опция'=> 'панель']);
Я также знаю, что я может вызывать интерактивную команду без таких взаимодействий из командной строки:
Команда Php artisan: имя --без взаимодействия
Но как я могу ответить на эти интерактивные вопросы из своей команды?
Я хотел бы сделать что-то вроде приведенного ниже (псевдокод).
$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)
не принимает третий параметр обратного вызова.
Как я могу отвечать на интерактивные вопросы при звонке консольная команда из консольной команды?
3 answers
У меня есть другое решение, оно заключается в вызове команды symfony, выполняющей "php artisan", вместо использования субкоманд artisan. Я думаю, что это лучше, чем исправлять сторонний код.
Вот черта, которая управляет этим.
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);
}
}
Теперь вы должны иметь возможность передавать любые специальные опции ремесленника, такие как отсутствие взаимодействия.
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;
}
Вы можете скачать эту черту из этой сути
Вот как я это сделал.
Осторожно: это исправляет основной класс Symfony QuestionHelper@doAsk
, и хотя этот код отлично подходит для моих целей (в настоящее время я просто проверяю концепцию), этот код, вероятно, не должен выполняться в любой производственной среде.
Я пока не принимаю свой собственный ответ, хотел бы знать, есть ли лучший способ сделать это.
Ниже предполагается установка Laravel 5.1.
-
Первый композитор - требует Пэчворк пакет. Я использую это для расширения функциональности этого метода класса Symfony.
composer require antecedent/patchwork
-
Отредактируйте
bootstrap/app.php
и добавьте следующее сразу после создания приложения. (Пэчворк не загружается автоматически)if($app->runningInConsole()) { require_once(__DIR__ . '/../vendor/antecedent/patchwork/Patchwork.php'); };
-
Добавьте следующие два оператора use в начало класса консольных команд
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
-
Дополните/исправьте
QuestionHelper@doAsk
с помощью этих вспомогательных методов в классе команд консоли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(); }
Laravel 5.7 наконец-то решил эту проблему навсегда с помощью встроенного способа обработки интерактивного тестирования команд!