Помощник Question

QuestionHelper предоставляет функции для того, чтобы запросить у пользователя больше информации. Он включён в набор помощников по умолчанию, который можете увидеть, вызвав getHelperSet():

1
$helper = $this->getHelper('question');

Помощник Question Helper имеет единственный метод ask(), которому нужен экземпляр InputInterface в качестве первого аргумента, экземпляр OutputInterface - в качестве второго, и Question в качестве последнего аргумента.

Запрос подтверждения от пользователя

Предположим, что вы хотите подтвердить действие перед тем, как его выполнять. Добавьте в вашу команду следующее:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// ...
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class YourCommand extends Command
{
    // ...

    public function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = $this->getHelper('question');
        $question = new ConfirmationQuestion('Continue with this action?', false);

        if (!$helper->ask($input, $output, $question)) {
            return;
        }
    }
}

В этом случае, пользователя спросят "Вы хотите продолжить это действие?". Если пользователь ответит y, то вернётся true, а false``вернётся, если ответ будет ``n. Второй аргумент метода __construct() - это значение по умолчанию, которое стоит вернуть, если пользователь введёт невалидное значение ввода. Если второй аргумент не предоставлен, то предполагается true.

Tip

Вы можете настроить используемое регулярное выражение так, чтобы проверять, означает ли ответ "yes", в третьем аргументе консруктора. Например, чтобы разрешить всё, что начинается с y или j, вам нужно установить его так:

1
2
3
4
5
$question = new ConfirmationQuestion(
    'Continue with this action?',
    false,
    '/^(y|j)/i'
);

Регулярное выражение по умолчанию - /^y/i.

Запрос информации у пользователя

Вы также можете задать вопрос с более сложным ответом, чем да/нет. Например, если вы хотите узнать имя пакета, вы можете добавить в вашу команду следующее:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use Symfony\Component\Console\Question\Question;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');

    $bundleName = $helper->ask($input, $output, $question);
}

Пользователя попросят "Пожалуйста, введите имя пакета". Он может ввести какое-то имя, которое будет возвращено методом ask(). Если он оставит поле пустым, то будет возвращено значение по умолчанию (здесь - AcmeDemoBundle).

Позвольте пользователю выбирать из списка ответов

Если у вас есть предопределённый набор ответов, из которого пользователь может выбирать, вы можете использовать ChoiceQuestion, который гарантирует, что пользователь может вводить только валидную строку из предопределённого списка:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use Symfony\Component\Console\Question\ChoiceQuestion;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');
    $question = new ChoiceQuestion(
        'Пожалуйста, выберите ваш любимый цвет (по умолчанию красный)',
        array('red', 'blue', 'yellow'),
        0
    );
    $question->setErrorMessage('Color %s is invalid.');

    $color = $helper->ask($input, $output, $question);
    $output->writeln('Вы только что выбрали: '.$color);

    // ... сделать что-то с цветом
}

Опция, выбранная по умолчанию, предоставляется третьим аргументом конструктора. По умолчанию она null, что означает, что опции по умолчанию нет.

Если пользователь вводит невалидную строку, отображается сообщение об ошибке и пользователя попросят предоставить ответ ещё раз, до тех пор, пока он не введёт влидную строку или не достигнет максимального количества попыток. Значение по умолчанию для максимального количества попыток - null, что означает бесконечное количество попыток. Вы можете определить ваше собственное сообщение об ошибке, используя setErrorMessage().

Множественный выбор

Иногда можно предоставить несколько ответов. ChoiceQuestion предлагает такую функцию, используя значения, разделённые запятыми. По умолчанию это отключено, чтобы подключить, используйте setMultiselect():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use Symfony\Component\Console\Question\ChoiceQuestion;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');
    $question = new ChoiceQuestion(
        'Пожалуйста, выберите ваши любимые цвета (по умолчанию красный и синий)',
        array('red', 'blue', 'yellow'),
        '0,1'
    );
    $question->setMultiselect(true);

    $colors = $helper->ask($input, $output, $question);
    $output->writeln('Вы только что выбрали: ' . implode(', ', $colors));
}

Теперь, когда пользователь вводит 1,2, результатом будет: Вы только что выбрали: синий, желтый.

Если пользователь не введёт ничего, то результат будет: Вы только что выбрали: красный, синий.

Автозаполнение

Вы также можете указать массив потенциальных ответов для заданных вопросов. Они будут автозаполнены во время того, как пользователь будет печатать:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use Symfony\Component\Console\Question\Question;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');

    $bundles = array('AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle');
    $question = new Question('Пожалуйста, введите имя пакета', 'FooBundle');
    $question->setAutocompleterValues($bundles);

    $bundleName = $helper->ask($input, $output, $question);
}

Скрытие ответов пользователя

Вы также можете задавать вопрос и скрывать ответ. Это особенно полезно для паролей:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use Symfony\Component\Console\Question\Question;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');

    $question = new Question('Какой пароль БД?');
    $question->setHidden(true);
    $question->setHiddenFallback(false);

    $password = $helper->ask($input, $output, $question);
}

Caution

Когда вы запрашиваете скрытый ответ, Symfony будет использовать либо бинарный режим изменения stty, либо другой фокус для скрытия ответа. Если ничего недоступно, то будет использован резервный план и ответ будет видимым, кроме случаев, если выустановите это поведение, как false, используя setHiddenFallback(), как в примере выше. В этом случае, будет вызвано RuntimeException.

Note

Команда stty используется для получения и установки свойств командной строки (вроде получения количества строк и столбцов или скрытия текста ввода). В системах Windows, команда stty может генерировать тарабарский вывод и коверкать текст ввода. Если это ваш случай, отключите это с помощью данной команды:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Question\ChoiceQuestion;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');
    QuestionHelper::disableStty();

    // ...
}

Нормализация ответа

Перед валидацией ответа вы можете "нормализовать" его, чтобы исправить мелкие ошибки или подстроить его по необходимости. Например, в предыдущем примере вы спрашивали имя пакета. В случае, если пользователь добавляет пробелы вокруг имени по ошибке, вы можете обрезать имя перед его валидацией. Чтобы сделать это, сконфигурируйте нормализатор, используя метод setNormalizer():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Symfony\Component\Console\Question\Question;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');

    $question = new Question('Пожалуйста, введите имя пакета', 'AcmeDemoBundle');
    $question->setNormalizer(function ($value) {
        // $value здесь может быть null
        return $value ? trim($value) : '';
    });

    $bundleName = $helper->ask($input, $output, $question);
}

Caution

Вначале вызывается нормализатор, а возвращённое значение используется в качестве ввода валидатора. Если ответ невалиден, не вызывайте исключений в нормализаторе и позвольте валидатору обработать эти ошибки.

Валидация ответа

Вы можете даже валидировать ответ. К прмиеру, в предыдущем примере вы спрашивали имя пакета. Следуя соглашениию именования Symfony, оно должно иметь суффикс Bundle. Вы можете валидировать это, используя метод setValidator():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Symfony\Component\Console\Question\Question;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');

    $question = new Question('Пожалуйста, введите имя пакета', 'AcmeDemoBundle');
    $question->setValidator(function ($answer) {
        if (!is_string($answer) || 'Bundle' !== substr($answer, -6)) {
            throw new \RuntimeException(
                'Имя пакета должно иметь суффикс \'Bundle\''
            );
        }

        return $answer;
    });
    $question->setMaxAttempts(2);

    $bundleName = $helper->ask($input, $output, $question);
}

$validator - это обратный вызов, который работает с валидацией. Он должен вызвать исключение, если что-то пошло не так. Сообщение об исключении отображается в консоли, поэтому хорошей практикой будет разместить в нём полезную информацию. Функция обратного вызова должна также возвращать значение ввода пользователя, если валидация была успешной.

Вы можете установить максимальное количество повторений вопроса с помощью метода setMaxAttempts(). Если вы достигните максимального количества, то будет использовано значение по умолчанию. Использования null означает бесконечное количество попыток. Пользователя будут спрашивать до тех пор, пока он не предоставит валидный ответ, и только тогда он сможет продолжать.

Валидация скрытых ответов

Вы также можете использовать валидатор со скрытым вопросом:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Component\Console\Question\Question;

// ...
public function execute(InputInterface $input, OutputInterface $output)
{
    // ...
    $helper = $this->getHelper('question');

    $question = new Question('Пожалуйста, введите ваш пароль');
    $question->setValidator(function ($value) {
        if (trim($value) == '') {
            throw new \Exception('Пароль не может быть пустым');
        }

        return $value;
    });
    $question->setHidden(true);
    $question->setMaxAttempts(20);

    $password = $helper->ask($input, $output, $question);
}

Тестирования команды, ожидающей ввода

Если вы хотите написать модульный тест для команды, ожидающей какого-либо ввода из командной строки, то вам нужно установить ввод, который будет ожидать команда:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Tester\CommandTester;

// ...
public function testExecute()
{
    // ...
    $commandTester = new CommandTester($command);

    // Эквивалентно вводу пользователем "Test" и нажатию ENTER
    $commandTester->setInputs(array('Test'));

    // Эквивалентно вводу пользователем  "This", "That" и нажатию ENTER
    // Может быть использовано для ответа на два разных вопроса, к примеру
    $commandTester->setInputs(array('This', 'That'));

    // Для симуляции положительного ответа на вопрос подтверждения, будет работать
    // дополнительный ввод "yes"
    $commandTester->setInputs(array('yes'));

    $commandTester->execute(array('command' => $command->getName()));

    // $this->assertRegExp('/.../', $commandTester->getDisplay());
}

Вызвав setInputs(), вы имитируете то, что консоль будет делать внутренне со всем вводом пользователя через CLI. Этот метод берёт массив в качестве единственного аргумента для каждого ожидаемого командой ввода, вместе со строкой, представляющей то, что напечатает пользователь. Таким образом вы можете тестировать любое взаимодействие пользователя (даже сложные), передавая соответствующие вводы.

Note

Класс CommandTester автоматически симулирует нажатие пользователем ENTER после каждого ввода, необходимости передавать дополнительный ввод нет.

Caution

В системах Windows Symfony использует специальную бинарность, чтобы реализовать скрытые вопросы. Это означает, что такие вопросы не используют объект консоли по умолчанию Input, и следовательно вы не можете тестировать его в Windows.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.