Як оформити консольну команду

Дата оновлення перекладу 2023-08-25

Як оформити консольну команду

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Command/GreetCommand.php
namespace App\Command;

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
    {
        $output->writeln([
            '<info>Lorem Ipsum Dolor Sit Amet</>',
            '<info>==========================</>',
            '',
        ]);

        // ...
    }
}

Відображення простого заголовку вимагає трьох рядків коду: щоб змінити фронт-контролер, підкреслити зміст та залишити додатковий пустий рядок після заголовку. Оформлення необхідно для всіх добре продуманих команд, але воно занадто ускладнює код.

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

Базове використання

У вашій команді інстанціюйте клас SymfonyStyle та передайте змінні $input і $output в якості його аргументів. Далі ви можете почати використовувати будь-який з його помічників, як, наприклад, title(), який відображає заголовок команди:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Command/GreetCommand.php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class GreetCommand extends Command
{
    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->title('Lorem Ipsum Dolor Sit Amet');

        // ...
    }
}

Методи-помічники

Клас SymfonyStyle визначає деякі методи-помічники, які охоплюють найрозповсюдженіші дії, виконувані консольними командами.

Методи заголовків

title()

Відображає заданий рядок як заголовок команди. Цей метод мав би виконуватися у даній команді лише один раз, але ніщо не зупиняє вас від його повторного використання:

1
$io->title('Lorem ipsum dolor sit amet');
section()

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

1
2
3
4
5
6
7
$io->section('Adding a User');

// ...

$io->section('Generating the Password');

// ...

Методи змісту

text()

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

1
2
3
4
5
6
7
8
9
10
11
// використовуйте прості рядки для коротких повідомлень
$io->text('Lorem ipsum dolor sit amet');

// ...

// розгляньте використання масивів при відображенні довгих повідомлень
$io->text([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
]);
listing()

Відображає невпорядкований список елементів, переданих у вигляді масиву:

1
2
3
4
5
$io->listing([
    'Element #1 Lorem ipsum dolor sit amet',
    'Element #2 Lorem ipsum dolor sit amet',
    'Element #3 Lorem ipsum dolor sit amet',
]);
table()

Відображає заданий масив заголовків та рядків у вигляді компактної таблиці:

1
2
3
4
5
6
7
8
$io->table(
    ['Header 1', 'Header 2'],
    [
        ['Cell 1-1', 'Cell 1-2'],
        ['Cell 2-1', 'Cell 2-2'],
        ['Cell 3-1', 'Cell 3-2'],
    ]
);
horizontalTable()

Відображає заданий масив заголовків та рядків у вигляді компактної горизонтальної таблиці:

1
2
3
4
5
6
7
8
$io->horizontalTable(
    ['Header 1', 'Header 2'],
    [
        ['Cell 1-1', 'Cell 1-2'],
        ['Cell 2-1', 'Cell 2-2'],
        ['Cell 3-1', 'Cell 3-2'],
    ]
);
definitionList()

Відображає задані пари key => value у вигляді компактного списку елементів:

1
2
3
4
5
6
7
8
9
$io->definitionList(
    'This is a title',
    ['foo1' => 'bar1'],
    ['foo2' => 'bar2'],
    ['foo3' => 'bar3'],
    new TableSeparator(),
    'This is another title',
    ['foo4' => 'bar4']
);
createTable()
Створює екземпляр Table, оформлений відповідно до Керівництва Symfony про оформлення, що дозволяє вам використовувати функції на кшталт динамічного додавання рядків.
newLine()

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

1
2
3
4
5
// виводить один порожній рядок
$io->newLine();

// виводить три порожні рядки
$io->newLine(3);

Методи попередження

note()

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

1
2
3
4
5
6
7
8
9
10
11
// використовуйте прості рядки для коротких повідомлень
$io->note('Lorem ipsum dolor sit amet');

// ...

// розгляньте використання масивів при відобаженні довгих повідомлень
$io->note([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
]);
caution()

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

1
2
3
4
5
6
7
8
9
10
11
// використовуйте прості рядки для коротких повідомлень
$io->caution('Lorem ipsum dolor sit amet');

// ...

// розгляньте використання масивів при відобаженні довгих попереджувальних повідомлень
$io->caution([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
    'Aenean sit amet arcu vitae sem faucibus porta',
]);

Методи індикатора виконання

progressStart()

Відображає індикатор виконання з кількістю кроків, що дорінює аргументу, переданому методу (не передавайте значення, якщо довжина індикатора виконання невідома):

1
2
3
4
5
// відображає індикатор виконання невідомої довжини
$io->progressStart();

// відображає індикатор виконання з довжиною у 100 кроків
$io->progressStart(100);
progressAdvance()

Змушує індикатор виконання просунутися на задану кількість кроків (або 1 крок, якщо не було передано аргумент):

1
2
3
4
5
// просуває індикатор виконання на 1 крок
$io->progressAdvance();

// просуває індикатор виконання на 10 кроків
$io->progressAdvance(10);
progressFinish()

Завершує індикатор виконання (заповнює всі кроки, що залишилися, за умови невідомої довжини):

1
$io->progressFinish();
progressIterate()

Якщо ваш індикатор виконання накладається на ітеровану колекцію, використайте помічника progressIterate():

1
2
3
4
5
$iterable = [1, 2];

foreach ($io->progressIterate($iterable) as $value) {
    // ... зробити якусь роботу
}
createProgressBar()
Створює екземпляр ProgressBar, оформлений відповідно до Керівництва Symfony про оформлення.

Методи введення користувача

ask()

Просить користувача надати якісь дані:

1
$io->ask('What is your name?');

Ви можете передати значення за замовчуванням в якості другого аргументу, щоб користувач міг просто натиснути клавішу <Enter> для вибору цього значення:

1
$io->ask('Where are you from?', 'United States');

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

1
2
3
4
5
6
7
$io->ask('Number of workers to start', '1', function (string $number): int {
    if (!is_numeric($number)) {
        throw new \RuntimeException('You must type a number.');
    }

    return (int) $number;
});
askHidden()

Дуже схожий на метод ask(), але введення користувача буде приховане і не може визначати значення за замовчуванням. Використовуйте його, коли запитуєте чутливу інформацію:

1
$io->askHidden('What is your password?');

У випадку, якщо вам потрібно валідувати задане значення, передайте валідатор зворотного виклику в якості другого аргументу:

1
2
3
4
5
6
7
$io->askHidden('What is your password?', function (string $password): string {
    if (empty($password)) {
        throw new \RuntimeException('Password cannot be empty.');
    }

    return $password;
});
confirm()

Ставить користувачу запитання, на яке можна відповісти Так/Ні і повертає лише true або false:

1
$io->confirm('Restart the web server?');

Ви можете передати значення за замовчуванням в якості другого аргументу, щоб користувач міг просто натиснути клавішу <Enter>, щоб обрати це значення:

1
$io->confirm('Restart the web server?', true);
choice()

Ставить запитання, відповідь на яке обмежена списком валідних відповідей:

1
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3']);

Ви можете передати значення за замовчуванням в якості третього аргументу, щоб користувач міг просто натиснути клавішу <Enter>, щоб обрати це значення:

1
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], 'queue1');

Нарешті, ви можете дозволити користувача обирати декілька варіантів. Щоб зробити це, користувачі повинні відокремити кожний вибір комою (наприклад, введення 1, 2 обере варіанти 1 і 2):

1
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], multiSelect: true);

Методи результатів

Note

Якщо ви надрукуєте будь-який URL, він не буде поламаний/обрізаний, на нього можна буде натиснути, якщо термінал надає таку можливість. Якщо "гарно сформатоване виведення" важливіше, ви можете це відключити:

1
$io->getOutputWrapper()->setAllowCutUrls(true);
success()

Відображає заданий рядок або масив рядків, виділених як повідомлення про успіх (зелений фон з ярликом [OK]). Мав би використовуватися один раз для відображення фінального результату виконання даної команди, але ви можете використовувати його повторно під час виконання команди:

1
2
3
4
5
6
7
8
9
10
// використовуйте прості рядки для коротких повідомлень про успіх
$io->success('Lorem ipsum dolor sit amet');

// ...

// розгляньте використання масивів при відображенні довгих повідомлень про успіх
$io->success([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);
info()

Схожий на метод success() (заданий рядок або масив рядків відображаються на зеленому фоні), але ярлик [OK] не має префиксу. Повинен бути використаний один раз для відображення фінального результату виконання заданої команди, не відображаючи результат як успішний або неуспішний:

1
2
3
4
5
6
7
8
9
10
// використовуйте прості рядки для коротких інформаційних повідомлень
$io->info('Lorem ipsum dolor sit amet');

// ...

// розгляньте використання масивів при відображенні довгих інформамційних повідомлень
$io->info([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);
warning()

Відображає заданий рядок або масив рядків, виділених як попереджувальне повідомлення (з червоним фоном та ярликом [WARNING]). Мав би використовуватися один раз для відображення фінального результату виконання даної команди, але ви можете використовувати його повторно під час виконання команди:

1
2
3
4
5
6
7
8
9
10
// використовуйте прості рядки для коротких попереджувальних повідомлень
$io->warning('Lorem ipsum dolor sit amet');

// ...

// розгляньте використання масивів при відображенні довгих попереджувальних повідомлень
$io->warning([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);
error()

Відображає заданий рядок або масив рядків, виділених як повідомлення про помилку (з червоним фоном та ярликом [ERROR]). Мав би використовуватися один раз для відображення фінального результату виконання даної команди, але ви можете використовувати його повторно під час виконання команди:

1
2
3
4
5
6
7
8
9
10
// використовуйте прості рядки для коротких повідомлень про помилку
$io->error('Lorem ipsum dolor sit amet');

// ...

// розгляньте використання масивів при відображенні довгих повідомлень про помилку
$io->error([
    'Lorem ipsum dolor sit amet',
    'Consectetur adipiscing elit',
]);

Конфігурація стилей за замовчуванням

За замовчуванням, стилі Symfony огортають весь зміст, щоб уникнути задовгих рядків тексту. Єдиним виключенням є URL, які не огортаються, незалежно від того, наскільки вони довгі. Це робиться, щоб включити URL, на які можна натискати, у терміналах, які це підтримують.

Якщо ви хочете огортати весь зміст, включно з URL, використайте цей метод:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Command/GreetCommand.php
namespace App\Command;

// ...
use Symfony\Component\Console\Style\SymfonyStyle;

class GreetCommand extends Command
{
    // ...

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->getOutputWrapper()->setAllowCutUrls(true);

        // ...
    }
}

Визначення ваших власних стилів

Якщо вам не подобається дизайн команд, що використовують Оформлення Symfony, ви можете визначити ваш власний набір стилів консолі. Просто створіть клас, що реалізує StyleInterface:

1
2
3
4
5
6
7
8
namespace App\Console;

use Symfony\Component\Console\Style\StyleInterface;

class CustomStyle implements StyleInterface
{
    // ...реалізуйте методи інтерфейсу
}

Далі, інстанціюйте у ваших коамндах цей користувацький клас замість класу за замовчуванням SymfonyStyle. Завдяки StyleInterface вам не знадобиться змінювати код ваших команд, щоб змінити їх зовнішній вигляд:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Command/GreetCommand.php
namespace App\Console;

use App\Console\CustomStyle;
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
    {

        // До
        // $io = new SymfonyStyle($input, $output);

        // Після
        $io = new CustomStyle($input, $output);

        // ...
    }
}

Запис у виведення помилки

Якщо ви повторно використовуєте виведення команди в якості введення інших команд, або скидаєте його у файл для подальшого використання, ви напевно хочете виключати індикатори виконання, нотатки та інше виведення, яке не має реального значення.

Команди можуть виводити інформацію у двох різних потоках: stdout (стандартне виведення) - це потік, де повинен виводитися справжній зміст, а stderr (стандартна помилка) - це потік, де повинні виводитися помилки та повідомлення налагодження.

Клас SymfonyStyle надає зручний метод під назвою getErrorStyle() для перемикання між потоками. Цей метод повертає новий екземпляр SymfonyStyle, який використовує виведення помилок:

1
2
3
4
5
6
7
$io = new SymfonyStyle($input, $output);

// Написати у стандартне виведення
$io->write('Reusable information');

// Написати у виведення помилки
$io->getErrorStyle()->warning('Debugging information or errors');

Note

Якщо ви створите екземпляр SymfonyStyle з обʼєктом OutputInterface, який не є екземпляром ConsoleOutputInterface, то метод getErrorStyle() не матиме ніякого ефекту, а повернений обʼєкт все одно писатиме у стандартне виведення, замість виведення помилки.