Skip to main content

Console Commands

Artisan commands allow your addon to perform CLI operations, background processing, and scheduled tasks.

Creating Commands

Commands are stored in the Console/ directory.

Basic Command

// Console/ValidateEmails.php
<?php

namespace Addons\EmailValidator\Console;

use Illuminate\Console\Command;
use Addons\EmailValidator\Entities\ValidationResult;

class ValidateEmails extends Command
{
/**
* Command signature with arguments and options
*/
protected $signature = 'emailvalidator:validate
{emails* : Email addresses to validate}
{--user= : User ID to associate results with}
{--batch= : Batch name for grouping}';

/**
* Command description
*/
protected $description = 'Validate one or more email addresses';

/**
* Execute the command
*/
public function handle(): int
{
$emails = $this->argument('emails');
$userId = $this->option('user') ?? 1;
$batchName = $this->option('batch');

$this->info('Validating ' . count($emails) . ' email(s)...');

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

$results = [];
foreach ($emails as $email) {
$result = $this->validateEmail($email, $userId);
$results[] = $result;
$bar->advance();
}

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

// Display results table
$this->table(
['Email', 'Status', 'Score'],
collect($results)->map(fn($r) => [$r->email, $r->status, $r->score])
);

$valid = collect($results)->where('status', 'valid')->count();
$this->info("Valid: {$valid}/" . count($results));

return Command::SUCCESS;
}

private function validateEmail(string $email, int $userId): ValidationResult
{
// Validation logic
$isValid = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;

return ValidationResult::create([
'user_id' => $userId,
'email' => $email,
'status' => $isValid ? 'valid' : 'invalid',
'score' => $isValid ? 100 : 0,
]);
}
}

Command with Interactive Prompts

// Console/ConfigureApi.php
<?php

namespace Addons\EmailValidator\Console;

use Illuminate\Console\Command;

class ConfigureApi extends Command
{
protected $signature = 'emailvalidator:configure';
protected $description = 'Configure API settings interactively';

public function handle(): int
{
$this->info('Email Validator Configuration');
$this->line('-----------------------------');

// Text input
$apiKey = $this->ask('Enter your API key');

// Secret input (hidden)
$apiSecret = $this->secret('Enter your API secret');

// Choice selection
$environment = $this->choice(
'Select environment',
['production', 'staging', 'development'],
0 // default index
);

// Confirmation
if (!$this->confirm('Save these settings?', true)) {
$this->warn('Configuration cancelled.');
return Command::SUCCESS;
}

// Save settings
$this->saveSettings([
'api_key' => $apiKey,
'api_secret' => $apiSecret,
'environment' => $environment,
]);

$this->info('Configuration saved successfully!');

return Command::SUCCESS;
}

private function saveSettings(array $settings): void
{
// Save to database or config file
}
}

Command for Data Processing

// Console/ProcessPendingValidations.php
<?php

namespace Addons\EmailValidator\Console;

use Illuminate\Console\Command;
use Addons\EmailValidator\Entities\ValidationResult;
use Addons\EmailValidator\Services\ValidatorService;

class ProcessPendingValidations extends Command
{
protected $signature = 'emailvalidator:process-pending
{--limit=100 : Maximum records to process}
{--dry-run : Show what would be processed}';

protected $description = 'Process pending email validations';

public function __construct(
private ValidatorService $validator
) {
parent::__construct();
}

public function handle(): int
{
$limit = (int) $this->option('limit');
$dryRun = $this->option('dry-run');

$pending = ValidationResult::where('status', 'pending')
->orderBy('created_at')
->limit($limit)
->get();

if ($pending->isEmpty()) {
$this->info('No pending validations found.');
return Command::SUCCESS;
}

$this->info("Found {$pending->count()} pending validations.");

if ($dryRun) {
$this->warn('Dry run mode - no changes will be made.');
$this->table(
['ID', 'Email', 'Created At'],
$pending->map(fn($r) => [$r->id, $r->email, $r->created_at])
);
return Command::SUCCESS;
}

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

foreach ($pending as $record) {
try {
$result = $this->validator->validate($record->email);

$record->update([
'status' => $result['valid'] ? 'valid' : 'invalid',
'score' => $result['score'],
'raw_response' => $result,
]);
} catch (\Exception $e) {
$this->error("Failed: {$record->email} - {$e->getMessage()}");
}

$bar->advance();
}

$bar->finish();
$this->newLine();
$this->info('Processing complete!');

return Command::SUCCESS;
}
}

Cleanup Command

// Console/CleanupResults.php
<?php

namespace Addons\EmailValidator\Console;

use Illuminate\Console\Command;
use Addons\EmailValidator\Entities\ValidationResult;

class CleanupResults extends Command
{
protected $signature = 'emailvalidator:cleanup
{--days=30 : Delete results older than X days}
{--status= : Only delete results with this status}
{--force : Skip confirmation}';

protected $description = 'Clean up old validation results';

public function handle(): int
{
$days = (int) $this->option('days');
$status = $this->option('status');
$force = $this->option('force');

$query = ValidationResult::where('created_at', '<', now()->subDays($days));

if ($status) {
$query->where('status', $status);
}

$count = $query->count();

if ($count === 0) {
$this->info('No records to delete.');
return Command::SUCCESS;
}

$this->warn("Found {$count} records older than {$days} days.");

if (!$force && !$this->confirm('Delete these records?')) {
$this->info('Cleanup cancelled.');
return Command::SUCCESS;
}

$deleted = $query->delete();

$this->info("Deleted {$deleted} records.");

return Command::SUCCESS;
}
}

Registering Commands

Register commands in your service provider:

// Providers/EmailValidatorServiceProvider.php
protected function registerCommands(): void
{
if ($this->app->runningInConsole()) {
$this->commands([
\Addons\EmailValidator\Console\ValidateEmails::class,
\Addons\EmailValidator\Console\ProcessPendingValidations::class,
\Addons\EmailValidator\Console\CleanupResults::class,
\Addons\EmailValidator\Console\ConfigureApi::class,
\Addons\EmailValidator\Console\SyncDisposableDomains::class,
]);
}
}

Running Commands

# Basic usage
php artisan emailvalidator:validate email@example.com

# With arguments
php artisan emailvalidator:validate email1@test.com email2@test.com --user=5

# With options
php artisan emailvalidator:cleanup --days=60 --status=invalid

# Dry run
php artisan emailvalidator:process-pending --dry-run

# Force (skip confirmation)
php artisan emailvalidator:cleanup --force

Scheduling Commands

Schedule commands in your service provider:

// Providers/EmailValidatorServiceProvider.php
use Illuminate\Console\Scheduling\Schedule;

protected function registerSchedule(): void
{
$this->callAfterResolving(Schedule::class, function (Schedule $schedule) {

// Run every 5 minutes
$schedule->command('emailvalidator:process-pending --limit=50')
->everyFiveMinutes()
->withoutOverlapping()
->runInBackground();

// Run daily at 3 AM
$schedule->command('emailvalidator:cleanup --days=30 --force')
->dailyAt('03:00')
->onOneServer();

// Run weekly on Sunday
$schedule->command('emailvalidator:sync-disposable')
->weekly()
->sundays()
->at('04:00');

// Run hourly during business hours
$schedule->command('emailvalidator:stats-report')
->hourly()
->between('9:00', '17:00')
->weekdays();

// Dynamic scheduling from database
$this->scheduleDynamicTasks($schedule);
});
}

private function scheduleDynamicTasks(Schedule $schedule): void
{
// Get schedule from database
$cronSettings = \DB::table('addon_settings')
->where('addon', 'emailvalidator')
->where('key', 'cron_schedule')
->first();

if ($cronSettings) {
$settings = json_decode($cronSettings->value, true);

if ($settings['cleanup_enabled'] ?? false) {
$schedule->command('emailvalidator:cleanup --force')
->cron($settings['cleanup_cron'] ?? '0 3 * * *');
}
}
}

Schedule Frequency Options

// Time-based
$schedule->command('...')->everyMinute();
$schedule->command('...')->everyFiveMinutes();
$schedule->command('...')->everyTenMinutes();
$schedule->command('...')->everyFifteenMinutes();
$schedule->command('...')->everyThirtyMinutes();
$schedule->command('...')->hourly();
$schedule->command('...')->hourlyAt(15); // At :15 past the hour
$schedule->command('...')->daily();
$schedule->command('...')->dailyAt('13:00');
$schedule->command('...')->weekly();
$schedule->command('...')->monthly();
$schedule->command('...')->quarterly();
$schedule->command('...')->yearly();

// Day-based
$schedule->command('...')->weekdays();
$schedule->command('...')->weekends();
$schedule->command('...')->sundays();
$schedule->command('...')->mondays();
// ... through saturdays()

// Custom cron
$schedule->command('...')->cron('0 */2 * * *'); // Every 2 hours

// Constraints
$schedule->command('...')->between('8:00', '17:00');
$schedule->command('...')->unlessBetween('23:00', '4:00');
$schedule->command('...')->when(fn() => config('app.env') === 'production');
$schedule->command('...')->skip(fn() => $this->isMaintenanceMode());

// Execution options
$schedule->command('...')->withoutOverlapping();
$schedule->command('...')->onOneServer();
$schedule->command('...')->runInBackground();
$schedule->command('...')->evenInMaintenanceMode();

Handling Output

// Log output
$schedule->command('emailvalidator:process-pending')
->daily()
->appendOutputTo(storage_path('logs/emailvalidator-schedule.log'));

// Send output via email
$schedule->command('emailvalidator:stats-report')
->weekly()
->emailOutputTo('admin@example.com');

// Callbacks
$schedule->command('emailvalidator:process-pending')
->daily()
->before(function () {
\Log::info('Starting scheduled validation processing');
})
->after(function () {
\Log::info('Completed scheduled validation processing');
})
->onSuccess(function () {
// Notify success
})
->onFailure(function () {
// Notify failure
});

Command Output

Styling Output

public function handle(): int
{
// Basic output
$this->line('Regular text');
$this->info('Information message'); // Green
$this->comment('Comment text'); // Yellow
$this->question('Question text'); // Cyan background
$this->error('Error message'); // Red background
$this->warn('Warning message'); // Yellow

// With verbosity levels
$this->line('Always shown');
$this->line('Verbose', null, 'v'); // -v
$this->line('Very verbose', null, 'vv'); // -vv
$this->line('Debug', null, 'vvv'); // -vvv

// New lines
$this->newLine();
$this->newLine(2);

return Command::SUCCESS;
}

Tables

$this->table(
['Name', 'Email', 'Status'],
[
['John Doe', 'john@example.com', 'valid'],
['Jane Doe', 'jane@example.com', 'invalid'],
]
);

// From collection
$this->table(
['ID', 'Email', 'Score'],
$results->map(fn($r) => [$r->id, $r->email, $r->score])->toArray()
);

Progress Bars

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

foreach ($items as $item) {
$this->processItem($item);
$bar->advance();
}

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

// With custom format
$bar = $this->output->createProgressBar(count($items));
$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');

Return Codes

public function handle(): int
{
try {
// Process...

if ($success) {
return Command::SUCCESS; // 0
}

return Command::FAILURE; // 1

} catch (\Exception $e) {
$this->error($e->getMessage());
return Command::INVALID; // 2
}
}

Calling Commands Programmatically

use Illuminate\Support\Facades\Artisan;

// Call command
Artisan::call('emailvalidator:validate', [
'emails' => ['test@example.com'],
'--user' => 5,
]);

// Get output
$output = Artisan::output();

// Queue command
Artisan::queue('emailvalidator:process-pending', [
'--limit' => 100,
]);

// From controller
public function runValidation()
{
Artisan::call('emailvalidator:process-pending');

return redirect()->back()->with('success', 'Validation started');
}

Best Practices

  1. Use descriptive signatures - Make command names clear and prefixed with addon name
  2. Add confirmation for destructive actions - Use --force flag to skip
  3. Implement --dry-run - Show what would happen without making changes
  4. Show progress - Use progress bars for long operations
  5. Handle errors gracefully - Catch exceptions and provide helpful messages
  6. Use appropriate return codes - SUCCESS, FAILURE, INVALID
  7. Log important operations - Especially for scheduled tasks
  8. Prevent overlapping - Use withoutOverlapping() for scheduled tasks