Статьи

Консоль Symfony Beyond the Basics — Помощники и другие инструменты

Эта статья была рецензирована Верной Анчетой . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!


Неоспоримо, насколько полезными могут быть консольные команды при разработке программного обеспечения. Не так давно мы повторно представили компонент Symfony Console .

Этот компонент позволяет нам создавать структурированные и тестируемые команды CLI. Мы создали несколько простых команд и протестировали их; но когда наши команды становятся больше и сложнее, нам нужен другой набор инструментов.

Это то, на что мы будем смотреть сегодня: передовые консольные инструменты Symfony.

Давайте создадим команду, которую мы можем использовать, чтобы показать некоторые из этих функций. Большая часть основных функций была показана в повторном введении к статье о консоли Symfony , поэтому обязательно ознакомьтесь с ней перед продвижением — это быстрое, но полезное чтение!

Скриншот консоли

Установка

composer require symfony/console 

Важную информацию о Composer можно найти здесь , и если вы не знакомы с хорошо спроектированными изолированными средами PHP, в которых можно разрабатывать свои приложения PHP, такие как Vagrant , у нас есть фантастическая книга, объясняющая все это, которую можно приобрести здесь .

Создание нашей команды

Давайте создадим команду для фаворита всех времен: Fizzbuzz.

Fizzbuzz — простая проблема, часто используемая в программировании интервью, чтобы утвердить компетентность интервьюируемого в программировании. Определение Fizzbuzz обычно приходит в следующей форме:

Напишите программу, которая печатает числа от 1 до x. Но для кратных трех выведите «Fizz» вместо числа, а для кратных пяти — «Buzz». Для чисел, кратных трем и пяти, выведите «FizzBuzz».

Наша команда получит аргумент, который будет верхним пределом для Fizzbuzz.

Прежде всего, давайте создадим наш класс Fizzbuzz.

 <?php declare(strict_types=1); namespace FizzBuzz; class Fizzbuzz{ public function isFizz(int $value): bool{ if($value % 3 === 0){ return true; } return false; } public function isBuzz(int $value): bool{ if($value % 5 === 0){ return true; } return false; } public function calculateFizzBuzz(int $number): bool{ if($this->isFizz($number) && $this->isBuzz($number)){ echo "FizzBuzz \n"; return true; } if($this->isFizz($number)){ echo "Fizz \n"; return true; } if($this->isBuzz($number)){ echo "Buzz \n"; return true; } echo $number . "\n"; return true; } public function firstNFizzbuzz(int $maxValue): void{ $startValue = 1; while($startValue <= $maxValue){ $this->calculateFizzBuzz($startValue); $startValue++; } } } 

Довольно просто. Метод firstNFizzbuzz() печатает результаты Fizzbuzz для значения $maxValue . Это делается путем рекурсивного вызова метода convertFizzBuzz calculateFizzBuzz() .

Далее давайте напишем нашу команду. Создайте файл FizzCommand.php со следующим содержимым:

 <?php namespace FizzBuzz; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use FizzBuzz\Fizzbuzz; class FizzCommand extends Command{ protected function configure(){ $this->setName("FizzBuzz:FizzBuzz") ->setDescription("Runs Fizzbuzz") ->addArgument('Limit', InputArgument::REQUIRED, 'What is the limit you wish for Fizzbuzz?'); } protected function execute(InputInterface $input, OutputInterface $output){ $fizzy = new FizzBuzz(); $input = $input->getArgument('Limit'); $result = $fizzy->firstNFizzbuzz($input); } } 

И наконец наш файл console .

 #!/usr/bin/env php <?php require_once __DIR__ . '/vendor/autoload.php'; use Symfony\Component\Console\Application; use FizzBuzz\FizzCommand; $app = new Application(); $app->add(new FizzCommand()); $app->run(); 

Здесь мы создаем новое консольное приложение и регистрируем в нем наш FizzCommand() . Не забудьте сделать этот файл исполняемым.

Теперь мы можем проверить, правильно ли зарегистрирована наша команда, запустив команду ./console . Мы также можем выполнить нашу команду с помощью ./console FizzBuzz:Fizzbuzz 25 . Это вычислит и напечатает результаты Fizzbuzz от 1 до 25.

До сих пор мы не сделали ничего нового. Но есть несколько способов улучшить нашу команду. Прежде всего, команда не очень интуитивно понятна. Откуда мы знаем, что мы должны передать ограничение команде? Для этого консоль Symfony предлагает нам помощника по вопросам .

Вопрос Помощник

Помощник по вопросам обеспечивает функциональность, чтобы запросить у пользователя дополнительную информацию. Таким образом, мы можем интерактивно собирать информацию для выполнения наших команд.

Давайте изменим нашу команду таким образом, чтобы вместо получения лимита выполнения через командную строку выполнения запрашивать у пользователя лимит. Для этого у помощника по вопросам есть единственный метод: ask() . Этот метод получает в качестве аргументов InputInterface , OutputInterface и question .

Давайте изменим файл FizzCommand.php чтобы он выглядел так:

 <?php namespace FizzBuzz; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Question\Question; use FizzBuzz\Fizzbuzz; class FizzCommand extends Command{ protected function configure(){ $this->setName("FizzBuzz:FizzBuzz") ->setDescription("Runs Fizzbuzz"); } protected function execute(InputInterface $input, OutputInterface $output){ $fizzy = new FizzBuzz(); $helper = $this->getHelper('question'); $question = new Question('Please select a limit for this execution: ', 25); $limit = $helper->ask($input, $output, $question); $result = $fizzy->firstNFizzbuzz($limit); } } 

Мы больше не ожидаем аргумента configure() метода configure() . Мы создаем новый Question по умолчанию 25 и используем его в методе ask() мы говорили ранее.

Теперь у нас есть интерактивная команда, которая запрашивает ограничение перед выполнением Fizzbuzz.

Помощник по вопросам также дает нам функциональность для проверки ответов. Итак, давайте используем его, чтобы убедиться, что лимит является целым числом.

 protected function execute(InputInterface $input, OutputInterface $output){ $fizzy = new FizzBuzz(); $helper = $this->getHelper('question'); $question = new Question('Please select a limit for this execution: ', 25); $question->setValidator(function ($answer) { if (!is_numeric($answer)) { throw new \RuntimeException('The limit should be an integer.'); } return $answer; }); $question->setNormalizer(function ($value) { return $value ? trim($value) : ''; }); $question->setMaxAttempts(2); $limit = $helper->ask($input, $output, $question); $result = $fizzy->firstNFizzbuzz($limit); } 

Мы не только setValidator() , что наш лимит является целым числом с помощью функции setValidator() , мы также нормализуем ввод в случае, если пользователь вставляет пробелы, а также устанавливаем максимальное количество попыток, разрешенное для двух.

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

таблицы

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

Для отображения таблицы нам нужно использовать класс Table ; установить заголовки и строки, и, наконец, визуализировать таблицу. Это может быть очень полезно, когда дело доходит до отображения структурированных данных. Давайте представим, что мы хотим создать команду, чтобы показать преобразования для некоторых метрических систем.

Давайте добавим MetricsCommand.php в наш новый php-файл.

 <?php namespace Metric; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Helper\Table; class MetricsCommand extends Command{ protected function configure(){ $this->setName("Metrics") ->setDescription("Inches to centimeters table."); } public function execute(InputInterface $input, OutputInterface $output){ $table = new Table($output); $table ->setHeaders(array('Inches', 'Centimeters')) ->setRows(array( array('1', '2.54'), array('5', '12.7'), array('10', '25.4'), array('50', '127'), )) ; $table->render(); } } 

И наш новый файл console :

 #!/usr/bin/env php <?php require_once __DIR__ . '/vendor/autoload.php'; use Symfony\Component\Console\Application; use Metric\MetricsCommand; $app = new Application(); $app->add(new MetricsCommand()); $app->run(); 

Это очень простая команда: она отображает таблицу с некоторыми значениями, преобразованными из дюймов в сантиметры. Если мы запустим нашу команду, используя ./console Metrics , результат будет примерно таким:

Наш результат выполнения

Класс Table также предлагает нам различные стили разделителей для наших таблиц. Проверьте эту страницу, если вы хотите узнать больше.
,

Прогресс Бары

Хотя вопросы и таблицы могут быть очень полезными, наиболее важным элементом может быть индикатор выполнения. Индикаторы выполнения дают нам обратную связь о выполнении команды и позволяют нам иметь четкое представление о том, как долго нам, возможно, придется ждать завершения операции.

Индикаторы прогресса необходимы для более длительных команд. Чтобы использовать их, нам нужен ProgressBar , передать ему общее количество единиц (если мы действительно знаем, сколько единиц мы ожидаем) и увеличить его по мере выполнения команды.

Простая команда с индикатором выполнения может выглядеть так:

 <?php namespace Progress; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Helper\ProgressBar; class ProgressCommand extends Command{ protected function configure(){ $this->setName("Progress") ->setDescription("Check Console componenet progress bar."); } public function execute(InputInterface $input, OutputInterface $output) { $progress = new ProgressBar($output); $progress->start(); $i = 0; while ($i++ < 50) { usleep(300000); $progress->advance(); } $progress->finish(); } } - <?php namespace Progress; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Helper\ProgressBar; class ProgressCommand extends Command{ protected function configure(){ $this->setName("Progress") ->setDescription("Check Console componenet progress bar."); } public function execute(InputInterface $input, OutputInterface $output) { $progress = new ProgressBar($output); $progress->start(); $i = 0; while ($i++ < 50) { usleep(300000); $progress->advance(); } $progress->finish(); } } - <?php namespace Progress; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Helper\ProgressBar; class ProgressCommand extends Command{ protected function configure(){ $this->setName("Progress") ->setDescription("Check Console componenet progress bar."); } public function execute(InputInterface $input, OutputInterface $output) { $progress = new ProgressBar($output); $progress->start(); $i = 0; while ($i++ < 50) { usleep(300000); $progress->advance(); } $progress->finish(); } } - <?php namespace Progress; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Helper\ProgressBar; class ProgressCommand extends Command{ protected function configure(){ $this->setName("Progress") ->setDescription("Check Console componenet progress bar."); } public function execute(InputInterface $input, OutputInterface $output) { $progress = new ProgressBar($output); $progress->start(); $i = 0; while ($i++ < 50) { usleep(300000); $progress->advance(); } $progress->finish(); } } 

И соответствующая console :

 #!/usr/bin/env php <?php require_once __DIR__ . '/vendor/autoload.php'; use Symfony\Component\Console\Application; use Progress\ProgressCommand; $app = new Application(); $app->add(new ProgressCommand()); $app->run(); 

Это очень простая команда. Мы настраиваем панель и перебираем функцию sleep() . Окончательный вывод будет выглядеть так:

Пример индикатора выполнения

Более подробную информацию о индикаторах выполнения можно найти в официальной документации .

Настройка нашего индикатора прогресса

Настройка индикаторов выполнения может быть полезна для предоставления дополнительной информации, пока пользователь ждет.

По умолчанию информация, отображаемая в индикаторе выполнения, зависит от уровня OutputInterface экземпляра OutputInterface . Итак, если мы хотим показать разные уровни информации, мы можем использовать метод setFormat() .

 $bar->setFormat('verbose'); 

Встроенные форматы: normal , verbose , very_verbose и debug .

Если мы используем normal формат, например, результат будет выглядеть так:

Индикатор выполнения, нормальный формат

Мы также можем установить наш собственный формат.

Индикатор выполнения — это строка, состоящая из разных конкретных заполнителей. Мы можем объединить эти конкретные заполнители для создания собственных индикаторов выполнения. Доступные заполнители: current , max , bar , percent , elapsed , remaining , estimated , memory и message . Поэтому, если, например, мы хотим скопировать точно такой же индикатор выполнения по умолчанию, мы можем использовать следующее:

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

Настроить индикаторы выполнения намного больше — читайте об этом здесь .

Вызов команды внутри команды

Еще одна очень полезная функция — возможность запуска команды внутри команды. Например, у нас может быть команда, которая зависит от другой команды для успешного выполнения, или последовательность команд, которые мы могли бы выполнить в последовательности.

Например, представьте, что мы хотим создать команду для запуска нашей команды fizzbuzz.
Нам нужно создать новую команду внутри нашей папки /src и внутри метода execute() , иметь следующее:

 protected function execute(InputInterface $input, OutputInterface $output) { $command = $this->getApplication()->find('FizzBuzz:FizzBuzz'); $returnCode = $command->run(); } 

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

Кроме этого все дело в использовании метода find() с именем нашей команды для поиска и регистрации нашей команды.

Цвет и Стиль

Раскраска и стилизация выходных данных могут быть полезны для оповещения или информирования пользователя о чем-либо при выполнении команды. Для этого нам просто нужно добавить следующие теги в наш метод writeln() , как показано ниже:

 // green text $output->writeln('<info>Output here</info>'); // yellow text $output->writeln('<comment>Output here</comment>'); // black text on a cyan background $output->writeln('<question>Output here</question>'); // white text on a red background $output->writeln('<error>Output here</error>'); 

Также есть возможность определить наши собственные стили с помощью класса OutputFormatterStyle :

 $style = new OutputFormatterStyle('red', 'yellow', array('bold', 'blink')); $output->getFormatter()->setStyle('fire', $style); $output->writeln('<fire>foo</fire>'); 

Более подробную информацию о стиле вывода можно найти здесь .

Подводя итоги

От стилей до помощников мы увидели множество функциональных возможностей, которые консоль Symfony предоставляет «из коробки». После сегодняшнего дня нет абсолютно никаких оправданий плохим документированным инструментам командной строки!

Какими помощниками и компонентами Консоли вы часто пользуетесь? Как вы начинаете свои инструменты CLI? Вам достаточно Symfony Console или вы предпочитаете альтернативу ?