Artisan команды Laravel: создание, настройка и выполнение
Artisan - это интерфейс командной строки, встроенный в Laravel. Он предоставляет множество встроенных команд для миграций, кэширования, генерации кода и других задач. Однако разработчики часто создают собственные команды для автоматизации бизнес-логики. Ниже рассматривается основной способ создания команды, а также альтернативные варианты с примерами и разбором типичных проблем.
Создание и настройка Artisan команд
Наиболее эффективное решение: использование генератора make:command и описание логики в методе handle. Этот способ поддерживается всеми версиями Laravel и обеспечивает минимальное количество шаблонного кода.
php artisan make:command SendEmails
После выполнения будет создан файл app/Console/Commands/SendEmails.php. Внутри необходимо задать сигнатуру ($signature) и описание ($description), а в методе handle - бизнес-логику.
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SendEmails extends Command
{
protected $signature = 'email:send {user} {--queue}';
protected $description = 'Отправка email пользователю';
public function handle()
{
$userId = $this->argument('user');
$useQueue = $this->option('queue');
$this->info("Отправка письма пользователю {$userId}, очередь: " . ($useQueue ? 'да' : 'нет'));
}
}
Пояснение шагов: сигнатура email:send {user} {--queue} указывает, что команда называется email:send, принимает обязательный аргумент user и опцию без значения --queue. В методе handle аргумент получается через $this->argument('user'), опция - через $this->option('queue'). Метод info выводит сообщение зелёным цветом.
Типичная ошибка: если в сигнатуре допущена опечатка, команда не будет зарегистрирована и появится ошибка Command "email:send" is not defined. Решение - проверить файл app/Console/Kernel.php (команды регистрируются автоматически, если класс находится в пространстве имён App\Console\Commands).
Как добавить необязательные аргументы и опции со значениями?
Аргумент можно сделать необязательным, добавив вопросительный знак: {user?}. Для опции со значением используется двойное тире и знак равенства: {--timeout=30} (значение по умолчанию 30).
protected $signature = 'report:generate {start?} {--format=html}';
В handle проверяется наличие аргумента через $this->argument('start') - если он не был передан, вернётся null. Опция format вернёт 'html' по умолчанию.
Ошибка: если опция объявлена как {--format} (без значения), то при передаче --format=pdf будет использовано значение true, а не 'pdf'. Для получения значения нужно обязательно указать знак равенства в сигнатуре.
Как выполнить команду из кода приложения?
Использование фасада Artisan позволяет запускать команды внутри контроллеров, очередей или других команд.
use Illuminate\Support\Facades\Artisan;
$exitCode = Artisan::call('email:send', [
'user' => 42,
'--queue' => true,
]);
Первый параметр - имя команды, второй - массив аргументов и опций. Возвращается код выхода (0 - успех, 1 - ошибка). Если нужно получить вывод, используется Artisan::output().
Проблема: при вызове команды внутри другой команды могут возникнуть конфликты с буферизацией вывода. Решение - временно отключить буферизацию или использовать Artisan::call с флагом --quiet.
Как использовать интерактивные запросы (ask, confirm)?
Методы ask, confirm, secret, choice позволяют взаимодействовать с пользователем в терминале.
public function handle()
{
$name = $this->ask('Введите имя пользователя:');
$notify = $this->confirm('Отправить уведомление?', true);
$this->info("Имя: $name, уведомление: " . ($notify ? 'да' : 'нет'));
}
Вторым параметром можно задать значение по умолчанию. Метод choice возвращает выбранный элемент из списка.
Распространённая ошибка: если команда запускается в неинтерактивном режиме (например, в CRON), ask и confirm вернут значение по умолчанию, и это может привести к неожиданному поведению. Всегда проверяйте, имеет ли команда интерактивный ввод, или используйте опции вместо запросов.
Как скрыть команду из списка php artisan list?
Установите свойство $hidden = true в классе команды.
protected $hidden = true;
Команда останется доступной для выполнения, но не будет отображаться в общем списке. Это полезно для внутренних служебных команд.
Проблема: если команда скрыта, её всё равно можно найти через php artisan help имя_команды. Для полной изоляции используйте кастомные сервис-провайдеры с условной регистрацией.
Как отобразить прогресс выполнения длительных операций?
Прогресс-бар создаётся через метод createProgressBar объекта вывода.
$bar = $this->output->createProgressBar($totalItems);
$bar->start();
foreach ($items as $item) {
// обработка
$bar->advance();
}
$bar->finish();
Можно менять формат и добавлять сообщения.
Ошибка в тестах: прогресс-бар использует поток вывода, который может отсутствовать в тестовом окружении. Решение - проверять наличие вывода или отключать прогресс-бар при APP_ENV=testing.
Расширенные примеры Artisan команд
Пример 1: Команда для импорта CSV с прогресс-баром
Команда принимает путь к файлу и опцию размера чанка, обрабатывает строки и отображает прогресс.
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class ImportCsv extends Command
{
protected $signature = 'import:csv {file} {--chunk=100}';
protected $description = 'Импорт CSV файла в базу данных';
public function handle()
{
$file = $this->argument('file');
$chunkSize = $this->option('chunk');
if (!file_exists($file)) {
$this->error('Файл не найден: ' . $file);
return 1;
}
$rows = array_map('str_getcsv', file($file));
$header = array_shift($rows);
$total = count($rows);
$bar = $this->output->createProgressBar($total);
$bar->start();
foreach (array_chunk($rows, $chunkSize) as $chunk) {
$data = [];
foreach ($chunk as $row) {
$data[] = array_combine($header, $row);
}
DB::table('imports')->insert($data);
$bar->advance(count($chunk));
}
$bar->finish();
$this->newLine();
$this->info("Импорт завершён. Всего строк: $total");
}
}
> php artisan import:csv data.csv --chunk=50 [============================] 100% Импорт завершён. Всего строк: 500
Пример 2: Вызов одной команды из другой и обработка вывода
Внутри команды можно запустить другую команду и получить её вывод.
public function handle()
{
$this->info('Начинаем миграцию...');
Artisan::call('migrate', ['--force' => true]);
$output = Artisan::output();
$this->line($output);
// Выполняем сиды
$exitCode = Artisan::call('db:seed', ['--force' => true]);
if ($exitCode !== 0) {
$this->error('Сиды не выполнены');
}
}
Начинаем миграцию... Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (0.01 seconds) ...
Важно: для использования Artisan::call внутри команды нужно добавить use Illuminate\Support\Facades\Artisan;.
Пример 3: Команда с табличным выводом
Метод table форматирует данные в виде таблицы.
public function handle()
{
$headers = ['ID', 'Имя', 'Email'];
$users = [
[1, 'Иван', 'ivan@example.com'],
[2, 'Мария', 'maria@example.com'],
];
$this->table($headers, $users);
}
+----+-------+------------------+ | ID | Имя | Email | +----+-------+------------------+ | 1 | Иван | ivan@example.com | | 2 | Мария | maria@example.com| +----+-------+------------------+
Пример 4: Использование метода confirm с автоматическим ответом
В неинтерактивном режиме (CRON, тесты) можно передать опцию --force и пропустить подтверждение.
public function handle()
{
if (!$this->option('force') && !$this->confirm('Вы уверены?')) {
$this->error('Операция отменена.');
return 1;
}
// выполнение
}
Теперь команду можно запускать с флагом --force, чтобы пропустить интерактивный запрос.
> php artisan dangerous:operation --force