🎉 Enjoying this package? Consider sponsoring me on GitHub or buying me a beer.
Error Handling
Laravel Patches provides comprehensive error handling to ensure patches fail gracefully and provide detailed information for troubleshooting.
Error Behavior
Default Behavior
By default, if a patch throws an exception:
- Execution stops immediately
- Error is logged to the patches table
- Exception is re-thrown
- No subsequent patches run
Continue on Error
You can configure patches to continue running even when one fails:
1// config/laravel-patches.php2return [3 'stop_on_error' => false, // Continue running patches after failures4];
Or use environment variable:
1PATCHES_STOP_ON_ERROR=false
Error Logging
Automatic Error Capture
When a patch fails, the following information is automatically captured:
- Error message
- Stack trace
- Execution time before failure
- Memory usage
- User who ran the patch
- Environment (production, staging, etc.)
Database Storage
Failed patches are stored in the patches table with:
1[2 'status' => 'failed',3 'error_message' => 'Exception message',4 'error_trace' => 'Full stack trace',5 'execution_time_ms' => 1234,6]
Viewing Failed Patches
1# See all patches including failed ones2php artisan patch:status3 4# Filter only failed patches5php artisan patch:list --status=failed
Configuration
config/laravel-patches.php
1return [ 2 /** 3 * Stop execution on first error 4 */ 5 'stop_on_error' => env('PATCHES_STOP_ON_ERROR', true), 6 7 /** 8 * Log errors to patches table 9 */10 'log_errors' => env('PATCHES_LOG_ERRORS', true),11];
Handling Errors in Patches
Try-Catch Blocks
Handle specific errors within your patch:
1use Rappasoft\LaravelPatches\Patch; 2 3class MyPatch extends Patch 4{ 5 public function up() 6 { 7 try { 8 // Risky operation 9 User::where('email', 'LIKE', '%old-domain.com')10 ->update(['email' => DB::raw("REPLACE(email, 'old-domain.com', 'new-domain.com')")]);11 12 $this->log('Updated user emails');13 } catch (\Exception $e) {14 $this->log('Error updating emails: ' . $e->getMessage());15 16 // Optionally rethrow17 throw $e;18 }19 }20 21 public function down()22 {23 // Rollback logic24 }25}
Validation Before Execution
Validate data before making changes:
1public function up() 2{ 3 // Check if operation is safe 4 if (User::whereNull('email')->exists()) { 5 throw new \Exception('Cannot proceed: Users with null emails exist'); 6 } 7 8 // Proceed with patch 9 // ...10}
Progressive Error Handling
1public function up() 2{ 3 $errors = []; 4 $processed = 0; 5 6 User::chunk(100, function ($users) use (&$errors, &$processed) { 7 foreach ($users as $user) { 8 try { 9 $user->update(['status' => 'active']);10 $processed++;11 } catch (\Exception $e) {12 $errors[] = "User {$user->id}: {$e->getMessage()}";13 }14 }15 });16 17 $this->log("Processed: {$processed} users");18 19 if (count($errors)) {20 $this->log("Errors: " . implode(', ', $errors));21 throw new \Exception(count($errors) . ' users failed to update');22 }23}
Event-Based Error Handling
Subscribe to the PatchFailed event for custom error handling:
1use Rappasoft\LaravelPatches\Events\PatchFailed; 2 3Event::listen(PatchFailed::class, function (PatchFailed $event) { 4 // Send alert 5 Notification::send( 6 User::admins()->get(), 7 new PatchFailedNotification($event->patch, $event->exception) 8 ); 9 10 // Log to external service11 Bugsnag::notifyException($event->exception, [12 'patch' => $event->patch,13 'batch' => $event->batch,14 ]);15 16 // Create rollback plan17 DB::table('patch_failures')->insert([18 'patch' => $event->patch,19 'error' => $event->exception->getMessage(),20 'needs_manual_intervention' => true,21 'created_at' => now(),22 ]);23});
Best Practices
1. Use Descriptive Error Messages
1throw new \Exception("Failed to update user #{$user->id}: email validation failed");
2. Log Progress
1$this->log("Processing 1000 records...");2// Process records3$this->log("Completed successfully");
3. Validate Preconditions
1public function up()2{3 if (! Schema::hasColumn('users', 'old_field')) {4 throw new \Exception('old_field column does not exist');5 }6 7 // Proceed with migration8}
4. Use Transactions (see Transactions documentation)
1protected bool $useTransaction = true;
5. Test Error Scenarios
1// In your tests2test('handles duplicate email error', function () {4 5 $this->expectException(\Exception::class);6 7 Artisan::call('patch');8});
Debugging Failed Patches
1. Check Logs
1# Laravel logs2tail -f storage/logs/laravel.log
2. Query Failed Patches
1use Rappasoft\LaravelPatches\Models\Patch;2 3$failed = Patch::where('status', 'failed')->get();4 5foreach ($failed as $patch) {6 dump($patch->patch);7 dump($patch->error_message);8 dump($patch->error_trace);9}
3. Re-run Individual Patch
Create a temporary command to re-run a specific patch:
1// app/Console/Commands/RerunPatch.php 2public function handle() 3{ 4 $patchName = $this->argument('patch'); 5 6 // Delete old failed attempt 7 Patch::where('patch', $patchName)->delete(); 8 9 // Re-run the patch10 Artisan::call('patch');11}
Recovery Strategies
After a Failed Patch
- Review the error in
patch:statusor database - Understand the cause from error message and trace
- Fix the data or code that caused the failure
- Rollback if needed with
patch:rollback - Fix the patch file if it contains bugs
- Re-run patches with
php artisan patch
Manual Intervention
If a patch partially completed before failing:
1public function up() 2{ 3 // Check what was already done 4 if (DB::table('temp_migration_state')->exists()) { 5 $this->log('Resuming from previous failed attempt'); 6 // Continue from checkpoint 7 } else { 8 // Start fresh 9 }10 11 // Your patch logic with checkpoints12}
Production Safeguards
1. Always Test First
1# Test in staging2php artisan patch --dry-run3php artisan patch
2. Backup Before Running
1php artisan snapshot:create2php artisan patch
3. Monitor Execution
1# Run with verbose output2php artisan patch -v
4. Have Rollback Plan
Ensure your down() methods can properly rollback:
1public function down()2{3 // Reverse the changes from up()4}