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": "*", diff --git a/src/Handler.php b/src/Handler.php index 0e85429..e3b26ff 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,18 @@ 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) { + 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..4379e6f 100644 --- a/src/Options.php +++ b/src/Options.php @@ -5,88 +5,168 @@ 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 $options = [ - 'integrationToken' => '', - 'url' => 'https://k1.hawk.so/', - 'release' => '', - 'error_types' => \E_ALL, - 'beforeSend' => null, - 'timeout' => 10, + private $url = 'https://k1.hawk.so/'; + + /** + * @var string + */ + 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; } } 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