Как стать автором
Обновить

Fluent CLI в PHP: Создаём консольные команды с __call и никаких танцев с бубном

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров338

В мире разработки часто возникает потребность в работе с командной строкой — будь то выполнение системных команд, обработка их вывода или работа с различными кодировками. Столкнувшись с такими задачами, программисты часто начинают искать готовые решения, чтобы сэкономить время и избежать повторения однотипного кода.

Именно для таких случаев была разработана библиотека PhpFluentConsole — простое и гибкое решение для работы с командной строкой, с поддержкой кодировок и множеством полезных фич для работы с выводом команд.

Зачем нужна эта библиотека?

PhpFluentConsole не заменяет существующие библиотеки, а выступает как легковесная основа для гибкого построения CLI-запросов. Её цель — упростить интеграцию системных команд без необходимости изучать объёмную документацию или вникать в сложный API.

Основная цель библиотеки — упрощение и автоматизация выполнения команд, а также обработка вывода, что делает её удобной для разработчиков, которые хотят создавать свои собственные решения на основе этой библиотеки.

Преимущества использования

Гибкость и удобство:

Библиотека поддерживает текучий интерфейс для построения команд, что делает код максимально читаемым и лаконичным.
Пример:

$cli = new ConsoleRunner()
     ->setCommand('echo')
     ->addKey('Hello, World!')

Поддержка кодировок:

Для работы с кириллицей и другими символами, библиотека включает поддержку различных кодировок, таких как CP866, UTF-8, CP1251, и других. Это особенно важно при работе в windows-среде, где часто возникают проблемы с выводом текста в стандартных кодировках, что так же может затруднить обработку вывода с помощью регулярных выражений.
Пример:

$cli = new ConsoleRunner()
     ->setCommand('dir')
     ->encoding('866')

Обработка ошибок и кодов возврата:

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

if (!$cli->run()) {
    echo "Ошибка выполнения команды!" . $cli->getReturnCode();
}

Поиск совпадений по паттернам:

Вы можете искать совпадения в выводе команды, используя регулярные выражения. Это полезно, если нужно извлечь конкретную информацию из вывода команд, например, лог-файлов или диагностики системы.
Пример:

if ($cli->run()) {
   $matches = $cli->getMatches('/Error/');
   print_r($matches);
}

Стандартный вывод:

Если нам просто нужно получить массив строк.
Пример:

if ($cli->run()) {
   print_r($cli->getOutput());
}

Установка

composer require mikhailovlab/php-fluent-console

Описание методов

Устанавливает команду для выполнения.

public function setCommand(string $cmd): self

Добавляет аргумент или часть команды.

public function addKey(string $key): self

Устанавливает кодировку для вывода. Например, '866' для отображения кириллицы в Windows.

public function encoding(?string $encoding = null): self

Устанавливает флаг, что нужно выполнить обратную конвертацию кодировки. Метод полезен для возврата вывода в исходной кодировке для работы с cmd.

public function decoding(): self

Возвращает текущую команду.

public function getCommand(): string

Выполняет команду и возвращает true, если код возврата равен 0.

public function run(): bool

Возвращает вывод после выполнения команды.

public function getOutput(): array

Возвращает код возврата выполнения команды.

public function getReturnCode(): int

Проверяет вывод на наличие ошибки по регулярному выражению.

public function hasError(string $pattern): bool

Возвращает все строки вывода, совпавшие с регулярным выражением.

public function getMatches(string|array $patterns): array

Примеры

Проверять функционал будем на примере фреймворка Laravel 11, но так же можно использовать любой другой, это непринципиально:

Пример 1: Получаем IP адрес в операционной системе windows

$cli = new ConsoleRunner()
     ->setCommand('ipconfig')
     ->addKey('/all')
     ->encoding('866'); //ожидаем вывод с кириллицей

if ($cli->run()) {
   dd($cli->getOutput());
}

Вывод:

array:59 [▼ // app\Http\Controllers\TestController.php:21
  0 => ""
  1 => "Настройка протокола IP для Windows"
  2 => ""
  3 => "   Имя компьютера  . . . . . . . . . : DESKTOP-HRTB4N9"
  4 => "   Основной DNS-суффикс  . . . . . . :"
  5 => "   Тип узла. . . . . . . . . . . . . : Гибридный"
  6 => "   IP-маршрутизация включена . . . . : Нет"
  7 => "   WINS-прокси включен . . . . . . . : Нет"
  8 => "   Порядок просмотра суффиксов DNS . : lan"
  9 => ""
  10 => "Адаптер Ethernet Ethernet 2:"
...

Кириллица отображается корректно, можно двигаться дальше.

Пример 2: Получаем список контейнеров с электронными подписями

$cli = new ConsoleRunner()
     ->setCommand('csptest')
     ->addKey('-keyset')
     ->addKey('-enum_cont')
     ->addKey('-verifycontext')
     ->addKey('-fqcn');

if ($cli->run()) {
    dd($cli->getMatches('#\\\\.*#'));
}

$pattern = '/\[ErrorCode:\s*(0x[0-9A-Fa-f]+)\]/';
dd('Error code: ' . $cli->getMatches($pattern)[0]);

Вывод:

array:1 [▼ // app\Http\Controllers\TestController.php:23
  0 => "\\.\REGISTRY\d58fe6c13-d917-2a53-8e9c-8c4b8158220-test"
]

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

Пример 3: Расширяем базовый класс

Мы можем наследоваться от базового класса ConsoleRunner и вместо указания методов через addKey, вызывать их динамически, используя магический метод __call. Как правило, в консольных утилитах мы можем получить список всех доступных методов и аргументов используя ключ -help и добавить их в массив, что бы исключить ошибки, т.к. при несовпадении будет выброшено исключение.

class customRunner extends ConsoleRunner
{
    private $methods = [
        'keyset',
        'enum_cont',
        'verifycontext',
        'fqcn'
    ];

    public function __call(string $name, array $arguments): self
    {
        if (in_array($name, $this->methods)) {
            $this->addKey('-' . $name);
            if (!empty($arguments)) {
                foreach ($arguments as $arg) {
                    $this->addKey((string) $arg);
                }
            }
            return $this;
        }
        throw new \BadMethodCallException("Method $name is not supported");
    }
}

try{
    $cli = new customRunner()
         ->setCommand('csptest')
         ->keyset()
         ->enum_cont() 
         ->verifycontext()
         ->fqcn();

    if ($cli->run()) {
        dd($cli->getMatches('#\\\\.*#'));
    }

   $pattern = '/\[ErrorCode:\s*(0x[0-9A-Fa-f]+)\]/';
   dd('Error code: ' . $cli->getMatches($pattern)[0]);

}catch (Exception $e){
    dd($e->getMessage());
}

Вывод:

array:1 [▼ // app\Http\Controllers\TestController.php:23
  0 => "\\.\REGISTRY\d58fe6c13-d917-2a53-8e9c-8c4b8158220-test"
]

Мы можем перенести логику и обработку вывода в отдельные методы, что бы сделать код еще более лаконичным.

try{
    $containers = new customRunner()
                ->getContainers()
                ->run();

    dd($containers);
    
}catch (Exception $e){
    dd($e->getMessage());
}

Таким образом мы можем наследоваться от базового класса и создавать свои классы-обертки, в которых будем описывать методы и обрабатывать вывод.

Заключение

PhpFluentConsole — это удобный и гибкий инструмент для работы с командной строкой. Он позволяет строить команды через простой и понятный интерфейс, поддерживает работу с кодировками, предоставляет возможности для обработки ошибок и извлечения данных из вывода команд. Библиотека является отличной основой для создания более сложных решений или даже для разработки других библиотек на её основе, таких как CryptoProBuilder, о которой я расскажу в следующей статье.

Если у вас есть вопросы или предложения по улучшению этой библиотеки, вы всегда можете обратиться к репозиторию на GitHub, где вы сможете внести свой вклад или создать issue для обсуждения новых возможностей.

Ссылка на GitHub
Ссылка на Packagist

Теги:
Хабы:
+3
Комментарии1

Публикации

Ближайшие события