404 Not Found

404 Not Found


nginx

The Laravel Artisan Console and Custom Commands

Artisan is Laravel's "Swiss Army knife"—with over 100 built-in commands covering all operations and maintenance tasks, and custom commands that let you handle repetitive tasks with a single click.

1. What You'll Learn


2. A True Story of an Operations Engineer

(1) Pain Point: Manually performing 20 operations tasks every day

Every morning, Charlie has to manually perform the following tasks: clear expired sessions, generate daily reports, sync subscription statuses, send expiration reminder emails, back up the database... Twenty tasks spread across five terminal windows—if he misses even one, he'll have to pay out of pocket. Bob asked, "Aren't these all scheduled tasks? Why do you have to do them manually?"

(2) Solutions for Artisan Commands and Scheduling

Artisan custom commands wrap repetitive tasks into a single command, and Schedule automatically runs them on a schedule—Charlie only needs to glance at the execution log once a day.

BASH
# One command does everything
php artisan shopmetrics:daily-maintenance

# Or let the scheduler run it automatically at 2 AM
php artisan schedule:run

(3) Revenue

After Charlie set up scheduling with Artisan, all 20 tasks ran automatically, and the failure rate dropped from 5% to 0%.


3. Complete List of Built-in Commands

(1) Categories of Common Commands

Category Command Description
Application about Environmental Information Overview
down / up Maintenance Mode Switch
env Show .env configuration
Database migrate Execute Migration
migrate:rollback Rollback Migration
migrate:fresh Rebuild Database
db:seed Data Filling
db:show Database Information
Cache cache:clear Clear Cache
config:cache / clear Configuration Cache
route:cache / clear Routing Cache
view:cache / clear View Cache
Routes route:list List all routes
Queue queue:work Start Worker
queue:failed View Failed Tasks
queue:retry Tasks that failed on retry
Generate make:model Create Model
make:controller Create Controller
make:migration Create Migration
make:command Create Command

(1) ▶ Example: Quick Reference for Common Artisan Commands

BASH
# App info
php artisan about
php artisan env

# Database operations
php artisan migrate --seed
php artisan db:show --counts

# Cache management
php artisan cache:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize           # Cache config + route + view

# Maintenance mode
php artisan down --secret="maintenance-token"  # Allow access with ?secret=
php artisan up

# Queue management
php artisan queue:work --queue=high,default
php artisan queue:failed
php artisan queue:retry all

# Route inspection
php artisan route:list --path=api
php artisan route:list --columns=method,uri,name

Output:

TEXT
# Command executed successfully

4. Custom Commands

(1) Create Command

BASH
php artisan make:command ProcessTenantAnalytics
# Creates: app/Console/Commands/ProcessTenantAnalytics.php

(2) Command Signature Syntax

PHP
// app/Console/Commands/ProcessTenantAnalytics.php
class ProcessTenantAnalytics extends Command
{
    // Signature syntax: {argument} {--option}
    protected $signature = 'shopmetrics:analytics
                            {tenant? : Tenant ID or slug (optional)}
                            {--type=monthly : Report type (daily|weekly|monthly)}
                            {--force : Force re-calculation}
                            {--format=csv : Output format (csv|json)}';

    protected $description = 'Process analytics for tenants';

    public function handle(): int
    {
        $tenant = $this->argument('tenant');
        $type = $this->option('type');
        $force = $this->option('force');

        if ($tenant) {
            $this->processSingleTenant($tenant, $type, $force);
        } else {
            $this->processAllTenants($type, $force);
        }

        return self::SUCCESS;
    }
}

(3) Signature Syntax Rules

Syntax Description Example
{name} Required parameter {tenant}
{name?} Optional parameters {tenant?}
{name=default} With default value {type=monthly}
{--option} Boolean Options --force
{--option=default} Options with Values --format=csv
{--O|shortcut} Short options --force|f

(1) ▶ Example: ShopMetrics Tenant Management Commands

PHP
// app/Console/Commands/ManageTenant.php
class ManageTenant extends Command
{
    protected $signature = 'shopmetrics:tenant
                            {action : Action to perform (list|suspend|activate|stats)}
                            {tenant? : Tenant ID or slug}
                            {--with-users : Include user statistics}';

    protected $description = 'Manage ShopMetrics tenants';

    public function handle(): int
    {
        $action = $this->argument('action');

        match ($action) {
            'list' => $this->listTenants(),
            'suspend' => $this->suspendTenant(),
            'activate' => $this->activateTenant(),
            'stats' => $this->showTenantStats(),
            default => $this->error("Unknown action: {$action}"),
        };

        return self::SUCCESS;
    }

    private function listTenants(): void
    {
        $tenants = Tenant::withCount(['shops', 'users'])->get();
        $this->table(
            ['ID', 'Name', 'Slug', 'Status', 'Shops', 'Users'],
            $tenants->map(fn ($t) => [
                $t->id, $t->name, $t->slug, $t->status,
                $t->shops_count, $t->users_count,
            ])
        );
    }

    private function suspendTenant(): void
    {
        $identifier = $this->argument('tenant') ?? $this->ask('Enter tenant ID or slug:');
        $tenant = $this->resolveTenant($identifier);
        $tenant->update(['status' => 'suspended']);
        $this->info("Tenant {$tenant->name} has been suspended.");
    }

    private function resolveTenant(string $identifier): Tenant
    {
        return is_numeric($identifier)
            ? Tenant::findOrFail($identifier)
            : Tenant::whereSlug($identifier)->firstOrFail();
    }
}

Output:

TEXT
// Execution Successful

5. Command Arguments and Options

(1) Retrieve Parameters

PHP
// Arguments
$name = $this->argument('name');        // Single argument
$all = $this->arguments();              // All arguments as array

// Options
$force = $this->option('force');        // Single option (boolean or value)
$all = $this->options();                // All options as array

(2) Array Parameters

PHP
// Signature with array argument
protected $signature = 'shopmetrics:report
                        {tenants* : One or more tenant IDs}
                        {--type=monthly}';

// Usage
php artisan shopmetrics:report 1 2 3 --type=weekly

// Access
$tenants = $this->argument('tenants'); // [1, 2, 3]

(3) Output Methods

Method Description Sample Output
info() Green Information ✓ Done
error() Red Error ✗ Failed
warn() Yellow Warning ⚠ Warning
line() Plain text Plain text
table() Table Format Table
progressBar() Progress Bar ██████░░ 60%

(1) ▶ Example: ShopMetrics data cleaning command with a progress bar

PHP
// app/Console/Commands/CleanupExpiredData.php
class CleanupExpiredData extends Command
{
    protected $signature = 'shopmetrics:cleanup
                            {--days=90 : Delete data older than N days}
                            {--dry-run : Show what would be deleted}';

    protected $description = 'Clean up expired data (old reports, expired tokens)';

    public function handle(): int
    {
        $days = $this->option('days');
        $dryRun = $this->option('dry-run');
        $cutoff = now()->subDays($days);

        // Clean expired tokens
        $expiredTokens = Sanctum::$personalAccessTokenModel::where('last_used_at', '<', $cutoff);
        $this->info(($dryRun ? 'Would delete' : 'Deleting') . " {$expiredTokens->count()} expired tokens.");

        // Clean old reports from S3
        $bar = $this->output->createProgressBar(Tenant::count());
        $deletedFiles = 0;

        Tenant::chunk(100, function ($tenants) use ($cutoff, $dryRun, &$deletedFiles, $bar) {
            foreach ($tenants as $tenant) {
                $files = Storage::disk('s3')->allFiles("reports/{$tenant->slug}");
                foreach ($files as $file) {
                    if (Storage::disk('s3')->lastModified($file) < $cutoff->timestamp) {
                        if (!$dryRun) Storage::disk('s3')->delete($file);
                        $deletedFiles++;
                    }
                }
                $bar->advance();
            }
        });

        $bar->finish();
        $this->newLine();
        $this->info(($dryRun ? 'Would delete' : 'Deleted') . " {$deletedFiles} old report files.");

        if (!$dryRun) {
            $expiredTokens->delete();
        }

        return self::SUCCESS;
    }
}

Output:

TEXT
// Execution Successful

6. Command Scheduling

(1) Definition of Scheduling

PHP
// routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('shopmetrics:analytics --type=daily')
    ->dailyAt('02:00')
    ->onOneServer()
    ->withoutOverlapping()
    ->emailOutputOnFailure('admin@shopmetrics.io');

Schedule::command('shopmetrics:cleanup --days=90')
    ->weekly()
    ->sundays()
    ->at('03:00')
    ->onOneServer();

Schedule::command('shopmetrics:sync-subscriptions')
    ->dailyAt('06:00')
    ->onOneServer()
    ->withoutOverlapping();

Schedule::job(new ProcessMonthlyAnalyticsJob)
    ->monthlyOn(1, '00:00')
    ->onOneServer();

(2) Scheduling Frequency Options

Method Frequency Equivalent Cron
everyMinute() Per Minute * * * * *
everyFiveMinutes() Every 5 minutes */5 * * * *
hourly() Per hour 0 * * * *
daily() 12:00 a.m. every day 0 0 * * *
dailyAt('14:00') 2:00 p.m. daily 0 14 * * *
weekly() Every Sunday at midnight 0 0 * * 0
monthly() 12:00 a.m. on the 1st of each month 0 0 1 * *
cron('...') Custom Cron Any

(3) Scheduling Constraints

Method Description
onOneServer() Run on only one server
withoutOverlapping() Overlapping execution is not allowed
runInBackground() Run in the background
when(Closure) Conditional Execution
environments('prod') Specified Environment
emailOutputOnFailure() Email notification in case of failure

(1) ▶ Example: Complete ShopMetrics Scheduling Configuration

PHP
// routes/console.php
Schedule::command('shopmetrics:analytics --type=daily')
    ->dailyAt('02:00')
    ->onOneServer()
    ->withoutOverlapping(60)
    ->emailOutputOnFailure('ops@shopmetrics.io');

Schedule::command('shopmetrics:cleanup --days=90')
    ->weeklyOn(Schedule::SUNDAY, '03:00')
    ->onOneServer();

Schedule::command('shopmetrics:send-expiring-notifications')
    ->dailyAt('08:00')
    ->when(fn () => Subscription::expiringSoon()->exists());

Schedule::command('queue:prune-failed --hours=168')
    ->daily();

Schedule::command('queue:prune-batches --hours=168')
    ->daily();

// Cron entry on server
// * * * * * cd /var/www/shopmetrics && php artisan schedule:run >> /dev/null 2>&1

Output:

TEXT
// Execution Successful

7. Interactive Commands

(1) Interaction Methods

PHP
// Ask for input
$name = $this->ask('What is the tenant name?');

// Ask with default
$email = $this->ask('Email address?', 'admin@example.com');

// Secret input (passwords)
$password = $this->secret('Enter password:');

// Confirm (yes/no)
if ($this->confirm('Do you wish to continue?', true)) {
    // Default: yes
}

// Choice (single select)
$type = $this->choice(
    'Select report type',
    ['daily', 'weekly', 'monthly'],
    0 // default index
);

// Anticipate (autocomplete)
$name = $this->anticipate('Tenant name', Tenant::pluck('name')->toArray());

(2) Comprehensive Interaction Workflow

PHP
// app/Console/Commands/SetupTenant.php
class SetupTenant extends Command
{
    protected $signature = 'shopmetrics:tenant-setup';

    protected $description = 'Interactive tenant setup wizard';

    public function handle(): int
    {
        $this->info('=== ShopMetrics Tenant Setup Wizard ===');

        $name = $this->ask('Tenant name');
        $slug = $this->anticipate('Slug', [Str::slug($name)]);
        $domain = $this->ask('Custom domain (optional)', $slug . '.shopmetrics.io');
        $plan = $this->choice('Select plan', ['Starter', 'Pro', 'Enterprise'], 1);
        $ownerEmail = $this->ask('Owner email');

        $this->table(
            ['Field', 'Value'],
            [['Name', $name], ['Slug', $slug], ['Domain', $domain], ['Plan', $plan], ['Owner', $ownerEmail]],
        );

        if (!$this->confirm('Create this tenant?', true)) {
            $this->warn('Cancelled.');
            return self::FAILURE;
        }

        $tenant = Tenant::create(compact('name', 'slug', 'domain'));
        User::factory()->create([
            'tenant_id' => $tenant->id,
            'email' => $ownerEmail,
            'role' => 'tenant_owner',
        ]);

        $this->info("Tenant {$name} created successfully!");
        return self::SUCCESS;
    }
}

(1) ▶ Example: ShopMetrics Interactive Data Export Command

PHP
// app/Console/Commands/ExportData.php
class ExportData extends Command
{
    protected $signature = 'shopmetrics:export';

    protected $description = 'Interactive data export tool';

    public function handle(): int
    {
        $type = $this->choice('What to export?', [
            'orders' => 'Orders',
            'products' => 'Products',
            'analytics' => 'Analytics Report',
        ]);

        $tenant = $this->anticipate('Tenant (leave blank for all)', Tenant::pluck('name')->push('All')->toArray());

        $format = $this->choice('Format?', ['csv', 'xlsx', 'json'], 0);

        $dateFrom = $this->ask('Date from (Y-m-d, optional)');
        $dateTo = $this->ask('Date to (Y-m-d, optional)');

        $this->info("Exporting {$type} for {$tenant} in {$format} format...");

        $query = match ($type) {
            'orders' => Order::query(),
            'products' => Product::query(),
            'analytics' => AnalyticsReport::query(),
        };

        if ($tenant !== 'All') {
            $tenantModel = Tenant::whereName($tenant)->firstOrFail();
            $query->where('tenant_id', $tenantModel->id);
        }

        if ($dateFrom) $query->where('created_at', '>=', $dateFrom);
        if ($dateTo) $query->where('created_at', '<=', $dateTo);

        $count = $query->count();
        $this->info("Found {$count} records.");

        if (!$this->confirm("Export {$count} records?", true)) {
            return self::FAILURE;
        }

        $path = ExportService::export($query, $format);
        $this->info("Export saved to: {$path}");

        return self::SUCCESS;
    }
}

Output:

TEXT
// Execution Successful

8. Comprehensive Example: ShopMetrics Operations Command Set

PHP
// ============================================
// Comprehensive: ShopMetrics Operations Commands
// Covers: signature, options, progress, scheduling, interactive
// ============================================

// app/Console/Commands/DailyMaintenance.php
class DailyMaintenance extends Command
{
    protected $signature = 'shopmetrics:daily-maintenance
                            {--skip-analytics : Skip analytics processing}
                            {--skip-cleanup : Skip data cleanup}
                            {--notify : Send completion notification}';

    protected $description = 'Run daily maintenance tasks';

    public function handle(): int
    {
        $this->info('Starting daily maintenance...');

        if (!$this->option('skip-analytics')) {
            $this->processAnalytics();
        }

        if (!$this->option('skip-cleanup')) {
            $this->cleanupExpiredData();
        }

        $this->syncSubscriptions();

        if ($this->option('notify')) {
            $this->sendCompletionNotification();
        }

        $this->info('Daily maintenance completed.');
        return self::SUCCESS;
    }

    private function processAnalytics(): void
    {
        $this->info('Processing daily analytics...');
        $tenants = Tenant::active()->get();
        $bar = $this->output->createProgressBar($tenants->count());

        foreach ($tenants as $tenant) {
            GenerateReportJob::dispatch($tenant, 'daily', 'json');
            $bar->advance();
        }

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

    private function cleanupExpiredData(): void
    {
        $this->call('shopmetrics:cleanup', ['--days' => 90]);
    }

    private function syncSubscriptions(): void
    {
        $this->call('shopmetrics:sync-subscriptions');
    }

    private function sendCompletionNotification(): void
    {
        $admin = User::where('role', 'super_admin')->first();
        $admin?->notify(new DailyMaintenanceCompleted());
    }
}

// Schedule it
// routes/console.php
Schedule::command('shopmetrics:daily-maintenance --notify')
    ->dailyAt('02:00')
    ->onOneServer()
    ->withoutOverlapping()
    ->emailOutputOnFailure('ops@shopmetrics.io');

❓ FAQ

Q What is the difference between make:command and make:command --command?
A --command=xxx Specifies a custom command name: php artisan make:command SendEmails --command=emails:send. If omitted, the default name app:console-commands:send-emails is used. It is recommended to always specify a custom command name.
Q Should I use Cron or Laravel Schedule for job scheduling?
A Use Laravel Schedule. The server only needs one Cron job: * * * * * php artisan schedule:run. All tasks are defined in the code, making them version-controlled and testable. Do not set up a separate Cron job for each task.
Q When should onOneServer be used?
A When deploying on multiple servers, to prevent the same scheduling task from running simultaneously on multiple machines. A Redis or database cache driver is required for coordination. It is not necessary on a single-server setup.
Q How do I test custom commands?
A Use $this->artisan('command:name', ['arg' => 'value']) to execute the command in a test, and assert that the output is ->expectsOutput('Done') or verify the database changes.
Q What should I do if commands are too slow?
A Process time-consuming operations asynchronously using a queue; commands are only responsible for dispatching jobs. For large datasets, use chunking with a progress bar. Add the --dry-run option to preview the results before execution.
Q How do I log command output?
A Use ->appendOutputTo(storage_path('logs/command.log')) to append logs in the scheduler; use Log::info() within the command; or use emailOutputOnFailure() to send an email notification in case of failure.

📖 Summary


📝 Exercises

  1. Basic Question (⭐): Create a command named shopmetrics:tenant-stats that accepts a tenant parameter and outputs the number of stores, orders, and total revenue for that tenant, formatted using the table command.

  2. Advanced Exercise (⭐⭐): Create the shopmetrics:daily-maintenance command, which includes analytics processing (with a progress bar) + cleanup of expired data + subscription synchronization, and configure the Schedule to run automatically at 2:00 a.m. every day.

  3. Challenge (⭐⭐⭐): Implement an interactive shopmetrics:tenant-setup wizard command—enter the tenant name, slug, plan, and administrator email address sequentially, with validation at each step, and finally confirm creation. Include auto-complete (anticipate) suggestions for the tenant name.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏