Laravel Framework

Artisan Console Commands

15 min Lesson 25 of 45

Artisan Console Commands

Artisan is Laravel's command-line interface, providing dozens of helpful commands for building your application. Beyond using built-in commands, you can create custom commands to automate tasks, run scheduled jobs, process data, and more. This lesson covers creating and working with custom Artisan commands.

Understanding Artisan Commands

Artisan commands are PHP classes that can be executed from the command line. They're perfect for:

  • Automated tasks and cron jobs
  • Data processing and imports
  • Database maintenance and cleanup
  • Generating reports
  • Sending notifications or emails in bulk
  • Running tests or health checks
Creating a Command:
// Generate a new command
php artisan make:command SendNewsletterCommand

// app/Console/Commands/SendNewsletterCommand.php
namespace App\Console\Commands;

use Illuminate\Console\Command;

class SendNewsletterCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'newsletter:send';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Send newsletter to all subscribers';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        $this->info('Sending newsletter...');

        // Command logic here

        $this->info('Newsletter sent successfully!');

        return Command::SUCCESS;
    }
}
Command Registration: Laravel automatically discovers and registers all commands in the app/Console/Commands directory. You can run your command with php artisan newsletter:send.

Command Signature and Arguments

The command signature defines how the command is called, including arguments and options:

Command Signatures:
namespace App\Console\Commands;

use Illuminate\Console\Command;

class UserManagementCommand extends Command
{
    // Simple command
    protected $signature = 'user:create';

    // With required argument
    protected $signature = 'user:create {name}';

    // With optional argument
    protected $signature = 'user:create {name?}';

    // With default value
    protected $signature = 'user:create {name=John}';

    // With argument description
    protected $signature = 'user:create {name : The name of the user}';

    // Multiple arguments
    protected $signature = 'user:create {name} {email}';

    // With option (flag)
    protected $signature = 'user:create {name} {--admin}';

    // Option with value
    protected $signature = 'user:create {name} {--role=}';

    // Option with default value
    protected $signature = 'user:create {name} {--role=user}';

    // Option shortcut
    protected $signature = 'user:create {name} {--R|role=}';

    // Option array
    protected $signature = 'user:create {name} {--role=*}';

    // Complete example
    protected $signature = 'user:create
                            {name : The user name}
                            {email : The user email}
                            {--admin : Make user an administrator}
                            {--role=* : Assign roles to user}
                            {--force : Force creation without confirmation}';

    protected $description = 'Create a new user account';

    public function handle()
    {
        // Access arguments
        $name = $this->argument('name');
        $email = $this->argument('email');

        // Access options
        $isAdmin = $this->option('admin');
        $roles = $this->option('role');
        $force = $this->option('force');

        // Get all arguments/options
        $allArguments = $this->arguments();
        $allOptions = $this->options();

        return Command::SUCCESS;
    }
}

// Usage examples:
// php artisan user:create "John Doe" john@example.com
// php artisan user:create "Jane" jane@example.com --admin
// php artisan user:create "Bob" bob@example.com --role=editor --role=author
// php artisan user:create "Alice" alice@example.com --force -R admin

Interactive Input and Output

Commands can interact with users through various input and output methods:

Input and Output Methods:
namespace App\Console\Commands;

use Illuminate\Console\Command;

class InteractiveCommand extends Command
{
    protected $signature = 'app:interactive';
    protected $description = 'Demonstrate interactive input/output';

    public function handle()
    {
        // Output methods
        $this->info('This is informational text');
        $this->comment('This is a comment');
        $this->question('This is a question');
        $this->error('This is an error message');
        $this->warn('This is a warning');
        $this->line('This is plain text');

        // New line
        $this->newLine();
        $this->newLine(3); // Three blank lines

        // Ask for input
        $name = $this->ask('What is your name?');
        $email = $this->ask('What is your email?', 'default@example.com');

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

        // Confirmation
        if ($this->confirm('Do you want to continue?')) {
            $this->info('Continuing...');
        }

        // Confirmation with default
        if ($this->confirm('Delete all records?', false)) {
            // Only runs if user explicitly confirms
        }

        // Choice selection
        $role = $this->choice(
            'What role should the user have?',
            ['admin', 'editor', 'viewer'],
            0  // Default index
        );

        // Multiple choice
        $roles = $this->choice(
            'Select roles (separate multiple with comma)',
            ['admin', 'editor', 'viewer'],
            0,
            $maxAttempts = null,
            $allowMultipleSelections = true
        );

        // Anticipate - autocomplete
        $country = $this->anticipate('Select country', ['USA', 'UK', 'Canada']);

        // Table output
        $this->table(
            ['Name', 'Email', 'Role'],
            [
                ['John Doe', 'john@example.com', 'Admin'],
                ['Jane Smith', 'jane@example.com', 'Editor'],
            ]
        );

        return Command::SUCCESS;
    }
}
User Experience: Use colors and formatting to make command output clear and easy to read. Use confirm() for destructive operations to prevent accidents.

Progress Bars and Task Tracking

For long-running operations, progress bars provide visual feedback:

Progress Indicators:
namespace App\Console\Commands;

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

class ProcessUsersCommand extends Command
{
    protected $signature = 'users:process';
    protected $description = 'Process all users';

    public function handle()
    {
        $users = User::all();

        // Simple progress bar
        $bar = $this->output->createProgressBar($users->count());
        $bar->start();

        foreach ($users as $user) {
            // Process user
            $this->processUser($user);

            $bar->advance();
        }

        $bar->finish();
        $this->newLine();

        // withProgressBar helper
        $this->withProgressBar($users, function ($user) {
            $this->processUser($user);
        });

        $this->newLine();

        return Command::SUCCESS;
    }

    protected function processUser($user)
    {
        // Simulate processing
        sleep(0.1);
    }
}

Calling Other Commands

Commands can call other Artisan commands programmatically:

Calling Commands:
namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;

class DeployCommand extends Command
{
    protected $signature = 'app:deploy';
    protected $description = 'Deploy the application';

    public function handle()
    {
        $this->info('Starting deployment...');

        // Call another command
        $this->call('down');

        // Call with arguments and options
        $this->call('migrate', [
            '--force' => true,
        ]);

        // Call silently (no output)
        $this->callSilently('db:seed');

        // Call and capture output
        $exitCode = $this->call('config:cache');

        if ($exitCode === 0) {
            $this->info('Config cached successfully');
        }

        // Using Artisan facade
        Artisan::call('optimize');

        // Get output from Artisan facade
        Artisan::call('inspire');
        $output = Artisan::output();
        $this->line($output);

        $this->call('up');
        $this->info('Deployment complete!');

        return Command::SUCCESS;
    }
}

Dependency Injection in Commands

Commands support dependency injection in the handle method:

Injecting Dependencies:
namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\ReportGenerator;
use App\Services\EmailService;
use Illuminate\Support\Facades\Storage;

class GenerateReportCommand extends Command
{
    protected $signature = 'report:generate {type}';
    protected $description = 'Generate and email reports';

    /**
     * Dependencies injected into handle method
     */
    public function handle(
        ReportGenerator $generator,
        EmailService $emailService
    ) {
        $type = $this->argument('type');

        $this->info("Generating {$type} report...");

        // Use injected services
        $report = $generator->generate($type);

        $filename = "report-{$type}-" . now()->format('Y-m-d') . '.pdf';
        Storage::put("reports/{$filename}", $report);

        $this->info('Report generated: ' . $filename);

        if ($this->confirm('Email the report?')) {
            $email = $this->ask('Email address?');
            $emailService->sendReport($email, $report);
            $this->info('Report emailed to ' . $email);
        }

        return Command::SUCCESS;
    }
}

Scheduling Commands

Commands can be scheduled to run automatically using Laravel's task scheduler:

Scheduling Commands:
// app/Console/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
    {
        // Run command every day at midnight
        $schedule->command('newsletter:send')
                 ->daily();

        // Run command every hour
        $schedule->command('backup:database')
                 ->hourly();

        // Run command with arguments and options
        $schedule->command('report:generate monthly --email')
                 ->monthlyOn(1, '09:00');

        // Using call method
        $schedule->call(function () {
            // Closure logic
        })->everyFifteenMinutes();

        // Frequency options
        $schedule->command('emails:send')->everyMinute();
        $schedule->command('backup:run')->everyFiveMinutes();
        $schedule->command('report:weekly')->weekly();
        $schedule->command('cleanup')->dailyAt('13:00');
        $schedule->command('invoice:generate')->monthlyOn(15, '08:00');

        // Conditional scheduling
        $schedule->command('emails:send')
                 ->daily()
                 ->when(function () {
                     return date('w') !== 0; // Don't run on Sundays
                 });

        // Prevent overlaps
        $schedule->command('process:large-dataset')
                 ->hourly()
                 ->withoutOverlapping();

        // Run in background
        $schedule->command('backup:run')
                 ->daily()
                 ->runInBackground();

        // Send output to file
        $schedule->command('report:generate')
                 ->daily()
                 ->sendOutputTo(storage_path('logs/report.log'));
    }
}

// Setup cron job to run scheduler (only once):
// * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Practice Exercise 1: Create a command called users:cleanup that deletes users who haven't logged in for over 180 days. Add a --dry-run option to preview what would be deleted without actually deleting. Show a confirmation prompt before deleting (unless --force is used). Display a table of users to be deleted and use a progress bar during deletion.
Practice Exercise 2: Build a report:sales command that accepts {period} argument (daily/weekly/monthly) and generates a sales report. Ask for email recipients interactively if --email option is present. Inject ReportGenerator and EmailService dependencies. Display report summary in a formatted table and save the report to storage.
Practice Exercise 3: Create a db:backup-and-optimize command that calls multiple commands in sequence: backs up the database, runs migrations, optimizes tables, and clears old logs. Add proper error handling and rollback if any step fails. Schedule it to run daily at 3 AM in the Kernel.

Summary

Artisan commands are powerful tools for automating Laravel applications:

  • Command Signature: Define arguments and options flexibly
  • Interactive I/O: Ask questions, show progress, format output
  • Calling Commands: Execute other commands programmatically
  • Dependency Injection: Inject services into handle method
  • Scheduling: Automate command execution with cron
  • Return Codes: Use Command::SUCCESS and Command::FAILURE

With Eloquent advanced queries, API Resources, the Service Container, Service Providers, and Artisan Commands, you now have a comprehensive understanding of Laravel's core architecture and can build sophisticated applications efficiently.