In Laravel, what does the report() helper do, and when would you call it instead of letting an exception propagate?
answer
- record it, keep going
- returns void, renders nothing
- a string is wrapped in Exception
- report_if, report_unless, rescue()
- dontReportDuplicates tracks object identity
basics
~20 sreport($e) passes a Throwable to Laravel's exception handler, which applies its filters, throttling, callbacks and default logging, then returns so your code carries on. Call it in a catch block when a failure must be recorded without failing the request.
solid answer
~40 s`report()` is a global helper that resolves the `ExceptionHandler` from the container and calls its `report()` method. The exception goes through the same pipeline as an uncaught one, `dontReport`, `throttle()`, report callbacks and the default log, but nothing is rendered and the helper returns `void`, so the request or job continues. Passing a string wraps it in a plain `Exception`. Use it for best-effort side work: the booking is saved, the confirmation SMS fails, so you catch, `report($e)` and return a fallback instead of showing an error page. `report_if()` and `report_unless()` add a condition, and `rescue(fn () => ..., $fallback)` packages try, report and return-a-default in one call. If an instance can be reported twice, for example reported in a catch and then rethrown, `$exceptions->dontReportDuplicates()` makes Laravel report each exception object once.
code
php · 27 lines<?php
namespace App\Actions;
use App\Models\Booking;
use App\Services\SmsGateway;
use Throwable;
class ConfirmBooking
{
public function __construct(private SmsGateway $sms) {}
public function __invoke(Booking $booking): bool
{
$booking->update(['status' => 'confirmed']);
try {
$this->sms->send($booking->phone, 'Your booking is confirmed.');
} catch (Throwable $e) {
report($e); // recorded; the booking stays confirmed
return false;
}
return true;
}
}go deeper
Remember report($e) logs through the handler and returns nothing, so code after it keeps running.
Explain that report() uses the full handler pipeline, and when report_if, report_unless or rescue() fit better than a try/catch.
Decide which failures should fail the request and which should be reported and survived, and switch on dontReportDuplicates when code reports then rethrows.
Set a team rule for swallowed exceptions: every catch that continues must report, so best-effort work never fails silently.
## What report() does Laravel's **exception handler** (`Illuminate\Foundation\Exceptions\Handler`, bound to the `Illuminate\Contracts\Debug\ExceptionHandler` contract) has two jobs: **reporting** a failure (logging it or forwarding it) and **rendering** it into a response. The framework calls both for an exception nobody caught. The global **`report()` helper** lets your own code trigger only the first half: ```php function report($exception): void ``` - It accepts a `Throwable` or a string; a string becomes `new Exception($message)`. - It resolves the handler from the container and calls `report()` on it. - It returns `void`. It does not rethrow, render a page or stop the request. Because it goes through the real handler, everything configured in `bootstrap/app.php` still applies: the `dontReport` list, `ShouldntReport`, `dontReportWhen`, `throttle()`, the exception's own `report()` and `context()` methods, the registered report callbacks and `level()`. `report()` is not a way to force an entry past those filters. ## When to report and continue, and when to let it propagate The choice is about whether the failure should fail the operation. | Situation | Better choice | |---|---| | Core work failed: the order could not be saved | Let it propagate; the handler reports it and renders an error | | Side work failed: the confirmation SMS could not be sent | Catch, `report($e)`, continue with a fallback | | A cache warm-up or analytics ping failed | `rescue()` with a default value | | The failure is expected and meaningless to operators | Handle it without reporting, or mark the class `ShouldntReport` | The danger in the second row is swallowing an exception silently. A bare `catch (Throwable $e) { return false; }` hides the failure from everyone; adding `report($e)` keeps the operator informed while the user gets a working page. ## Shortcuts: report_if, report_unless and rescue Three helpers wrap common patterns: - `report_if($condition, $exception)` reports only when the condition is true. - `report_unless($condition, $exception)` reports only when it is false. - `rescue(callable $callback, $rescue = null, $report = true)` runs the callback, and if it throws, reports the exception and returns the `$rescue` value. `$rescue` may be a closure that receives the exception, and `$report` may be `false` or a closure deciding per exception. So `$sent = rescue(fn () => $sms->send($phone, $text), false);` either returns the send result or reports the failure and returns `false`. ## Where the framework already reports for you Knowing where Laravel calls the handler itself tells you where an explicit `report()` would be redundant: - **HTTP requests:** the HTTP kernel reports any exception that escapes the route pipeline, then renders it. - **Queued jobs:** the queue worker reports an exception a job throws, after it has released the job for a retry or marked it failed. - **Artisan commands:** an uncaught exception in a command is reported and then printed to the console. So inside a job, catching an exception, calling `report($e)` and rethrowing records the same failure twice: once by you and once by the worker. Either rethrow without reporting, or report and return without rethrowing, or turn on deduplication. ## Deduplication with dontReportDuplicates() Reporting in a catch block and then rethrowing is a common way to report the same failure twice: once by your `report($e)` and again by the framework when the rethrown exception goes uncaught. Calling `$exceptions->dontReportDuplicates()` in `bootstrap/app.php` turns on deduplication: 1. Every exception the handler actually reports is recorded in a `WeakMap`, keyed by the exception **object**. 2. Before reporting, the handler checks the map and skips an instance it has already reported. 3. The `WeakMap` holds no strong reference, so recorded exceptions are still garbage-collected. Deduplication is by **identity**, not by message or class. Two separate `new RuntimeException('Gateway timeout')` objects are two reports. Floods of similar exceptions are a job for `throttle()`, not for deduplication. ## Common mistakes - Believing `report()` rethrows, so the code after it never runs. - Expecting `report()` to return a response or a boolean you can branch on. - Calling `report()` and rethrowing without `dontReportDuplicates()`, then seeing each failure twice. - Assuming `dontReportDuplicates()` collapses exceptions with the same message. - Using `report()` to log an exception class that is in `dontReport` and wondering why nothing appears.
- Does report() bypass the dontReport list or throttle()?No. `report()` calls the same handler the framework uses for uncaught exceptions, so `dontReport`, `ShouldntReport`, `dontReportWhen` and `throttle()` all apply, followed by the exception's `report()` method, the report callbacks and the default log.
- With dontReportDuplicates() on, is a second exception with the same message reported?Yes. Deduplication is keyed by object identity: the handler records each reported instance in a `WeakMap` and skips only that same object. A new exception with the same class and message is a separate report; use `throttle()` to limit floods of similar exceptions.
- What can the second and third arguments of rescue() be?The second is the fallback returned when the callback throws; if it is a closure, it is called with the exception. The third, default `true`, decides whether to report; pass `false` to swallow silently or a closure that receives the exception and returns a boolean.
saying these in an interview costs you the question
- report() rethrows the exception after logging it
- report() writes straight to the log, skipping dontReport and throttle()
- rescue() swallows exceptions without reporting them by default
- dontReportDuplicates() collapses exceptions that share a message
- report() returns true when the exception was logged