Індикатор виконання

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

Індикатор виконання

При виконанні довших команд, може бути корисним відображувати інформацію про виконання, яка оновлюється під час виконання команди:

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

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\Helper\ProgressBar;

// створює новий індикатор виконання (50 одиниць)
$progressBar = new ProgressBar($output, 50);

// запускає та відображує індикатор виконання
$progressBar->start();

$i = 0;
while ($i++ < 50) {
    // ... зробити якусь роботу

    // просуває індикатор виконання на 1 одиницю
    $progressBar->advance();

    // ви також можете просунути індикатор виконання більше, ніж на 1 одиницю
    // $progressBar->advance(3);
}

// гарантує, що індикатор виконання досяг 100%
$progressBar->finish();

Tip

Ви також можете просунутися за індикатором у іншому напрямку (тобто, зробити крок назад), викликавши $progress->advance() з відʼємним значенням. Наприклад, якщо ви викличете $progress->advance(-2), то це зменшить виконання на 2 кроки.

Замість просування індикатора на кількість кроків (з методом advance()), ви також можете встановити поточний прогрес, викликавши метод setProgress().

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

1
2
3
4
5
6
7
use Symfony\Component\Console\Helper\ProgressBar;

// створює новий індикатор виконання (100 одиниць)
$progressBar = new ProgressBar($output, 100);

// відображає індикатор виконання, починаючи з 25 виконаних одиниць
$progressBar->start(null, 25);

6.2

Опція починати індикатор виконання з певної точки була представлена в Symfony 6.2.

Tip

Якщо ваша платформа не підтримує коди ANSI, оновлення в індикаторі виконання додаються у вигляді нових рядків. Щоб запобігти перенасиченню виведення, відповідно використайте метод minSecondsBetweenRedraws(), шоб обмежити кількість повторних відтворень та метод setRedrawFrequency(), щоб відтворювати кожні N ітерацій. За замовчуванням, частота відтворень становить 100мс або 10% вашого max.

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

1
2
3
4
5
// почніть з індикатору виконання на 50 одиниць
$progressBar = new ProgressBar($output, 50);

// щойно було створено складне завдання; збільшіть індикатор виконання до 200 одиниць
$progressBar->setMaxSteps(200);

Іншим рішенням буде просто опустити аргумент кроків при створенні екземпляру ProgressBar:

1
$progressBar = new ProgressBar($output);

Тоді прогрес буде відображений у вигляді тробера:

1
2
3
4
5
6
7
8
9
# немає максимальної кількості кроків (відображує його, як тробер)
    0 [>---------------------------]
    5 [----->----------------------]
    5 [============================]

# максимальна кількість кроків визначена
 0/3 [>---------------------------]   0%
 1/3 [=========>------------------]  33%
 3/3 [============================] 100%

Кожний раз після завершення завдання, не забудьте викликати finish(), щоб гарантувати, що індикатор виконання оновлено до 100% завершення.

Note

Якщо ви хочете вивести щось під час роботи індикатора виконання, спочатку викличте clear(). Після того, як ви закінчите, викличте display(), щоб відобразити індикатор виконання знову.

Якщо інформація про виконання зберігається у ітерованій змінній (такій як масиві або генераторі PHP), ви можете використати метод iterate(), який починає, проводить та завершує індикатор виконання автоматично:

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

$progressBar = new ProgressBar($output);

// $iterable може бути масивом
$iterable = [1, 2];
foreach ($progressBar->iterate($iterable) as $value) {
    // ... зробити якусь роботу
}

// або генератор
function iterable() { yield 1; yield 2; ... };
foreach ($progressBar->iterate(iterable()) as $value) {
    // ... зробити якусь роботу
}

Попередній код виведе:

1
2
3
0/2 [>---------------------------]   0%
1/2 [==============>-------------]  50%
2/2 [============================] 100%

Налаштування індикатору виконання

Вбудовані формати

За замовчуванням, інформація, що відображена в індикаторі виконання, залежить від поточного рівня деталізації екземпляра OutputInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# OutputInterface::VERBOSITY_NORMAL (CLI без прапорцю деталізації)
 0/3 [>---------------------------]   0%
 1/3 [=========>------------------]  33%
 3/3 [============================] 100%

# OutputInterface::VERBOSITY_VERBOSE (-v)
 0/3 [>---------------------------]   0%  1 сек
 1/3 [=========>------------------]  33%  1 сек
 3/3 [============================] 100%  1 сек

# OutputInterface::VERBOSITY_VERY_VERBOSE (-vv)
 0/3 [>---------------------------]   0%  1 сек
 1/3 [=========>------------------]  33%  1 сек
 3/3 [============================] 100%  1 сек

# OutputInterface::VERBOSITY_DEBUG (-vvv)
 0/3 [>---------------------------]   0%  1 сек/1 сек  1.0 MB
 1/3 [=========>------------------]  33%  1 сек/1 сек  1.0 MB
 3/3 [============================] 100%  1 сек/1 сек  1.0 MB

Note

Якщо ви викличете команду з прапорцем приглушення (-q), то індикатор виконання не буде відображено.

Замість того, щоб покладатися на режим деталізації поточної команди, ви також можете форсувати формат через setFormat():

1
$progressBar->setFormat('verbose');

Наступні формати є вбудованими:

  • normal
  • verbose
  • very_verbose
  • debug

Якщо ви не встановите кількість кроків для вашого індикатора виконання, використайте варіанти _nomax:

  • normal_nomax
  • verbose_nomax
  • very_verbose_nomax
  • debug_nomax

Користувацькі формати

Замість використання вбудованих форматів, ви також можете встановити власні:

1
$progressBar->setFormat('%bar%');

Це встановлює формат, щоб відображати лише сам індикатор:

1
2
3
>---------------------------
=========>------------------
============================

Формат індикатора виконання - це рядок, що містить заповнювачі (імʼя, що закінчується символом %); заповнювачі замінюються в залежності від поточного прогресу індикатора. Ось список вбудованих заповнювачів:

  • current: Поточний крок;
  • max: Максимальна кількість кроків (або 9, якщо максимум не визначено);
  • bar: Сам індикатор;
  • percent: Процент виконання (не доступний, якщо не визначено максимум);
  • elapsed: Час, що пройшов з початку запуску індикатора виконання;
  • remaining: Час, що залишився для виконання завдання (не доступний, якщо не визначено максимум);
  • estimated: Передбачуваний час для виконання завдання (не доступний, якщо нне визначено максимум);
  • memory: Поточне використання памʼяті;
  • message: Використовується для відображення довільних повідомлень в індикаторі (пояснюється нижче).

Наприклад, ось як ви можете встановити такий же формат, як і debug:

1
$progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');

Помітили частину :6s, додану до деяких заповнювачів? Це те, як ви можете налаштувати зовнішній вигляд індикатора (форматування та розташування). Частина після двокрапки (:) використовується для встановлення формату рядку sprintf.

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

1
2
3
4
ProgressBar::setFormatDefinition('minimal', 'Progress: %percent%%');

$progressBar = new ProgressBar($output, 3);
$progressBar->setFormat('minimal');

Цей код визначає новий формат minimal, який ви потім можете використовувати для ваших індикаторів виконання:

1
2
3
Progress: 0%
Progress: 33%
Progress: 100%

Tip

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

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

1
2
3
4
5
ProgressBar::setFormatDefinition('minimal', '%percent%% %remaining%');
ProgressBar::setFormatDefinition('minimal_nomax', '%percent%%');

$progressBar = new ProgressBar($output);
$progressBar->setFormat('minimal');

При відображенні індикатора виконання, формат буде автоматично встановлено, як minimal_nomax, якщо шкала не має максимальної кількості кроків, як у прикладі вище.

Tip

Формат може містити будь-які валідні коди ANSI, а також використовувати специфічний спосіб Symfony для установки кольорів:

1
2
3
4
ProgressBar::setFormatDefinition(
    'minimal',
    '<info>%percent%</info>\033[32m%\033[0m <fg=white;bg=blue>%remaining%</>'
);

Note

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

Налаштування індикатора

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

1
2
3
4
5
6
7
8
9
10
11
// завершена частина індикатора
$progressBar->setBarCharacter('<comment>=</comment>');

// незавершена частина індикатора
$progressBar->setEmptyBarCharacter(' ');

// символ прогресу
$progressBar->setProgressCharacter('|');

// ширина індикатора
$progressBar->setBarWidth(50);

Caution

З міркувань продуктивності, Symfony повторно відтворює екран раз на 100мс. Якщо це занадто швидко чи занадто повільно для вашого додатку, використайте методи minSecondsBetweenRedraws() і maxSecondsBetweenRedraws():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$progressBar = new ProgressBar($output, 50000);
$progressBar->start();

// це відтворює екран кожні 100 ітерацій, але встановлює додаткові обмеження:
// не відтворювати повільніше, ніж раз на 200мс (0.2) або швидше, ніж раз на 100мс (0.1)
$progressBar->setRedrawFrequency(100);
$progressBar->maxSecondsBetweenRedraws(0.2);
$progressBar->minSecondsBetweenRedraws(0.1);

$i = 0;
while ($i++ < 50000) {
    // ... зробити якусь роботу

    $progressBar->advance();
}

Користувацькі заповнювачі

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

1
2
3
4
5
6
ProgressBar::setPlaceholderFormatterDefinition(
    'remaining_steps',
    function (ProgressBar $progressBar, OutputInterface $output) {
        return $progressBar->getMaxSteps() - $progressBar->getProgress();
    }
);

Користувацькі повідомлення

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

1
2
3
4
ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message%');

$progressBar = new ProgressBar($output, 100);
$progressBar->setFormat('custom');

Тепер використайте метож setMessage(), щоб встановити значення заповнювача %message% до відображення індикатора виконання:

1
2
3
4
5
6
7
8
// ...
$progressBar->setMessage('Start');
$progressBar->start();
// 0/100 -- Start

$progressBar->advance();
$progressBar->setMessage('Задача в процессе выполнения...');
// 1/100 -- Завдання в процесі виконання...

Повідомлення можуть бути також обʼєднані з користувацькимм заповнювачами. У цьому прикладі, індикатор виконання використовує заповнювачі %message% та %filename%:

1
2
3
4
ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message% (%filename%)');

$progressBar = new ProgressBar($output, 100);
$progressBar->setFormat('custom');

Метод setMessage() приймає другий необовʼязковий аргумент для встановлення значення користувацького заповнювача:

1
2
3
4
5
6
7
8
// ...
// $files = array('client-001/invoices.xml', '...');
foreach ($files as $filename) {
    $progressBar->setMessage('Імпорт рахунків...');
    $progressBar->setMessage($filename, 'filename');
    $progressBar->advance();
    // 2/100 -- Імпорт рахунків... (client-001/invoices.xml)
}

Відображення декількох індикаторів виконання

При використанні Розділів виведення консолі , можливо відображати декілька індикаторів виконання одночасно та змінювати їх прогрес незалежно один від одного:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$section1 = $output->section();
$section2 = $output->section();

$progress1 = new ProgressBar($section1);
$progress2 = new ProgressBar($section2);

$progress1->start(100);
$progress2->start(100);

$i = 0;
while (++$i < 100) {
    $progress1->advance();

    if ($i % 2 === 0) {
        $progress2->advance(4);
    }

    usleep(50000);
}

Після декількох ітерацій виведення в терміналі виглядатиме так:

1
2
34/100 [=========>------------------]  34%
68/100 [===================>--------]  68%