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
- Use descriptive signatures - Make command names clear and prefixed with addon name
- Add confirmation for destructive actions - Use
--forceflag to skip - Implement
--dry-run- Show what would happen without making changes - Show progress - Use progress bars for long operations
- Handle errors gracefully - Catch exceptions and provide helpful messages
- Use appropriate return codes - SUCCESS, FAILURE, INVALID
- Log important operations - Especially for scheduled tasks
- Prevent overlapping - Use
withoutOverlapping()for scheduled tasks