Narzędzie CLI w CakePHP 5

CakePHP 5 daje całkiem wygodne narzędzia do pisania komend terminalowych, zwłaszcza jeśli chcesz pokazać statusy, ostrzeżenia i postęp długiego procesu. Dzięki obsłudze stylizowanego outputu i helpera Progress można zbudować czytelne narzędzie CLI bez dokładania zewnętrznych bibliotek. Taki CLI sprawdza się przy importach danych, migracjach, synchronizacji z API, masowych aktualizacjach rekordów i zadaniach batchowych. Właśnie w takich przypadkach kolorowy output i progres bar poprawiają UX operatora, bo od razu widać, czy proces działa, ostrzega czy zakończył się sukcesem.

Struktura komendy

W CakePHP 5 komenda zwykle dziedziczy po Command, a główna logika trafia do metody execute(). To dobre miejsce na pobranie argumentów, inicjalizację helperów i uruchomienie właściwego procesu.
<?php
declare(strict_types=1);

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;

final class ImportUsersCommand extends Command
{
    public function execute(Arguments $args, ConsoleIo $io): int
    {
        $io->out('Start importu');

        return static::CODE_SUCCESS;
    }
}

Kolory w output

CakePHP 5 wspiera stylizowany output przez znaczniki w tekście oraz tryby ConsoleOutput::COLOR i ConsoleOutput::PLAIN. W praktyce oznacza to, że możesz wypisywać komunikaty sukcesu, błędy i ostrzeżenia w kolorach, a jeśli trzeba, łatwo przełączyć się na zwykły tekst.
$io->out('<success>Import zakończony pomyślnie</success>');
$io->out('<warning>To jest ostrzeżenie</warning>');
$io->err('<error>Wystąpił błąd</error>');
To podejście jest wygodne, bo sam tekst komendy pozostaje czytelny, a warstwa prezentacji jest oddzielona od logiki.

Progress bar

Do paska postępu CakePHP 5 udostępnia ProgressHelper. Helper można pobrać przez $io->helper('Progress'), zainicjalizować go parametrem total, a potem wywoływać increment() i draw() w trakcie przetwarzania.
$progress = $io->helper('Progress');
$progress->init([
    'total' => 100,
]);

for ($i = 0; $i < 100; $i++) {
    usleep(20000);
    $progress->increment();
    $progress->draw();
}
W dokumentacji i przykładach praktycznych ważne jest, żeby nie odświeżać paska zbyt często przy bardzo dużej liczbie rekordów, bo to może niepotrzebnie obciążać output. W wielu batchach lepiej aktualizować go co kilka, kilkadziesiąt albo kilkaset elementów.

Pełny przykład

Poniżej masz prostą komendę importującą dane z tablicy, z kolorami i paskiem postępu.
<?php
declare(strict_types=1);

namespace App\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;

final class ImportUsersCommand extends Command
{
    public function execute(Arguments $args, ConsoleIo $io): int
    {
        $rows = [];
        for ($i = 1; $i <= 50; $i++) {
            $rows[] = ['id' => $i, 'email' => "user{$i}@example.com"];
        }

        $io->out('<info>Rozpoczynam import</info>');

        $progress = $io->helper('Progress');
        $progress->init([
            'total' => count($rows),
        ]);

        foreach ($rows as $row) {
            usleep(30000);

            $progress->increment();
            $progress->draw();
        }

        $io->out();
        $io->out('<success>Import zakończony</success>');

        return static::CODE_SUCCESS;
    }
}
Ten wzorzec jest dobrym punktem startowym, bo pokazuje cały przepływ: przygotowanie danych, komunikat startowy, inicjalizacja progress bara i finalny komunikat sukcesu.

Obsługa błędów

Dobra komenda CLI powinna umieć zakończyć się kodem błędu i wypisać zrozumiały komunikat. W CakePHP 5 warto używać STDERR przez $io->err() oraz zwracać kod inny niż 0, gdy coś się nie uda.
if ($file === null) {
    $io->err('<error>Brak pliku wejściowego</error>');
    return static::CODE_ERROR;
}
To ważne zwłaszcza wtedy, gdy komenda jest uruchamiana z crona, CI albo zewnętrznego orkiestratora.

Przetwarzanie dużych danych

Jeśli komenda ma pracować na większej liczbie rekordów, lepiej aktualizować progress bar rzadziej niż w każdej iteracji. W praktyce można zwiększać licznik lokalny i odświeżać UI co 100 rekordów, co ogranicza koszt renderowania.
$counter = 0;

foreach ($items as $item) {
    $this->process($item);
    $counter++;

    if ($counter % 100 === 0) {
        $progress->increment(100);
        $progress->draw();
    }
}
To dobry kompromis między informacją zwrotną a wydajnością. Taki wzorzec szczególnie dobrze działa przy:
  • importach CSV i JSON.
  • migracjach danych.
  • synchronizacji zewnętrznych systemów.
  • przetwarzaniu batchy.
  • zadaniach administracyjnych uruchamianych ręcznie.
W tych przypadkach kolorowy output pomaga operatorowi, a progress bar daje poczucie kontroli nad długim procesem. CakePHP 5 daje wystarczająco dobre narzędzia, żeby pisać czytelne i wygodne CLI bez dodatkowych bibliotek. Kolory poprawiają komunikację z użytkownikiem, a ProgressHelper pozwala pokazać postęp długich operacji w prosty sposób.