# 如何使用提问
[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);
}
}
```