Laravel Tutorials

Preventing Command Conflicts with Laravel's Isolatable Interface

Published
Preventing Command Conflicts with Laravel's Isolatable Interface image

Laravel's Isolatable interface ensures exclusive command execution, preventing multiple instances from running simultaneously and protecting against resource conflicts or data corruption during critical operations.

The Isolatable interface automatically manages command locking, making it ideal for long-running processes, data synchronization, or system maintenance tasks:

<?php
 
namespace App\Console\Commands;
 
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Isolatable;
 
class SyncInventoryCommand extends Command implements Isolatable
{
protected $signature = 'inventory:sync';
protected $description = 'Synchronize inventory with external systems';
 
public function handle()
{
$this->info('Synchronizing inventory data...');
}
}

Laravel automatically adds the --isolated option when you implement this interface, enabling exclusive execution mode.

php artisan inventory:sync --isolated

Here's a comprehensive backup system that demonstrates isolation with progress tracking and error handling:

<?php
 
namespace App\Console\Commands;
 
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Isolatable;
use App\Services\DatabaseBackupService;
use App\Services\FileBackupService;
use App\Services\NotificationService;
use Exception;
 
class SystemBackupCommand extends Command implements Isolatable
{
protected $signature = 'backup:system
{--type=full : Backup type (full, incremental, database)}
{--compress : Compress backup files}
{--verify : Verify backup integrity}';
 
protected $description = 'Create comprehensive system backup';
 
protected $databaseService;
protected $fileService;
protected $notificationService;
 
public function __construct(
DatabaseBackupService $databaseService,
FileBackupService $fileService,
NotificationService $notificationService
) {
parent::__construct();
$this->databaseService = $databaseService;
$this->fileService = $fileService;
$this->notificationService = $notificationService;
}
 
public function handle()
{
$startTime = now();
$backupType = $this->option('type');
$shouldCompress = $this->option('compress');
$shouldVerify = $this->option('verify');
 
$this->info("Starting {$backupType} backup at {$startTime->format('Y-m-d H:i:s')}");
 
try {
$backupResults = $this->performBackup($backupType, $shouldCompress);
 
if ($shouldVerify) {
$this->info('Verifying backup integrity...');
$this->verifyBackupIntegrity($backupResults);
}
 
$duration = now()->diffInMinutes($startTime);
$this->info("Backup completed successfully in {$duration} minutes");
 
$this->notificationService->sendBackupSuccess($backupResults, $duration);
 
return Command::SUCCESS;
 
} catch (Exception $e) {
$this->error("Backup failed: {$e->getMessage()}");
$this->notificationService->sendBackupFailure($e);
 
return Command::FAILURE;
}
}
 
private function performBackup($type, $compress)
{
$results = [];
 
if (in_array($type, ['full', 'database'])) {
$this->info('Backing up databases...');
$results['database'] = $this->databaseService->createBackup([
'compress' => $compress,
'include_structure' => true,
'include_data' => true
]);
$this->info('Database backup completed');
}
 
if (in_array($type, ['full', 'incremental'])) {
$this->info('Backing up application files...');
 
$fileOptions = [
'compress' => $compress,
'incremental' => $type === 'incremental',
'exclude_patterns' => [
'storage/logs/*',
'storage/cache/*',
'node_modules/*',
'.git/*'
]
];
 
$results['files'] = $this->fileService->createBackup($fileOptions);
$this->info('File backup completed');
}
 
$this->info('Cleaning up old backups...');
$this->cleanupOldBackups();
 
return $results;
}
 
private function verifyBackupIntegrity($backupResults)
{
foreach ($backupResults as $backupType => $result) {
$this->line("Verifying {$backupType} backup...");
 
$isValid = match($backupType) {
'database' => $this->databaseService->verifyBackup($result['path']),
'files' => $this->fileService->verifyBackup($result['path']),
default => false
};
 
if ($isValid) {
$this->info("✓ {$backupType} backup verification passed");
} else {
throw new Exception("Backup verification failed for {$backupType}");
}
}
}
 
private function cleanupOldBackups()
{
$retentionDays = config('backup.retention_days', 30);
$cutoffDate = now()->subDays($retentionDays);
 
$deletedCount = $this->fileService->cleanupOldBackups($cutoffDate);
$this->info("Cleaned up {$deletedCount} old backup files");
}
}
 
class ReportGenerationCommand extends Command implements Isolatable
{
protected $signature = 'reports:generate
{type : Report type to generate}
{--period=monthly : Time period for the report}
{--format=pdf : Output format (pdf, excel, csv)}
{--email= : Email address to send the report}';
 
protected $description = 'Generate comprehensive business reports';
 
public function handle()
{
$reportType = $this->argument('type');
$period = $this->option('period');
$format = $this->option('format');
$emailRecipient = $this->option('email');
 
$this->info("Generating {$reportType} report for {$period} period...");
 
$reportData = $this->gatherReportData($reportType, $period);
$reportPath = $this->generateReport($reportData, $format);
 
$this->info("Report generated: {$reportPath}");
 
if ($emailRecipient) {
$this->emailReport($reportPath, $emailRecipient);
$this->info("Report emailed to {$emailRecipient}");
}
 
return Command::SUCCESS;
}
 
private function gatherReportData($type, $period)
{
$this->info('Collecting report data...');
 
return match($type) {
'sales' => $this->getSalesData($period),
'user-activity' => $this->getUserActivityData($period),
'financial' => $this->getFinancialData($period),
default => throw new Exception("Unknown report type: {$type}")
};
}
 
private function generateReport($data, $format)
{
$this->info("Formatting report as {$format}...");
 
$reportGenerator = app("App\\Services\\ReportGenerators\\{$format}Generator");
return $reportGenerator->generate($data);
}
 
private function emailReport($reportPath, $recipient)
{
$this->info('Sending report via email...');
 
Mail::to($recipient)->send(new ReportGeneratedMail($reportPath));
}
}

Custom exit codes provide specific feedback when isolation prevents execution:

php artisan backup:system --isolated=42

The Isolatable interface ensures command safety in production environments, preventing resource conflicts and maintaining data integrity during critical operations.

Harris Raftopoulos photo

Senior Software Engineer • Staff & Educator @ Laravel News • Co-organizer @ Laravel Greece Meetup

Sponsored

serpapi logo
SerpApi

The Web Search API for Your LLM and AI Applications

Visit SerpApi

The latest

View all →
Running Laravel's Scheduler on Multiple Servers image

Running Laravel's Scheduler on Multiple Servers

Read article
Laravel Fake Assertions Now Accept Property Arrays image

Laravel Fake Assertions Now Accept Property Arrays

Read article
Route::query() and Model defaults() in Laravel 13.35 image

Route::query() and Model defaults() in Laravel 13.35

Read article
Laravel AI SDK 1.1 Adds Agent Skills and Cohere Chat image

Laravel AI SDK 1.1 Adds Agent Skills and Cohere Chat

Read article
VMPal: Give Your AI Agent a Whole Computer image

VMPal: Give Your AI Agent a Whole Computer

Read article
Synapse: A Dev Dashboard for Laravel AI SDK Agents image

Synapse: A Dev Dashboard for Laravel AI SDK Agents

Read article