Как вызвать консольную команду 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) не принимает третий параметр обратного вызова.

Как я могу отвечать на интерактивные вопросы при звонке консольная команда из консольной команды?

Author: Pepijn Olivier, 2015-12-10

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

Вы можете скачать эту черту из этой сути

 3
Author: motia, 2016-08-01 20:50:17

Вот как я это сделал.

Осторожно: это исправляет основной класс 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();
    }
    
 1
Author: Pepijn Olivier, 2015-12-11 23:55:22

Laravel 5.7 наконец-то решил эту проблему навсегда с помощью встроенного способа обработки интерактивного тестирования команд!

Консоль ремесленника Laravel 5.7

 1
Author: Juha Vehnia, 2018-09-26 02:20:13