【问题标题】:How to call an artisan console command with an interaction如何通过交互调用工匠控制台命令
【发布时间】:2016-03-15 22:21:06
【问题描述】:

我目前正在一个 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)不接受第三个回调参数。

从控制台命令调用控制台命令时如何回答交互式问题?

【问题讨论】:

  • 我对这个问题很感兴趣。我已经在 Laravel 沙盒中玩了一个小时左右,看看我是否可以让它工作。我得出了一个不幸的结论,即实现此功能将是大量的工作。目前,无法通过在另一个调用中进行的调用传递任何内容,而无需通过参数提供默认值。
  • @DavidBarker 我通过在 Symfony 类方法 QuestionHelper@doAsk 中添加 5 行代码使其工作(现在)。我目前正在集思广益,考虑如何在不触及核心的情况下增强这种方法,考虑使用runkit_method_redefine()。如果/当我成功时会更新这篇文章。欢迎提出建议。
  • 仍然非常老套,但如果没有 Symfony 组件中可用的功能,我想不出或看到任何其他方法来实现您需要的功能。

标签: php laravel console command laravel-artisan


【解决方案1】:

mpyw/streamable-console: Call interactive artisan command using arbitrary stream:

$this->usingInputStream("yes\nno\n")->call('command:name');

【讨论】:

    【解决方案2】:

    我有另一个解决方案,它是调用 symfony 命令执行 'php artisan' 而不是使用 artisan 子命令。我认为这比修补 3rd 方代码要好。

    这是一个管理这个的特征。

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

    您可以从此gist下载特征

    【讨论】:

    • 这样更好;我接受你的回答是最好的
    • @PepijnOlivier 除非你没有
    【解决方案3】:

    我是这样做的。

    注意:这修补了核心 Symfony 类 QuestionHelper@doAsk,虽然这段代码可以正常运行(我目前只是做概念证明),但这段代码可能不应该在任何生产环境中运行。 我还没有接受我自己的答案,想知道是否有更好的方法来做到这一点。

    以下假设安装了 Laravel 5.1。

    • 首先作曲家需要 Patchwork 包。我正在使用它来增强 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();
      }
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-08
      • 1970-01-01
      • 2015-07-09
      • 2021-12-31
      • 1970-01-01
      • 2018-02-21
      • 2016-10-05
      • 1970-01-01
      相关资源
      最近更新 更多