The Laravel exception handler — App\Exceptions\Handler deep dive
Concept
Laravel's App\Exceptions\Handler is the central exception processing hub. Every exception that isn't caught in a try/catch bubbles up to this class, which decides whether to log it and how to render it as an HTTP response.
Two key methods:
report(\Throwable $e): Called for every uncaught exception. Responsible for logging/alerting. Callsparent::report()which invokes registered logger integrations (Sentry, Bugsnag, Flare via the\Illuminate\Log\Logger).render($request, \Throwable $e): Called to convert the exception to an HTTP response. Returns a\Symfony\Component\HttpFoundation\Response.
$dontReport: Array of exception classes that should NOT be logged (e.g., ValidationException, AuthenticationException). These are expected errors that don't indicate bugs.
$dontFlash: Array of form input keys that should not be flashed to the session (e.g., password, password_confirmation) for security.
reportable() / renderable(): Laravel 8+ added fluent registration of per-exception callbacks in register():
$this->reportable(fn(PaymentException $e) => Slack::alert($e->getMessage()));
$this->renderable(fn(UserNotFoundException $e, $request) => response()->json(['error' => 'User not found'], 404));$withoutExceptionHandling in tests: $this->withoutExceptionHandling() in a test throws exceptions directly instead of routing through the handler — useful when you want to see raw exceptions in test output rather than HTTP responses.
Code Example
<?php
// app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* Exceptions NOT logged (expected business errors)
*/
protected $dontReport = [
\App\Exceptions\UserNotFoundException::class,
\App\Exceptions\OrderAlreadyShippedException::class,
];
/**
* Input fields NOT flashed to session on validation error
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
public function register(): void
{
// Per-exception reporting
$this->reportable(function (\App\Exceptions\PaymentException $e): void {
// Send to Slack or PagerDuty for payment failures
SlackNotifier::critical("Payment failure: " . $e->getMessage());
});
// Per-exception rendering
$this->renderable(function (\App\Exceptions\UserNotFoundException $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['message' => $e->getMessage()], 404);
}
return response()->view('errors.user-not-found', [], 404);
});
// Global fallback for API requests
$this->renderable(function (\Throwable $e, Request $request) {
if ($request->expectsJson()) {
$statusCode = method_exists($e, 'getStatusCode')
? $e->getStatusCode()
: 500;
return response()->json([
'message' => app()->environment('production')
? 'Server Error'
: $e->getMessage(),
], $statusCode);
}
});
}
}