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

acquaintsoft logo
Acquaint Softtech

Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Visit Acquaint Softtech

The latest

View all →
CPX: The Composer Package Executor for PHP image

CPX: The Composer Package Executor for PHP

Read article
Laravel AI SDK Adds Human-in-the-Loop Tool Approval image

Laravel AI SDK Adds Human-in-the-Loop Tool Approval

Read article
Pest 5 Released With Test Impact Analysis, Agent Verification, and Evals image

Pest 5 Released With Test Impact Analysis, Agent Verification, and Evals

Read article
Queue-SQL: Run Mass Deletes and Updates Across Parallel Queue Jobs image

Queue-SQL: Run Mass Deletes and Updates Across Parallel Queue Jobs

Read article
Blade Formatting in Laravel Pint image

Blade Formatting in Laravel Pint

Read article
Inertia DevTools Is Now on the Chrome Web Store image

Inertia DevTools Is Now on the Chrome Web Store

Read article