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

от автора

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

Именно для таких случаев была разработана библиотека 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


ссылка на оригинал статьи https://habr.com/ru/articles/923254/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *