Stop Swallowing Exceptions in PHP

๐Ÿ“– 2 minutes read

You inherit a codebase. Every API call wrapped in try-catch. Every exception swallowed. Every error returns an empty collection.

And nobody knows when things break.

The Anti-Pattern

Here’s what I found in a legacy integration:

public function getProducts(): Collection
{
    try {
        $response = Http::get($this->apiUrl . '/products');
        return collect($response->json('data'));
    } catch (Exception $e) {
        // "Handle" the error by pretending it didn't happen
        return collect([]);
    }
}

What happens when the API is down? Nothing. The method returns empty. The caller thinks there are no products. The error disappears into the void.

Weeks later, you’re debugging why users see blank pages. The real error? A 500 from the API three weeks ago that nobody noticed because the logs were clean.

Let It Fail

The fix is brutal: delete the try-catch.

public function getProducts(): Collection
{
    $response = Http::get($this->apiUrl . '/products');
    return collect($response->json('data'));
}

Now when the API fails, the exception bubbles up. Your error tracking (Sentry, Bugsnag, whatever) catches it. You get alerted. You fix it.

But What About Graceful Degradation?

If you genuinely want to fail gracefully, make it explicit:

public function getProducts(): Collection
{
    try {
        $response = Http::timeout(5)->get($this->apiUrl . '/products');
        return collect($response->json('data'));
    } catch (RequestException $e) {
        // Log it so you know it happened
        Log::warning('Product API failed', [
            'error' => $e->getMessage(),
            'url' => $this->apiUrl
        ]);
        
        // Return cached/stale data as fallback
        return Cache::get('products.fallback', collect([]));
    }
}

Now you’re:

  • Logging the failure (visibility)
  • Using a fallback strategy (resilience)
  • Not silently lying to callers (honesty)

When to Catch

Catch exceptions when you have a recovery strategy:

  • Retry logic: API hiccup? Try again.
  • Fallback data: Cache, defaults, partial results.
  • User-facing context: Transform technical errors into user messages.

If you’re just catching to return empty, you’re hiding problems.

Laravel’s Global Exception Handler

Laravel already has a system for this. Exceptions bubble to App\Exceptions\Handler, where you can:

// app/Exceptions/Handler.php
public function register()
{
    $this->reportable(function (RequestException $e) {
        // Log to Sentry/Slack/etc
    });
    
    $this->renderable(function (RequestException $e) {
        return response()->json([
            'error' => 'External service unavailable'
        ], 503);
    });
}

Now all API failures follow the same pattern. No more scattered try-catch blocks pretending everything’s fine.

The Takeaway

Empty catch blocks are lies. If you can’t handle the error meaningfully, let it bubble. Your future self โ€” the one debugging at 2 AM โ€” will thank you.

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 *