Per-Step Try/Catch: Don’t Let One Bad Record Kill Your Entire Batch

πŸ“– 2 minutes read

Last week I had an Artisan command that processed about 2,000 records. The first version used a transaction wrapper — if any single record failed, the whole batch rolled back. Clean, right?

Except when record #1,847 hit an edge case, all 1,846 successful records got nuked. That’s not clean. That’s a landmine.

The Fix: Per-Step Try/Catch

Instead of wrapping the entire loop in one big try/catch, wrap each iteration individually:

$records->each(function ($record) {
    try {
        $this->processRecord($record);
        $this->info("βœ… Processed #{$record->id}");
    } catch (\Throwable $e) {
        $this->error("❌ Failed #{$record->id}: {$e->getMessage()}");
        Log::error("Batch process failed", [
            'record_id' => $record->id,
            'error' => $e->getMessage(),
        ]);
    }
});

Why This Matters

The all-or-nothing approach feels safer because it’s “atomic.” But for batch operations where each record is independent, it’s actually worse. One bad record shouldn’t hold 1,999 good ones hostage.

The status symbols (βœ…/❌) aren’t just cute either. When you’re watching a command chug through thousands of records, that visual feedback tells you instantly if something’s going sideways without reading log files.

When to Use Which

Use transactions (all-or-nothing) when records depend on each other. Think: transferring money between accounts, or creating a parent record with its children.

Use per-step try/catch when each record is independent. Think: sending notification emails, syncing external data, or migrating legacy records.

The pattern is simple but I’ve seen teams default to transactions for everything. Sometimes the safest thing is to let the failures fail and keep the successes.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *