Введення консолі (аргументи та опції)

Дата оновлення перекладу 2022-12-12

Введення консолі (аргументи та опції)

Найцікавішою частиною команд є аргументи та опції, які ви можете зробити доступними. Ці аргументи та опції дозволяють вам передавати динамічну інформацію з терміналу в команду.

Використання аргументів команд

Аргументи - це рядки, розділені пробілами, які йдуть після самого імені команди. Вони впорядковані та можуть бути обовʼязковими або необовʼязковими. Наприклад, щоб додати необовʼязковий аргумент last_name в команду і зробити аргумент name
обовʼязковим:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ...
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;

class GreetCommand extends Command
{
    // ...

    protected function configure(): void
    {
        $this
            // ...
            ->addArgument('name', InputArgument::REQUIRED, 'Who do you want to greet?')
            ->addArgument('last_name', InputArgument::OPTIONAL, 'Your last name?')
        ;
    }
}

Тепер у вас є доступ до аргументу last_name у вашій команді:

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

class GreetCommand extends Command
{
    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $text = 'Hi '.$input->getArgument('name');

        $lastName = $input->getArgument('last_name');
        if ($lastName) {
            $text .= ' '.$lastName;
        }

        $output->writeln($text.'!');

        return Command::SUCCESS;
    }
}

Тепер команда може бути використана будь-яким з наступних способів:

1
2
3
4
5
$ php bin/console app:greet Fabien
Привіт, Фабієн!

$ php bin/console app:greet Fabien Potencier
Привіт, Фабієн Потенсьє!

Також можливо дозволлити аргументи використовувати список значень (уявіть, що ви хочете привітатися з усіма вашими друзями). Лише останній аргумент може бути списком:

1
2
3
4
5
6
7
8
$this
    // ...
    ->addArgument(
        'names',
        InputArgument::IS_ARRAY,
        'З ким ви хочете привітатися (розділіть декілька імен пробілами)?'
    )
;

Щоб використати це, просто вкажіть стільки імен, скільки хочете:

1
$ php bin/console app:greet Fabien Ryan Bernhard

Ви можете отримати доступ до аргументу names, як до масиву:

1
2
3
4
$names = $input->getArgument('names');
if (count($names) > 0) {
    $text .= ' '.implode(', ', $names);
}

Існує три варіанти аргументів, які ви можете використати:

InputArgument::REQUIRED
Аргумент обовʼязковий. Команда не буде виконана, якщо цього аргументу немає;
InputArgument::OPTIONAL
Аргумент необовʼязковий і тому може бути опущений. Це поведінка аргументу за замовчуванням;
InputArgument::IS_ARRAY
Аргумент може містити будь-яку кількість значень. З цієї причини, він може бути використаний наприкінці списку аргументів.

Ви можете комбінувати IS_ARRAY з REQUIRED і OPTIONAL таким чином:

1
2
3
4
5
6
7
$this
    // ...
    ->addArgument(
        'names',
        InputArgument::IS_ARRAY | InputArgument::REQUIRED,
        'С кем вы хотите поздороваться (разделите несколько имён пробелом)?'
    );

Використання опцій команд

На відміну від аргументів, опції не впорядковані (тобто ви можете вказувати їх у будь-якому порядку) і вказуються з двома дефісами (наприклад, --yell). Опції завжди обовʼязкові і можуть бути налаштовані так, щоб приймати значення (наприклад, --dir=src) або просто як булевий прапорець без значення (наприклад, --yell).

Наприклад, додайте нову опцію до команди, яка може бути використана для вказання того, скільки разів поспіль повинно бути надруковане повідомлення:

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

$this
    // ...
    ->addOption(
        // це імʼя, яке користувачі повинні надрукувати, щоб передати цю опцію (наприклад, --iterations=5)
        'iterations',
        // це необовʼязкове скорочення імені опції, яке зазвичай є просто літерою
        // (наприклад, `i`, щоб користувачі передавали його як `-i`); використовуйте це для часто використовуваних опцій
        // або опцій з довгими іменами
        null,
        // це тип опції (наприклад, вимагає значення, може бути передана більше одного разу, та ін.)
        InputOption::VALUE_REQUIRED,
        // опис опції, відображений при відображенні допомоги команди
        'Скільки разів має бути надруковане повідомлення?',
        // значення опції за замовчуванням (для тих, які дозволяють передачу значень)
        1
    )
;

Далі, використайте це в команді, щоб надрукувати повідомленння декілька разів:

1
2
3
for ($i = 0; $i < $input->getOption('iterations'); $i++) {
    $output->writeln($text);
}

Тепер, коли ви виконаєте команду, ви можете за бажанням вказати прапорець --iterations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# не надано --iterations, використовується (1) за замовчуванням
$ php bin/console app:greet Fabien
Привіт, Фабієн!

$ php bin/console app:greet Fabien --iterations=5
Привіт, Фабієн!
Привіт, Фабієн!
Привіт, Фабієн!
Привіт, Фабієн!
Привіт, Фабієн!
Привіт, Фабієн!

# порядок опцій неважливий
$ php bin/console app:greet Fabien --iterations=5 --yell
$ php bin/console app:greet Fabien --yell --iterations=5
$ php bin/console app:greet --yell --iterations=5 Fabien

Tip

Ви також можете оголосити скорочення з однієї літери, яке ви можете викликати за допомогою одного дефісу, наприклад, -i:

1
2
3
4
5
6
7
8
9
10
$this
    // ...
    ->addOption(
        'iterations',
        'i',
        InputOption::VALUE_REQUIRED,
        'Скільки разів має бути надруковане повідомлення?',
        1
    )
;

Відмітьте, що щоб відповідати стандарту docopt, довгі опції можуть вказувати свої значення після пробілу або знаку = (наприклад, --iterations 5 або --iterations=5), але короткі опції можуть використовувати лише пробіли або взагалі не використовувати ніякого розділювача (наприклад, -i 5 або -i5).

Caution

Хоча можливо відділити опцію від її значення за допомогою пробілу, використання цієї форми призводить до двозначності, якщо опція зʼявляється перед іменем команди. Наприклад, php bin/console --iterations 5 app:greet Fabien двозначна; Symfony інтерпретує 5 як імʼя команди. Щоб уникнути такої ситуації, завжди розміщуйте опції після імені команди, або уникайте використання пробілу для відділення імʼя опції від її значення.

Існує пʼять варіантів опцій, які ви можете використовувати:

InputOption::VALUE_IS_ARRAY
Ця опція примає декілька значень (наприклад, --dir=/foo --dir=/bar);
InputOption::VALUE_NONE
Не приймає введення для цієї опції (наприклад, --yell). Це поведінка опцій за замовчуванням;
InputOption::VALUE_REQUIRED
Це значення обовʼязкове (наприклад, --iterations=5), але сама опція все ще не є обовʼязковою;
InputOption::VALUE_OPTIONAL
Ця опція може мати або не мати значення (наприклад, --yell або --yell=loud).
InputOption::VALUE_NEGATABLE
Приймає або прапорець (наприклад, --yell) або його інверсію (наприклад, --no-yell).

Ви маєте комбінувати VALUE_IS_ARRAY з VALUE_REQUIRED або VALUE_OPTIONAL таким чином:

1
2
3
4
5
6
7
8
9
10
$this
    // ...
    ->addOption(
        'colors',
        null,
        InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
        'Які кольори вам подобаються?',
        ['blue', 'red']
    )
;

Опції з необовʼязковими аргументами

Вам нічого не забороняє створити команду з опцією, яка опціонально приймає значення, але це трохи складно. Розгляньте цей приклад:

1
2
3
4
5
6
7
8
9
10
11
12
// ...
use Symfony\Component\Console\Input\InputOption;

$this
    // ...
    ->addOption(
        'yell',
        null,
        InputOption::VALUE_OPTIONAL,
        'Мені кричати при привітанні?'
    )
;

Ця опція може бути використана трьома способами: greet --yell, greet --yell=louder, та greet. Однак, важко розрізнити передачу опції без значення (greet --yell) і відсутність передачі опції (greet).

Щоб вирішити цю проблему, вам потрібно встановити значення опції за замовчуванням як false:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
use Symfony\Component\Console\Input\InputOption;

$this
    // ...
    ->addOption(
        'yell',
        null,
        InputOption::VALUE_OPTIONAL,
        'Мені кричати при привітанні?',
        false // це нове значення за замовчуванням замість null
    )
;

Тепер можливо розрізнювати відсутність передачі опції та відсутність передачі значення для неї:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$optionValue = $input->getOption('yell');
if (false === $optionValue) {
    // в цьому випадку, опція не була передана при виконанні команди
    $yell = false;
    $yellLouder = false;
} elseif (null === $optionValue) {
    // в цьому випадку, опція була передана при виконанні команди, але
    // їй не було надано значення
    $yell = true;
    $yellLouder = false;
} else {
    // в цьому випадку, опція була передана при виконанні команди і їй
    // було надане якесь конкретне значення
    $yell = true;
    if ('louder' === $optionValue) {
        $yellLouder = true;
    } else {
        $yellLouder = false;
    }
}

Код вище можна спростити таким чином, так як false !== null:

1
2
3
$optionValue = $input->getOption('yell');
$yell = ($optionValue !== false);
$yellLouder = ($optionValue === 'louder');

Додавання заповнення значень аргументів/опцій

Якщо встановлено заповнення Консолі , імена команд та опцій будуть автоматично заповнені оболонкою. Однак, ви також можете реалізувати заповнення значеня для введення у ваших командах. Наприклад, ви можете захотіти заповнити всі імена користувачів з бази даних в аргументі name вашої команди привітання.

Щоб досягти цього, використайте 5ий аргумент addArgument()/addOption:

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
26
27
28
29
30
31
// ...
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;

class GreetCommand extends Command
{
    // ...
    protected function configure(): void
    {
        $this
            ->addArgument(
                'names',
                InputArgument::IS_ARRAY,
                'Кого ви хочете привітати (розділіть багато імен пробілами)?',
                null,
                function (CompletionInput $input) {
                    // значення користувача вже надруковано, наприклад, при введенні "app:greet Fa" до
                    // натискання Tab, це міститиме "Fa"
                    $currentValue = $input->getCompletionValue();

                    // отримати список імен користувачів звідкись (наприклад, база даних)
                    // ви можете використати $currentValue, щоб відфільтрувати імена
                    $availableUsernames = ...;

                    // потім запропоновані імена користувачів як значення
                    return $availableUsernames;
                }
            )
        ;
    }
}

6.1

Аргумент addOption()/addArgument() було представлено в Symfony 6.1.
До цієї версії вам потрібно було перевизначати метод команди complete().

Це все, що вам потрібно! Припускаючи, що "Fabien" та "Fabrice" існують, натискання табу після введення app:greet Fa надасть вам ці імена в якості пропозиції.

Tip

Скрипт оболонки доступний для обробки величезної кількості пропозицій та автоматично відфільтрує запропоновані значення, засновуючись на існуючому введенні від користувача. Вам не потрібно реалізовувати ніякої логіки фільтрації у команді.

Ви можете використати CompletionInput::getCompletionValue(), щоб отримати поточне введення, якщо це допоможе покращити продуктивність (наприклад, зменшивши кількість рядків, вилучених з бази даних).

Тестування скрипту заповнення

Компонент Консоль постачається зі спеціальним класом CommandCompletionTester`, щоб допомогти вам модульно тестувати логіку заповнення:

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

class GreetCommandTest extends TestCase
{
    public function testComplete()
    {
        $application = new Application();
        $application->add(new GreetCommand());

        // створіть нового тестувальника з командою привітання
        $tester = new CommandCompletionTester($application->get('app:greet'));

        // закінчіть введення без існуючого введення (пустий рядок представляє
        // позицію курсору)
        $suggestions = $tester->complete(['']);
        $this->assertSame(['Fabien', 'Fabrice', 'Wouter'], $suggestions);

        // закінчіть введення з "Fa" в якості введення
        $suggestions = $tester->complete(['Fa']);
        $this->assertSame(['Fabien', 'Fabrice'], $suggestions);
    }
}