Консоль Artisan

Введение

Artisan - это интерфейс командной строки (CLI) входящий в состав Laravel. Он предоставляет ряд команд, которые будут полезны при разработке вашего приложения. Чтобы посмотреть список всех доступных Artisan-команд, вы можете воспользоваться командой list:

php artisan list

Каждая команда так же содержит "подсказку", которая отображает и описывает все возможные аргументы и опции доступные для команды. Чтобы увидеть подсказку, просто напишите перед названием команды слово help:

php artisan help migrate

Интерфейс ввода/вывода Laravel REPL

В состав всех Laravel приложений входит Tinker, REPL интерфейс основанный на пакете PsySH. Tinker позволяет вам взаимодействовать полностью со всем Laravel приложением из командной строки, включая Eloquent ORM, задачи в очереди, события, и т.д. Чтобы запустить Tinker, выполните Artisan команду tinker:

php artisan tinker

Написание команд

Помимо базовых команд включенных в состав Artisan, вы так же можете написать свои собственные команды. Обычно они хранятся в директории app/Console/Commands; однако, вы можете сами выбрать место хранения команд, лишь бы они загружались с помощью Composer.

Генерация команд

Чтобы создать новую команду, вам нужно выполнить Artisan-команду make:command. Она создаст вам новый класс команды в директории app/Console/Commands. Не переживайте по поводу того, что этой папки не существует в вашем приложении. Она создастся автоматически, как только вы запустите Artisan-команду make:command в первый раз. Сгенерированный файл будут содержать стандартный набор свойств и методов необходимый для всех команд:

php artisan make:command SendEmails

Затем вам надо зарегистрировать команду, тогда она сможет быть запущена через командный интерфейс Artisan.

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

После генерации вашей команды вы должны заполнить свойства класса signature и description, которые используются для отображения вашей команды в списке list. Когда команда будет запущена, будет исполнен метод handle. В этом методе вы можете разместить логику команды.

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

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

<?php

namespace App\Console\Commands;

use App\User;
use App\DripEmailer;
use Illuminate\Console\Command;

class SendEmails extends Command
{
    /**
     * Имя и сигнатура консольной команды.
     *
     * @var string
     */
    protected $signature = 'email:send {user}';

    /**
     * Описание консольной команды.
     *
     * @var string
     */
    protected $description = 'Send drip e-mails to a user';

    /**
     * Служба "капельных" e-mail сообщений.
     *
     * @var DripEmailer
     */
    protected $drip;

    /**
     * Создание нового экземпляра команды.
     *
     * @param  DripEmailer  $drip
     * @return void
     */
    public function __construct(DripEmailer $drip)
    {
        parent::__construct();

        $this->drip = $drip;
    }

    /**
     * Выполнение консольной команды.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->drip->send(User::find($this->argument('user')));
    }
}

Команды на функциях-замыканиях

Команды, основанные на функциях-замыканиях (анонимных функциях), предоставляют альтернативу определению команд как классов. Точно так же, как и роуты с анонимными функциями являются альтернативой контроллерам, так и команды с анонимными функциями являются альтернативой командам-классам. Внутри метода commands файла app/Console/Kernel.php Laravel загружает файл routes/console.php:

/**
 * Регистрация команд для приложения.
 *
 * @return void
 */
protected function commands()
{
    require base_path('routes/console.php');
}

Хотя этот файл не определяет HTTP роуты, зато он определяет точки входа в консоль вашего приложения. Внутри этого файла вы можете определить все точки на основе функций-замыканий, используя методArtisan::command. Метод command принимает два аргумента: сигнатуру команды и функцию, которая принимает аргументы и опции самой команды:

Artisan::command('build {project}', function ($project) {
    $this->info("Building {$project}!");
});

Функция-замыкание будет привязана к экземпляру команды, так что вы будете иметь полный доступ ко всем методам, которым обычно есть доступ у команд-классов.

Объявление типов (Type-Hinting) зависимостей

Помимо получения аргументов и опций, в команде на основе анонимной функции могут быть дополнительно объявлены типы зависимостей, которые вы захотите получить из сервис-контейнера:

use App\User;
use App\DripEmailer;

Artisan::command('email:send {user}', function (DripEmailer $drip, $user) {
    $drip->send(User::find($user));
});

Описание команды на основе функции-замыкания

Когда вы определяете команду на основе функции, вы так же можете использовать метод describe, чтобы добавить описание для вашей команды. Это описание будет отображаться, когда вы запустите команды php artisan list или php artisan help:

Artisan::command('build {project}', function ($project) {
    $this->info("Building {$project}!");
})->describe('Build the project');

Определенние входных данных

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

Аргументы

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

/**
 * Имя и сигнатура консольной команды.
 *
 * @var string
 */
protected $signature = 'email:send {user}';

Также вы можете сделать аргументы необязательными или определить для аргументов значение по умолчанию:

// Необязательный аргумент...
email:send {user?}

// Необязательный аргумент со значением по умолчанию...
email:send {user=foo}

Опции

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

/**
 * Имя и сигнатура консольной команды.
 *
 * @var string
 */
protected $signature = 'email:send {user} {--queue}';

В этом примере опция --queue может быть указана при вызове Artisan команды. Если будет передана опция --queue, то значение этой опции будет true. В противном случае значение будет false:

php artisan email:send 1 --queue

Опции со значениями

Теперь давайте взглянем на опции, которые должны принимать значения. Если вы хотите, чтобы пользователь передал вам значение опции, поставьте после названия опции знак =:

/**
 * Имя и сигнатура консольной команды.
 *
 * @var string
 */
protected $signature = 'email:send {user} {--queue=}';

В этом примере пользователь может передать значение опции, как указано ниже:

php artisan email:send 1 --queue=default

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

email:send {user} {--queue=default}

Сокращения для опций

Чтобы назначить сокращение при определении опции, вы можете указать его до названия опции и использовать разделитель |, чтобы отделить сокращение от полного названия опции:

email:send {user} {--Q|queue}

Массивы входных данных

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

email:send {user*}

При вызове этого метода, аргументы user могут быть указаны по очереди. Например, следующая команда установитuser значение ['foo', 'bar']:

php artisan email:send foo bar

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

email:send {user} {--id=*}

php artisan email:send --id=1 --id=2

Описания входных данных

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

/**
 * Имя и сигнатура консольной команды.
 *
 * @var string
 */
protected $signature = 'email:send
                        {user : ID пользователя}
                        {--queue= : Ставить ли задачу в очередь}';

Команда ввода/вывода

Получение входных данных

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

/**
 * Выполнение консольной команды.
 *
 * @return mixed
 */
public function handle()
{
    $userId = $this->argument('user');

    //
}

Если вы хотие получить все аргументы в виде массива array, вызовите метод arguments:

$arguments = $this->arguments();

Получить опции так же просто, как и аргументы - нужно использовать метод option. Чтобы получить все опции в виде массива, воспользуйтесь методом options:

// Получение конкретной опции...
$queueName = $this->option('queue');

// Получение всех опций...
$options = $this->options();

Если аргумент или опция не существуют, тогда функция вернет значение null.

Запрашивание входных данных

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

/**
 * Выполнение консольной команды.
 *
 * @return mixed
 */
public function handle()
{
    $name = $this->ask('What is your name?');
}

Метод secret похож на ask, только пользовательские входные данные не будут видны в консоли. Этот метод пригодится тогда, когда вам нужно запросить конфиденциальную информацию, например, пароль:

$password = $this->secret('What is the password?');

Запрос подтверждения

Если вам нужно запросить у пользователя простое подтверждение, вы можете воспользоваться методом confirm. По умолчанию этот метод вернет false. Однако, если пользователь введет y или yes в ответ на запрос, тогда метод вернет true.

if ($this->confirm('Do you wish to continue?')) {
    //
}

Автовыбор

Метод anticipate может использоваться, чтобы обеспечить автовыбор возможных вариантов. Пользователь может выбрать любой ответ, независимо от подсказок автовыбора:

$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']);

Вопросы с множественным выбором

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

$name = $this->choice('What is your name?', ['Taylor', 'Dayle'], $default);

Вывод данных

Чтобы вывести данные в консоль, воспользуйтесь методами line, info, comment, question и error. Каждый из этих методов будет использовать соответствующие ANSI цвета для своего предназначения. Например, давайте отобразим некоторую общую информацию пользователю. Как правило, метод info отобразится в консоли в виде зеленого текста:

/**
 * Выполнение консольной команды.
 *
 * @return mixed
 */
public function handle()
{
    $this->info('Отобразить это на экране');
}

Чтобы отобразить сообщение об ошибке, используйте метод error. Сообщения об ошибке обычно отображаются красным цветом:

$this->error('Что-то пошло не так!');

Если вы хотите вывести обычное, не цветное сообщение, используйте метод line:

$this->line('Отобразить это на экране');

Табличная разметка

Метод table легко и правильно отформатирует данные с несколькими строками/колонками. Просто передайте в метод заголовки и строки. Ширина и высота будут динамически рассчитаны на основании предоставленных данных:

$headers = ['Name', 'Email'];

$users = App\User::all(['name', 'email'])->toArray();

$this->table($headers, $users);

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

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

$users = App\User::all();

$bar = $this->output->createProgressBar(count($users));

foreach ($users as $user) {
    $this->performTask($user);

    $bar->advance();
}

$bar->finish();

Для более продвинутых возможностей, посмотрите документацию компонента Symfony Progress Bar.

Регистрация команд

После того, как вы напишите свою команду, вам необходимо будет зарегистрировать ее в Artisan. Все команды регистрируются в файле app/Console/Kernel.php. Внутри файла вы найдете список всех команд в свойстве commands. Чтобы зарегистрировать свою, просто добавьте название класса команды в список. Когда Artisan загрузится, все команды из этого списка будут доступны в сервис-контейнере и зарегистрированы в Artisan:

protected $commands = [
    Commands\SendEmails::class
];

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

Иногда вам может потребоваться выполнить Artisan команду не в интерфейсе командной строки. Например, вы захотите запустить Artisan команду из роута или контроллера. Для этого вы можете воспользоваться методом call фасада Artisan. Метод call принимает название команды первым аргументом и массив параметров вторым аргументом. Возрващен будет код выхода:

Route::get('/foo', function () {
    $exitCode = Artisan::call('email:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    //
});

Используя метод queue в фасаде Artisan, вы можете запустить Artisan-команды в очереди так, что они будут выполнятся в фоне вашим обработчиком очереди. Прежде чем использовать этот метод, убедитесь, что вы настроили очереди и запустили обработчик очереди:

Route::get('/foo', function () {
    Artisan::queue('email:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    //
});

Если вам нужно указать значение опции, которая не принимает текстовых значений, такую как опция --force в команде migrate:refresh, то можете передать true или false:

$exitCode = Artisan::call('migrate:refresh', [
    '--force' => true,
]);

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

Иногда вам потребуется вызвать одни команды из других существующих Artisan команд. Вы можете сделать это, используя метод call. Этот метод call принимает название команды и массив с параметрами:

/**
 * Выполнение консольной команды.
 *
 * @return mixed
 */
public function handle()
{
    $this->call('email:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    //
}

Если вам потребуется вызвать другую консольную команду и скрыть все ее выходные данные, то вы можете использовать метод callSilent. У этого метода такая же сигнатура, как и у метода call:

$this->callSilent('email:send', [
    'user' => 1, '--queue' => 'default'
]);