From 8f6426792b90dcb80366590d59a13326335118c3 Mon Sep 17 00:00:00 2001
From: James Titcumb <james@asgrim.com>
Date: Wed, 23 Mar 2022 12:03:43 +0000
Subject: [PATCH] Prevent the same exception from being sent to Scout multiple
 times

---
 src/Errors/ScoutErrorHandling.php            | 13 ++++++++++++-
 tests/Unit/Errors/ScoutErrorHandlingTest.php | 20 ++++++++++++++++++--
 2 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/src/Errors/ScoutErrorHandling.php b/src/Errors/ScoutErrorHandling.php
index ff68310d..032886a2 100644
--- a/src/Errors/ScoutErrorHandling.php
+++ b/src/Errors/ScoutErrorHandling.php
@@ -18,6 +18,7 @@
 use function is_array;
 use function register_shutdown_function;
 use function set_exception_handler;
+use function spl_object_hash;
 
 use const E_COMPILE_ERROR;
 use const E_CORE_ERROR;
@@ -36,6 +37,8 @@ final class ScoutErrorHandling implements ErrorHandling
     private $reportingClient;
     /** @var Config */
     private $config;
+    /** @var list<string> */
+    private $objectHashesOfHandledExceptions = [];
     /** @var list<ErrorEvent> */
     private $errorEvents = [];
     /** @var callable|null */
@@ -102,7 +105,15 @@ public function recordThrowable(Throwable $throwable): void
             return;
         }
 
-        $this->errorEvents[] = ErrorEvent::fromThrowable($this->request, $throwable);
+        $thisThrowableObjectHash = spl_object_hash($throwable);
+
+        // Storing the object hashes & checking means we don't send exactly the same exception twice in one request
+        if (! in_array($thisThrowableObjectHash, $this->objectHashesOfHandledExceptions, true)) {
+            $this->objectHashesOfHandledExceptions[] = $thisThrowableObjectHash;
+
+            $this->errorEvents[] = ErrorEvent::fromThrowable($this->request, $throwable);
+        }
+
         $this->sendCollectedErrors();
     }
 
diff --git a/tests/Unit/Errors/ScoutErrorHandlingTest.php b/tests/Unit/Errors/ScoutErrorHandlingTest.php
index 686f61af..4b993ee5 100644
--- a/tests/Unit/Errors/ScoutErrorHandlingTest.php
+++ b/tests/Unit/Errors/ScoutErrorHandlingTest.php
@@ -175,11 +175,27 @@ public function testExceptionIsNotRecordedWhenDisabled(): void
 
         $this->reportingClient
             ->expects(self::never())
-            ->method('sendErrorToScout')
-            ->with(self::isInstanceOf(ErrorEvent::class));
+            ->method('sendErrorToScout');
 
         $handling->recordThrowable(new RuntimeException());
 
         $handling->sendCollectedErrors();
     }
+
+    public function testExactlyTheSameExceptionIsNotSentTwice(): void
+    {
+        $handling = $this->errorHandlingFromConfig(Config::fromArray([Config\ConfigKey::ERRORS_ENABLED => true]));
+
+        $this->reportingClient
+            ->expects(self::once())
+            ->method('sendErrorToScout')
+            ->with(self::containsOnlyInstancesOf(ErrorEvent::class));
+
+        $exceptionInstance = new RuntimeException();
+
+        $handling->recordThrowable($exceptionInstance);
+        $handling->recordThrowable($exceptionInstance);
+
+        $handling->sendCollectedErrors();
+    }
 }