Anvil
Anvil - The mobile companion for Laravel Forge. Available now. Download for iOS

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:

  1. Execution stops immediately
  2. Error is logged to the patches table
  3. Exception is re-thrown
  4. No subsequent patches run

Continue on Error

You can configure patches to continue running even when one fails:

1// config/laravel-patches.php
2return [
3 'stop_on_error' => false, // Continue running patches after failures
4];

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 ones
2php artisan patch:status
3 
4# Filter only failed patches
5php 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 rethrow
17 throw $e;
18 }
19 }
20 
21 public function down()
22 {
23 // Rollback logic
24 }
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 service
11 Bugsnag::notifyException($event->exception, [
12 'patch' => $event->patch,
13 'batch' => $event->batch,
14 ]);
15 
16 // Create rollback plan
17 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 records
3$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 migration
8}

4. Use Transactions (see Transactions documentation)

1protected bool $useTransaction = true;

5. Test Error Scenarios

1// In your tests
2test('handles duplicate email error', function () {
3 User::factory()->create(['email' => '[email protected]']);
4 
5 $this->expectException(\Exception::class);
6 
7 Artisan::call('patch');
8});

Debugging Failed Patches

1. Check Logs

1# Laravel logs
2tail -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 patch
10 Artisan::call('patch');
11}

Recovery Strategies

After a Failed Patch

  1. Review the error in patch:status or database
  2. Understand the cause from error message and trace
  3. Fix the data or code that caused the failure
  4. Rollback if needed with patch:rollback
  5. Fix the patch file if it contains bugs
  6. 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 checkpoints
12}

Production Safeguards

1. Always Test First

1# Test in staging
2php artisan patch --dry-run
3php artisan patch

2. Backup Before Running

1php artisan snapshot:create
2php artisan patch

3. Monitor Execution

1# Run with verbose output
2php 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}