Консольные команды с PHPixie Console

image
PHPixie Console — это новый компонент позволяющий создавать, роутить и запускать консольные команды. Как и другие библиотеки фреймворка он может легко использоваться без самой PHPixie как более простая альтернатива аналогичной библиотеки из Symfony. В первую очередь это статься рассчитана на тех кто уже пользуется PHPixie и в ней будет короткое описание стандартных команд фреймворка, но в конце я так же приведу пример того как запустить PHPixie Console в отдельности.

Апгрейд существующих проектов
Если у вас уже есть PHPixie проект, то для работы Console надо внести несколько простых изменений:
  1. Скопируйте https://github.com/PHPixie/Project/blob/master/console в корень проекта. Если вы на линуксе то поставьте на этот файл права на исполнение (chmod +x console)
  2. Добавьте «phpixie/framework-bundle»:»~3.0» в composer.json
  3. Поключите бандл \PHPixie\FrameworkBundle и добавьте /*GeneratorPlaceholder*/
    как тут: https://github.com/PHPixie/Project/blob/master/src/Project/Framework/Bundles.php

Опционально можно также скопировать стандартную архитектуру и демо-команду с обновленного скелета проекта:

  1. Console.php
  2. Console/Greet.php
  3. В Builder классе банда подключите класс консоли как тут: Builder.php

Внимание, замените NamespacePlaceholder на неймспейс своего проекта (по умолчанию Project) и BundleNamePlaceholder на имя своего бандла (скорее всего это App).

После этого ваша структура будет такая-же как у свежего проекта.

Использование

Запустите консоль чтобы увидеть список доступных команд:

cd your_project_directory/

./console
# или
php ./console

Результат будет примерно таким:

Available commands:

app:greet                     Greet the user
framework:installWebAssets    Symlink or copy bundle web files to the projects web folder
framework:generateBundle      Generate a new bundle
help                          Print command list and usage

Команда help покажет больше информации и список доступных параметров:

./console help framework:installWebAssets

framework:installWebAssets [ --copy ]
Symlink or copy bundle web files to the projects web folder

Options:
copy    Whether to copy web directories instead of symlinking them

Стандартные команды

  • framework: installWebAssets — создает ярлыки в папке /web/bundles указующие на /web папки внутри бандлов, например /web/bundles/app → /bundles/app/web. Это делается для того чтобы бандлы инсталлированные с помощью композера могли предоставить свои веб файлы. Флажок --copy скопирует файлы вместо создания ярлыков. Это удобно например для создания архива для CDN.
  • framework: generateBundle — генерирует и подключает новый бандл в проект. Больше не придется вручную создавать бандлы путем копирования существующего.

Добавление собственных команд

У вас в проекте уже добавлена простая команда app: greet. Работа с командами полностью аналогична добавлению HTTP процессоров, используя класс \Project\App\Console. Достаточно добавить имя команды в массив возвращаемый методом commandNames () и добавить метод buildCommand.

В конструкторе команды вы можете задать описание и список параметров и аргументов:

namespace Project\App\Console;

class Greet extends \PHPixie\Console\Command\Implementation
{
    public function __construct($config)
    {
        // Описание
        $config->description('Greet the user');

        // Добавим параметр 'message'
        $config->argument('message')
            ->description("Message to display");

        parent::__construct($config);
    }

    /**
     * Этот метод вызывается при запуске команды.
     * $argumentData и $optionData работают так же
     * как HTTP $request->query() и $request->data()
     */
    public function run($argumentData, $optionData)
    {
        $message = $argumentData->get('message', "Have fun coding!");
        $this->writeLine($message);
    }
}

Аргументы и опции

Допустим мы хотим добавить следующую команду:

sqldump --user=root --skip-missing -f myDatabase users items

Здесь myDatabase имя базы данных, а за ней список таблиц которые мы хотели бы забэкапить. Это аргументы нашей команды. А user, skip-missing, и f опции. Заметьте что для аргументов важен порядок в котором они задаются, а для опций нет. Также короткие опции с одной буквы используют один знак - вместо двух.

В коде это будет выглядеть вот так:

$config->option('user')

    //Обязательная опция
    ->required()

    //Ее описание, будет показано при запуске команды 'help'
    ->description("User to connect to the database with");

$config->option('skip-missing')
    ->description("Don't throw an error if the tables are missing")

    //Задать опцию как флажок.
    //Опциям-флажкам не задается значение,
    //взамен они устанавливаются в 'true' если они присутствуют.
    ->flag();

$config->option('f')
    ->flag()
    ->description("Force database dump");

При описании аргументов надо помнить что задавать их надо в том порядке в котором они должны присутствовать в команде. В нашем случае аргумент database идеи перед tables:

$config->argument('database')
    ->required()
    ->description("Which database to dump the tables from");

$config->argument('tables')
    ->description("Tables to dump")

    // Принимает массив значений
    // В команде может быть толко один такой аргумент,
    // и логично что он должен быть последним
    ->arrayOf();

Теперь при запуске help мы получим вот такой результат:

./console help app:sqldump

app:sqldump --user=VALUE [ -f ] [ --skip-missing ] DATABASE [ TABLES... ]

Options:
user            User to connect to the database with
f               Force database dump
skip-missing    Dont throw an error if the tables are missing

Arguments:
DATABASE  Which database to dump the tables from
TABLES    Tables to dump

При запуске команды методу run () передадутся опции и аргументы, откуда их можно получить аналогично как и в HTTP процессоре:

public function run($argumentData, $optionData)
{
    $database = $argumentData->get('database');

    // С указанием дефолтного значения
    $user = $optionData->get('user', 'phpixie');
}

Ввод и вывод

Самый простой метод вывода в консоль это просто return-уть текст. Но если процесс должен работать долгое время и надо выводить промежуточный результат, то можно использовать дополнительные методы:

public function run($argumentData, $optionData)
{
    // Вывод текста
    $this->write("Hello ");

    // Вывод текста с новой строкой
    $this->writeLine("World");

    // Считать ввод пользователя с консоли
    $str = $this->readLine();

    // Если бросить CommandException то в консоли отобразится текст ошибки
    // и сама команда возвратит не-нулевой статус код (полезно при работе с Bash).
    throw new \PHPixie\Console\Exception\CommandException("Something bad happened");
}

Дополнительно можно получить доступ к CLI контексту и работать уже с ним:

public function run($argumentData, $optionData)
{
    $context = $this->cliContext();

    $inputStream = $cliContext->inputStream();
    $outputStream = $cliContext->outputStream();
    $errorStream = $cliContext->errorStream();

    $outputStream->write("Hello");
    $errorStream->writeLine("Something bad happened");
    $context->setExitCode(1); // указать код статуса

Код статуса пригодится при проверке удачно ли исполнилась команда извне, например из Bash:

if ./console app:somecommand ; then
    echo "Command succeeded"
else
    echo "Command failed"
fi

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

class YourCommandRegistry extends \PHPixie\Console\Registry\Provider\Implementation 
{
    public function commandNames()
    {
         return ['greet'];
    }

    public function buildGreetCommand($config)
    {
         return new Greet($config);
    }
}

$slice = new \PHPixie\Slice();
$cli = new \PHPixie\CLI();
$registry = new YourCommandRegistry();

$console = new \PHPixie\Console($slice, $cli, $registry);
$console->runCommand();

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

Комментарии (2)

  • 2 ноября 2016 в 21:11

    0

    А почему было решено делать свою версию? Вроде получилась плюс-минус та же Symfony Console.
    • 2 ноября 2016 в 21:30

      0

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

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

      И даже если смотреть на использование без фреймворка, как я писал, зависимостей меньше, работает даже на PHP 5.3 (ну если очень надо). Но главная разница в парадигме.

© Habrahabr.ru