Skip to content

Commit 2e3d23f

Browse files
[10.x] Deduplicate exceptions (#48288)
* Deduplicate exceptions * Fix test * lint * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 52b3017 commit 2e3d23f

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

src/Illuminate/Foundation/Exceptions/Handler.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
4343
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
4444
use Throwable;
45+
use WeakMap;
4546

4647
class Handler implements ExceptionHandlerContract
4748
{
@@ -119,6 +120,20 @@ class Handler implements ExceptionHandlerContract
119120
'password_confirmation',
120121
];
121122

123+
/**
124+
* Indicates that exception reporting should be deduplicated.
125+
*
126+
* @var bool
127+
*/
128+
protected $deduplicateReporting = false;
129+
130+
/**
131+
* The already reported exception map.
132+
*
133+
* @var \WeakMap
134+
*/
135+
protected $reportedExceptionMap;
136+
122137
/**
123138
* Create a new exception handler instance.
124139
*
@@ -129,6 +144,8 @@ public function __construct(Container $container)
129144
{
130145
$this->container = $container;
131146

147+
$this->reportedExceptionMap = new WeakMap;
148+
132149
$this->register();
133150
}
134151

@@ -260,6 +277,8 @@ public function report(Throwable $e)
260277
*/
261278
protected function reportThrowable(Throwable $e): void
262279
{
280+
$this->reportedExceptionMap[$e] = true;
281+
263282
if (Reflector::isCallable($reportCallable = [$e, 'report']) &&
264283
$this->container->call($reportCallable) !== false) {
265284
return;
@@ -307,6 +326,10 @@ public function shouldReport(Throwable $e)
307326
*/
308327
protected function shouldntReport(Throwable $e)
309328
{
329+
if ($this->deduplicateReporting && ($this->reportedExceptionMap[$e] ?? false)) {
330+
return true;
331+
}
332+
310333
$dontReport = array_merge($this->dontReport, $this->internalDontReport);
311334

312335
return ! is_null(Arr::first($dontReport, fn ($type) => $e instanceof $type));
@@ -786,6 +809,18 @@ public function renderForConsole($output, Throwable $e)
786809
(new ConsoleApplication)->renderThrowable($e, $output);
787810
}
788811

812+
/**
813+
* Do not report duplicate exceptions.
814+
*
815+
* @return $this
816+
*/
817+
public function dontReportDuplicates()
818+
{
819+
$this->deduplicateReporting = true;
820+
821+
return $this;
822+
}
823+
789824
/**
790825
* Determine if the given exception is an HTTP exception.
791826
*

tests/Foundation/FoundationExceptionsHandlerTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,40 @@ public function testAssertExceptionIsThrown()
436436
Assert::fail('assertThrows failed: non matching message are thrown.');
437437
}
438438
}
439+
440+
public function testItReportsDuplicateExceptions()
441+
{
442+
$reported = [];
443+
$this->handler->reportable(function (\Throwable $e) use (&$reported) {
444+
$reported[] = $e;
445+
446+
return false;
447+
});
448+
449+
$this->handler->report($one = new RuntimeException('foo'));
450+
$this->handler->report($one);
451+
$this->handler->report($two = new RuntimeException('foo'));
452+
453+
$this->assertSame($reported, [$one, $one, $two]);
454+
}
455+
456+
public function testItCanDedupeExceptions()
457+
{
458+
$reported = [];
459+
$e = new RuntimeException('foo');
460+
$this->handler->reportable(function (\Throwable $e) use (&$reported) {
461+
$reported[] = $e;
462+
463+
return false;
464+
});
465+
466+
$this->handler->dontReportDuplicates();
467+
$this->handler->report($one = new RuntimeException('foo'));
468+
$this->handler->report($one);
469+
$this->handler->report($two = new RuntimeException('foo'));
470+
471+
$this->assertSame($reported, [$one, $two]);
472+
}
439473
}
440474

441475
class CustomException extends Exception

0 commit comments

Comments
 (0)