# 如何使用提問
[TOC]
## 要求用戶確認
假設你希望在一個方法被執行之前先行確認。添加以下代碼到你的命令中:
```
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$question = $output->confirm($input, 'Continue with this action?', false);
if (!$question) {
return;
}
}
}
```
本例中,用戶會被問到 "Continue with this action?"。如果用戶回答 `yes` 或者 `y` 開頭的字符串 它就返回 `true`,如果答案是 `no` 或者 `n` 開頭的字符串的話,它就返回 `false`。 `confirm()` 的第三個參數,是當用戶不鍵入任何有效input時,返回的默認值。如果沒有提供第三個參數, true 會被取用。
現在,你可以傳入用戶名到命令中:
```bash
$ php think demo:question
Continue with this action? (yes/no) [no]:
> yes
```
如果我們想輸入其他的字符(如:`j`) 也表示 `yes` 的意思,該怎么處理呢?
前面的示例調用的是 Output 中的方法, 我們可以參考其代碼:
```php
// ...
public function confirm(Input $input, $question, $default = true)
{
return $this->askQuestion($input, new Confirmation($question, $default));
}
// ...
protected function askQuestion(Input $input, Question $question)
{
$ask = new Ask($input, $this, $question);
$answer = $ask->run();
if ($input->isInteractive()) {
$this->newLine();
}
return $answer;
}
// ...
```
我們再看下類 `\think\console\output\question\Confirmation` 的構造方法 `__construct()`
```
// ...
public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i')
{
// ...
}
```
我們可以看到構造器的第三個參數中自定義一個正則表達式,用于判斷答案是否是 "yes"的意思(默認的正則表達式是 `/^y/i`)。
我們將上面的示例代碼進行改造:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\output\Ask;
use think\console\output\question\Confirmation;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$question = new Confirmation('Continue with this action?', false, '/^(y|j)/i');
$ask = new Ask($input, $output, $question);
$answer = $ask->run();
// if ($input->isInteractive()) {
// // 輸出空行
// $output->newLine();
// }
if (!$answer) {
$output->writeln('false');
return;
}
$output->writeln('true');
}
}
```
測試一下成果:
```bash
php think demo:question
Continue with this action? (yes/no) [no]:
> j
true
```
## 詢問用戶信息
你也可以用超過一個簡單的 `yes/no` 的答案來向用戶提問。例如,如果你想要知道 behavior 的名稱,可以把下面代碼添加到你的命令中:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\output\Ask;
use think\console\output\question\Confirmation;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$behavior = $output->ask($input, 'Please enter the name of the behavior', 'initBehavior');
$output->writeln($behavior);
}
}
```
用戶會被問 "Please enter the name of the behavior"。我們可以輸入一些會被 ask() 方法返回的名稱。如果用戶留空,默認值 (此處是 initBehavior) 會被返回。
## 讓用戶從答案列表中選擇
如果你預定義了一組答案讓用戶從中選擇,你可以使用 `choice`,它確保用戶只能從預定義列表中輸入有效字符串:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$color = $output->choice(
$input,
'Please select your favorite color (defaults to red)',
['red', 'blue', 'yellow'],
'red'
);
$output->writeln('You have just selected: ' . $color);
}
}
```
現在,我們可以傳入顏色到命令中:
```bash
$ php think demo:question
Please select your favorite color (defaults to red) [red]:
[0] red
[1] blue
[2] yellow
> 0
red
$ php think demo:question
Please select your favorite color (defaults to red) [red]:
[0] red
[1] blue
[2] yellow
> 10
Value "10" is invalid
Please select your favorite color (defaults to red) [red]:
[0] red
[1] blue
[2] yellow
>
```
上面的示例可以看到,當我們輸入的數據超出范圍后,出現錯誤并讓我們重新輸入, 但錯誤提示不怎么友好,因此我們需要對其進行改造:
我們看下 `choice()` 代碼:
```php
// ...
public function choice(Input $input, $question, array $choices, $default = null)
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default];
}
return $this->askQuestion($input, new Choice($question, $choices, $default));
}
protected function askQuestion(Input $input, Question $question)
{
$ask = new Ask($input, $this, $question);
$answer = $ask->run();
if ($input->isInteractive()) {
$this->newLine();
}
return $answer;
}
// ...
```
我們可以看到,choice 中使用了 `\think\console\output\question\Choice`,分析代碼可以看到,修改錯誤提示的方法是:
```php
// ...
/**
* 設置錯誤提示信息
* @param string $errorMessage
* @return self
*/
public function setErrorMessage($errorMessage)
{
// ...
}
// ...
```
我們將上面的示例代碼進行改造:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\output\Ask;
use think\console\output\question\Choice;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$question = new Choice(
'Please select your favorite color (defaults to red)',
['red', 'blue', 'yellow'],
0
);
$question->setErrorMessage('Color %s is invalid.');
$ask = new Ask($input, $output, $question);
$color = $ask->run();
$output->writeln('You have just selected: '.$color);
}
}
```
現在,你可以傳入顏色到命令中:
```bash
$ php think demo:question
Please select your favorite color (defaults to red) [red]:
[0] red
[1] blue
[2] yellow
> 0
red
$ php think demo:question
Please select your favorite color (defaults to red) [red]:
[0] red
[1] blue
[2] yellow
> 10
Color 10 is invalid
Please select your favorite color (defaults to red) [red]:
[0] red
[1] blue
[2] yellow
>
```
默認被選中的選項由構造器的第三個參數提供。默認是 `null`,代表沒有默認的選項。
如果用戶輸入了無效字符串,會顯示一個錯誤信息,用戶會被要求再一次提供答案,直到他們輸入一個有效字符串,或是達到了嘗試上限為止。默認的最大嘗試次數是 `null`,代表可以無限次嘗試。你可以使用 setErrorMessage() 定義自己的錯誤信息。
### 多選
有時,可以給出多個答案。 `Choice` 使用逗號分隔的值,提供了此項功能。默認是禁用的,開啟它可使用 `setMultiselect()`:
> `\think\Console` 中沒有提供該方法
現在,你可以傳入顏色到命令中:
```bash
$ php think demo:question
Please select your favorite color (defaults to red) [red, blue]:
[0] red
[1] blue
[2] yellow
> 1,2
You have just selected: blue,yellow
```
如果用戶不輸入任何內容,結果是: `You have just selected: red, blue`。
## 自動完成
對于給定的問題,你也可以提供一個默認的答案數組。它們將根據用戶的操作而自動完成:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\output\Ask;
use think\console\output\Question;
use think\console\output\question\Choice;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$question = new Question('Please enter the name of a color', 'pink');
$question->setAutocompleterValues(['red', 'blue', 'yellow']);
$ask = new Ask($input, $output, $question);
$color = $ask->run();
$output->writeln('You have just selected: ' . $color);
}
}
```
## 隱藏用戶輸入
你也可以在問問題時隱藏輸入。這對密碼來說極為方便:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\output\Ask;
use think\console\output\Question;
use think\console\output\question\Choice;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$password = $output->askHidden($input, 'What is the database password?');
$output->writeln($password);
}
}
```
現在,你可以傳入登錄密碼到命令中:
```bash
$ php think demo:question
What is the login password?:
>
123456
```
## 驗證答案
你甚至可以驗證答案。例如,前面例子中你曾詢問過 behavior 名稱。假設我們設置了后綴 Behavior,那么我們可以使用 `setValidator()` 方法 來驗證它:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$name = $output->ask($input,
'Please enter the name of the behavior',
'demoBehavior',
function ($answer) {
if ('Behavior' !== substr($answer, -8)) {
throw new \RuntimeException(
'The name of the behavior should be suffixed with \'Behavior\''
);
}
return $answer;
});
$output->writeln($name);
}
}
```
`$validator` 是一個 `callback` ,專門處理驗證。它在有錯誤發生時應拋出一個異常。異常信息會被顯示在控制臺中,所以在里面放入一些有用的信息是一個很好的實踐。回調函數在驗證通過時,應該返回用戶的`input`。
如果我們想設置最大提問次數該怎么辦呢?
你可以用 `setMaxAttempts()` 方法來設置(驗證失敗時的)最大的提問次數。如果達到最大值,它將使用默認值。使用 `null` 代表可以無限次嘗試回答(直到驗證通過)。用戶將被始終提問,直到他們提供了有效答案為止,也只有輸入有效時命令才會繼續執行。
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\output\Ask;
use think\console\output\Question;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
// ...
$question = new Question('Please enter the name of the behavior', 'demoBehavior');
$question->setValidator(function ($answer) {
if ('Behavior' !== substr($answer, -8)) {
throw new \RuntimeException(
'The name of the behavior should be suffixed with \'Behavior\''
);
}
return $answer;
});
// 設置最大提問數
$question->setMaxAttempts(2);
$ask = new Ask($input, $output, $question);
$name = $ask->run();
$output->writeln($name);
}
}
```
## 驗證一個隱藏的響應
你也可以在隱藏(答案輸入)的提問中使用validator:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
$name = $output->askHidden($input,
'Please enter your password',
'demoBehavior',
function ($value) {
if (trim($value) == '') {
throw new \Exception('The password can not be empty');
}
return $value;
});
$output->writeln($name);
}
}
```
或者使用下面的方式:
```php
<?php
namespace app\console;
use think\console\Command;
use think\console\Output;
use think\console\Input;
use think\console\output\Ask;
use think\console\output\Question;
class QuestionCommand extends Command
{
protected function configure()
{
$this
// 命令的名字("think" 后面的部分)
->setName('demo:question')
->setDescription('Test question');
}
public function execute(Input $input, Output $output)
{
// ...
$question = new Question('Please enter your password');
$question->setValidator(function ($value) {
if (trim($value) == '') {
throw new \Exception('The password can not be empty');
}
return $value;
});
$question->setHidden(true);
$question->setMaxAttempts(2);
$ask = new Ask($input, $output, $question);
$name = $ask->run();
$output->writeln($name);
}
}
```