Laravel Artisan Console — Custom Commands and CLI Development
In this tutorial, you will learn about Laravel Artisan Console. We cover key concepts, practical examples, and best practices to help you master this topic.
Laravel Artisan provides a command-line interface with custom commands supporting input arguments, options, progress bars, and scheduled task execution.
What You'll Learn
By the end of this tutorial, you'll create custom Artisan commands, handle input arguments and options, implement command logic with progress bars, and schedule commands.
Why Artisan Matters
Artisan commands automate repetitive tasks like data imports, report generation, and maintenance operations. Scheduled commands replace Cron Jobs with Laravel-managed tasks.
Real-World Use
A daily Artisan command imports orders from an external API, processes them, and sends a summary email. Another command generates invoice PDFs for the billing team.
Artisan Path
flowchart LR
A[Laravel Framework] --> B[Artisan Console]
B --> C[Commands]
B --> D[Scheduling]
B --> E[Input/Output]
B --> F[Progress Bars]
B --> G{You Are Here}
style G fill:#f90,color:#fff
Creating a Command
Generate and implement a custom Artisan command.
php artisan make:command ImportOrders
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\OrderImporter;
class ImportOrders extends Command {
protected $signature = "orders:import
{--source= : The data source URL}
{--limit=100 : Maximum orders to import}
{--force : Skip confirmation prompt}
{source? : Optional positional argument}";
protected $description = "Import orders from external API";
public function handle(OrderImporter $importer): int {
$this->info("Starting order import...");
$source = $this->option("source") ?? config("services.orders.api_url");
$limit = (int) $this->option("limit");
if (!$this->option("force") && !$this->confirm("Import from $source?")) {
$this->warn("Import cancelled.");
return Command::FAILURE;
}
$count = $importer->import($source, $limit);
$this->info("Imported $count orders successfully.");
return Command::SUCCESS;
}
}
Progress Bars
Show progress for long-running tasks.
<?php
public function handle(): int {
$users = User::where("needs_update", true)->get();
$bar = $this->output->createProgressBar(count($users));
$bar->start();
foreach ($users as $user) {
$user->updateFromApi();
$bar->advance();
}
$bar->finish();
$this->newLine();
$this->info("Updated " . count($users) . " users.");
return Command::SUCCESS;
}
Command Scheduling
Schedule commands in the Kernel.
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel {
protected function schedule(Schedule $schedule): void {
$schedule->command("orders:import --force")
->dailyAt("02:00")
->withoutOverlapping()
->runInBackground()
->onFailure(function () {
Log::error("Order import failed");
});
$schedule->command("reports:generate")
->weeklyOn(1, "06:00")
->emailOutputTo("admin@example.com");
$schedule->command("cache:clean")
->hourly()
->environments(["production"]);
}
}
Command Testing
Test Artisan commands with unit tests.
<?php
namespace Tests\Feature\Console;
use Tests\TestCase;
class ImportOrdersTest extends TestCase {
public function test_import_command_runs_successfully(): void {
$this->artisan("orders:import", ["--source" => "https://api.test.com/orders", "--force" => true])
->expectsOutput("Starting order import...")
->assertExitCode(0);
}
public function test_import_requires_confirmation(): void {
$this->artisan("orders:import")
->expectsQuestion("Import from https://api.example.com/orders?", "no")
->expectsOutput("Import cancelled.")
->assertExitCode(1);
}
}
Common Mistakes
1. Heavy Logic in handle() Method
Command handlers should call services. Business logic in commands is untestable.
2. Not Returning Exit Codes
Commands must return Command::SUCCESS or Command::FAILURE for scheduling to work.
3. Ignoring Command Failure Events
Use onFailure() in scheduling to handle failures gracefully.
4. Overlapping Commands
Without withoutOverlapping(), commands scheduled to run frequently may overlap.
5. Hardcoding Configuration in Commands
Use config() or environment variables for configurable values.
Practice Questions
1. How do you define command arguments?
In the signature using curly braces: {argument} for required, {argument?} for optional.
2. What does withoutOverlapping() do?
Prevents the command from running if a previous instance is still running.
3. How do you return a failure from a command?
Return Command::FAILURE or exit code 1.
4. How do you display a progress bar?
Use $this->output->createProgressBar($total) and call advance() in the loop.
5. Challenge: Create a command that exports data to CSV with progress.
<?php
class ExportUsers extends Command {
protected $signature = "users:export {--file=users.csv}";
public function handle(): int {
$users = User::all();
$file = fopen($this->option("file"), "w");
$bar = $this->output->createProgressBar(count($users));
foreach ($users as $user) {
fputcsv($file, [$user->id, $user->name, $user->email]);
$bar->advance();
}
$bar->finish();
$this->info("Exported " . count($users) . " users.");
return Command::SUCCESS;
}
}
FAQ
Mini Project: Data Export Command Suite
Build a suite of export commands with scheduling.
<?php
class DailyReport extends Command {
protected $signature = "report:daily {--format=csv}";
public function handle(): int {
$this->info("Generating daily report...");
$this->call("users:export", ["--file" => storage_path("reports/users.csv")]);
$this->call("orders:export", ["--file" => storage_path("reports/orders.csv")]);
$this->info("Reports generated successfully.");
return Command::SUCCESS;
}
}
What's Next
Laravel Queues Deep Laravel Horizon Laravel Broadcasting
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro