From b64f33076d3c57f9b74887140003ca5447870fea Mon Sep 17 00:00:00 2001 From: Pavel Zotikov Date: Wed, 4 Dec 2024 22:48:06 +0300 Subject: [PATCH 1/5] Add shouldHandleError method to Handler and update Options class --- src/Handler.php | 17 +++-- src/Options.php | 169 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 135 insertions(+), 51 deletions(-) diff --git a/src/Handler.php b/src/Handler.php index 0e85429..54cc49d 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -200,12 +200,12 @@ public function handleError(int $level, string $message, string $file, int $line $eventPayload = $this->eventPayloadBuilder->create($data); $event = $this->buildEvent($eventPayload); - if ($event !== null) { + if ($event !== null && $this->shouldHandleError($level, $isSilencedError)) { $this->send($event); + } - if (null !== $this->previousErrorHandler) { - return false !== ($this->previousErrorHandler)($level, $message, $file, $line); - } + if (null !== $this->previousErrorHandler) { + return false !== ($this->previousErrorHandler)($level, $message, $file, $line); } return false; @@ -335,6 +335,15 @@ public function buildEvent(EventPayload $eventPayload): ?Event ); } + private function shouldHandleError(int $level, bool $silenced): bool + { + if ($silenced) { + return $this->options->shouldCaptureSilencedErrors(); + } + + return ($this->options->getErrorTypes() & $level) !== 0; + } + /** * Send the event to the remote server. */ diff --git a/src/Options.php b/src/Options.php index 7a7d11c..7f6cc78 100644 --- a/src/Options.php +++ b/src/Options.php @@ -5,88 +5,163 @@ namespace Hawk; /** - * Class Options is responsible for configuring catcher - * - * @package Hawk + * Class Options is responsible for configuring the Hawk catcher. */ class Options { /** - * Default available options - * - * @var array + * @var string + */ + private $integrationToken = ''; + + /** + * @var string + */ + private $url = 'https://k1.hawk.so/'; + + /** + * @var string */ - private $options = [ - 'integrationToken' => '', - 'url' => 'https://k1.hawk.so/', - 'release' => '', - 'error_types' => \E_ALL, - 'beforeSend' => null, - 'timeout' => 10, + private $release = ''; + + /** + * @var int|null + */ + private $errorTypes = null; + + /** + * @var bool + */ + private $captureSilencedErrors = false; + + /** + * @var callable|null + */ + private $beforeSend = null; + + /** + * @var int + */ + private $timeout = 10; + + /** + * Map of accepted option keys to class properties. + */ + private const OPTION_KEYS = [ + 'integrationToken' => 'integrationToken', + 'integration_token' => 'integrationToken', + 'url' => 'url', + 'release' => 'release', + 'errorTypes' => 'errorTypes', + 'error_types' => 'errorTypes', + 'captureSilencedErrors' => 'captureSilencedErrors', + 'capture_silenced_errors' => 'captureSilencedErrors', + 'beforeSend' => 'beforeSend', + 'before_send' => 'beforeSend', + 'timeout' => 'timeout', ]; /** * Options constructor. * - * @param array $options + * @param array $options Associative array of options to initialize. */ public function __construct(array $options = []) { - $this->options = array_merge($this->options, $options); + foreach ($options as $key => $value) { + $normalizedKey = self::OPTION_KEYS[$key] ?? null; + + if ($normalizedKey === null) { + throw new \InvalidArgumentException("Unknown option: $key"); + } + + $this->setOption($normalizedKey, $value); + } } /** - * Returns access token. It is available on projects settings + * Set a class property based on the normalized option key. * - * @return string + * @param string $key + * @param mixed $value */ - public function getIntegrationToken(): string + private function setOption(string $key, $value): void { - return $this->options['integrationToken']; + switch ($key) { + case 'integrationToken': + case 'release': + case 'url': + if (!is_string($value)) { + throw new \InvalidArgumentException("Option '$key' must be a string."); + } + $this->$key = $value; + break; + + case 'errorTypes': + if (!is_int($value) && $value !== null) { + throw new \InvalidArgumentException("Option 'errorTypes' must be an integer or null."); + } + $this->errorTypes = $value; + break; + + case 'captureSilencedErrors': + if (!is_bool($value)) { + throw new \InvalidArgumentException("Option 'captureSilencedErrors' must be a boolean."); + } + $this->captureSilencedErrors = $value; + break; + + case 'beforeSend': + if (!is_callable($value) && $value !== null) { + throw new \InvalidArgumentException("Option 'beforeSend' must be callable or null."); + } + $this->beforeSend = $value; + break; + + case 'timeout': + if (!is_int($value)) { + throw new \InvalidArgumentException("Option 'timeout' must be an integer."); + } + $this->timeout = $value; + break; + + default: + throw new \InvalidArgumentException("Unknown option '$key'."); + } } - /** - * Returns URL that should be send - * - * @return string - */ - public function getUrl(): string + public function getIntegrationToken(): string { - return $this->options['url']; + return $this->integrationToken; } - public function getTimeout(): int + public function getUrl(): string { - return $this->options['timeout']; + return $this->url; } - /** - * Returns application release - * - * @return string - */ public function getRelease(): string { - return $this->options['release']; + return $this->release; } - /** - * Returns error types - * - * @return int - */ public function getErrorTypes(): int { - return $this->options['error_types']; + return $this->errorTypes ?? error_reporting(); + } + + public function shouldCaptureSilencedErrors(): bool + { + return $this->captureSilencedErrors; } - /** - * Returns before send callback - * - * @return callable|null - */ public function getBeforeSend(): ?callable { - return $this->options['beforeSend']; + return $this->beforeSend; + } + + public function getTimeout(): int + { + return $this->timeout; } -} +} \ No newline at end of file From 6d0aea322e4cafded513afe4bbe9c84648b1d5bc Mon Sep 17 00:00:00 2001 From: Pavel Zotikov Date: Wed, 4 Dec 2024 22:48:33 +0300 Subject: [PATCH 2/5] new version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f5236d0..783544d 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "PHP errors Catcher module for Hawk.so", "keywords": ["hawk", "php", "error", "catcher"], "type": "library", - "version": "2.2.6", + "version": "2.2.7", "license": "MIT", "require": { "ext-curl": "*", From d7cef3fa6bb64c3ca2b9e80b50e232fd729b9f6c Mon Sep 17 00:00:00 2001 From: Pavel Zotikov Date: Thu, 5 Dec 2024 12:17:29 +0300 Subject: [PATCH 3/5] phpdoc --- src/Handler.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Handler.php b/src/Handler.php index 54cc49d..e3b26ff 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -335,6 +335,9 @@ public function buildEvent(EventPayload $eventPayload): ?Event ); } + /** + * Determines if the error should be handled, considering its level and if it was silenced using (@). + */ private function shouldHandleError(int $level, bool $silenced): bool { if ($silenced) { From 8074894bf89a65cb4d534133f1d09f4bc3bd615a Mon Sep 17 00:00:00 2001 From: Pavel Zotikov Date: Thu, 5 Dec 2024 13:01:49 +0300 Subject: [PATCH 4/5] linter --- src/Options.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Options.php b/src/Options.php index 7f6cc78..4379e6f 100644 --- a/src/Options.php +++ b/src/Options.php @@ -83,7 +83,7 @@ public function __construct(array $options = []) * Set a class property based on the normalized option key. * * @param string $key - * @param mixed $value + * @param mixed $value */ private function setOption(string $key, $value): void { @@ -95,6 +95,7 @@ private function setOption(string $key, $value): void throw new \InvalidArgumentException("Option '$key' must be a string."); } $this->$key = $value; + break; case 'errorTypes': @@ -102,6 +103,7 @@ private function setOption(string $key, $value): void throw new \InvalidArgumentException("Option 'errorTypes' must be an integer or null."); } $this->errorTypes = $value; + break; case 'captureSilencedErrors': @@ -109,6 +111,7 @@ private function setOption(string $key, $value): void throw new \InvalidArgumentException("Option 'captureSilencedErrors' must be a boolean."); } $this->captureSilencedErrors = $value; + break; case 'beforeSend': @@ -116,6 +119,7 @@ private function setOption(string $key, $value): void throw new \InvalidArgumentException("Option 'beforeSend' must be callable or null."); } $this->beforeSend = $value; + break; case 'timeout': @@ -123,6 +127,7 @@ private function setOption(string $key, $value): void throw new \InvalidArgumentException("Option 'timeout' must be an integer."); } $this->timeout = $value; + break; default: @@ -164,4 +169,4 @@ public function getTimeout(): int { return $this->timeout; } -} \ No newline at end of file +} From 60b1d64c863c6874f76b9aa830a5002b2cdd7490 Mon Sep 17 00:00:00 2001 From: Pavel Zotikov Date: Thu, 5 Dec 2024 13:03:19 +0300 Subject: [PATCH 5/5] Update OptionsTest.php --- tests/Unit/OptionsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/OptionsTest.php b/tests/Unit/OptionsTest.php index 9c43fa5..1016708 100644 --- a/tests/Unit/OptionsTest.php +++ b/tests/Unit/OptionsTest.php @@ -17,7 +17,7 @@ public function testDefaultOptions(): void $this->assertEmpty($options->getIntegrationToken()); $this->assertEmpty($options->getRelease()); $this->assertEquals('https://k1.hawk.so/', $options->getUrl()); - $this->assertEquals(\E_ALL, $options->getErrorTypes()); + $this->assertEquals(error_reporting(), $options->getErrorTypes()); } public function testCustomOptions(): void