From 117673f4e7d0f322674b627afba5cbb92a8aff8c Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Fri, 11 Apr 2025 19:43:56 +0200 Subject: [PATCH 01/14] chore: organize files, code, and project structure for better clarity and maintainability --- src/Enums/BlunderErrorType.php | 117 +++++++++++++++ src/ExceptionMetadata.php | 146 ++++++++++++++++++ src/Handlers/AbstractHandler.php | 217 ++------------------------- src/Handlers/CliHandler.php | 18 +-- src/Handlers/HtmlHandler.php | 134 ++--------------- src/Handlers/JsonHandler.php | 16 +- src/Handlers/PlainTextHandler.php | 12 -- src/Handlers/SilentHandler.php | 12 -- src/Handlers/TextHandler.php | 16 +- src/Handlers/XmlHandler.php | 16 +- src/SeverityLevelPool.php | 28 +--- src/Templates/HtmlHelperTrait.php | 228 +++++++++++++++++++++++++++++ tests/unitary-blunder-handlers.php | 16 +- 13 files changed, 542 insertions(+), 434 deletions(-) create mode 100644 src/Enums/BlunderErrorType.php create mode 100644 src/ExceptionMetadata.php create mode 100644 src/Templates/HtmlHelperTrait.php diff --git a/src/Enums/BlunderErrorType.php b/src/Enums/BlunderErrorType.php new file mode 100644 index 0000000..96890da --- /dev/null +++ b/src/Enums/BlunderErrorType.php @@ -0,0 +1,117 @@ + + */ + private const MAP = [ + 'FALLBACK' => [0, 'E_USER_ERROR', 'Fallback error'], + 'ERROR' => [E_ERROR, 'E_ERROR', 'Fatal error'], + 'WARNING' => [E_WARNING, 'E_WARNING', 'Warning'], + 'PARSE' => [E_PARSE, 'E_PARSE', 'Parse error'], + 'NOTICE' => [E_NOTICE, 'E_NOTICE', 'Notice'], + 'CORE_ERROR' => [E_CORE_ERROR, 'E_CORE_ERROR', 'Core fatal error'], + 'CORE_WARNING' => [E_CORE_WARNING, 'E_CORE_WARNING', 'Core warning'], + 'COMPILE_ERROR' => [E_COMPILE_ERROR, 'E_COMPILE_ERROR', 'Compile-time fatal error'], + 'COMPILE_WARNING' => [E_COMPILE_WARNING, 'E_COMPILE_WARNING', 'Compile-time warning'], + 'USER_ERROR' => [E_USER_ERROR, 'E_USER_ERROR', 'User fatal error'], + 'USER_WARNING' => [E_USER_WARNING, 'E_USER_WARNING', 'User warning'], + 'USER_NOTICE' => [E_USER_NOTICE, 'E_USER_NOTICE', 'User notice'], + 'RECOVERABLE_ERROR' => [E_RECOVERABLE_ERROR, 'E_RECOVERABLE_ERROR', 'Recoverable fatal error'], + 'DEPRECATED' => [E_DEPRECATED, 'E_DEPRECATED', 'Deprecated notice'], + 'USER_DEPRECATED' => [E_USER_DEPRECATED, 'E_USER_DEPRECATED', 'User deprecated notice'], + 'E_ALL' => [E_ALL, 'E_ALL', 'All'], + ]; + + + /** + * Retrieve all PHP constant values that are mapped, either preserving the original keys or as a flat list. + * + * @param bool $preserveKeys If true, retains the original keys. Otherwise, returns a flat indexed array. + * @return array List of PHP constant values. + */ + public static function getAllErrorLevels(bool $preserveKeys = false): array + { + $items = self::MAP; + array_shift($items); + $arr = array_map(fn ($item) => $item[0], $items); + if($preserveKeys) { + return $arr; + } + return array_values($arr); + } + + /** + * Get the PHP constant value associated with this error type. + * + * @return int The constant value corresponding to the enum case. + */ + public function getErrorLevel(): int + { + return self::MAP[$this->name][0]; + } + + + /** + * Get the PHP constant key (name) associated with this error type. + * + * @return string The constant key corresponding to the enum case. + */ + public function getErrorLevelKey(): string + { + return self::MAP[$this->name][1]; + } + + + /** + * Get the user-friendly title for this error type. + * If the error type is `FALLBACK`, a custom fallback title is returned. + * + * @param string $fallback Custom fallback title used if the error type is `FALLBACK`. Defaults to 'Error'. + * @return string The user-friendly title associated with this error type. + */ + public function getErrorLevelTitle(string $fallback = 'Error'): string + { + return $this === self::FALLBACK ? $fallback : self::MAP[$this->name][2]; + } + + /** + * Get the enum instance corresponding to the specified error number. + * + * If the error number does not match any predefined case, it returns the default fallback case. + * + * @param int $errno The error number to find the corresponding enum case for. + * @return self The matching enum case, or the fallback case if no match is found. + */ + public static function fromErrorLevel(int $errno): self + { + $cases = self::cases(); + foreach ($cases as $case) { + if ($case->getErrorLevel() === $errno) { + return $case; + } + } + return reset($cases); + } +} diff --git a/src/ExceptionMetadata.php b/src/ExceptionMetadata.php new file mode 100644 index 0000000..c7971a7 --- /dev/null +++ b/src/ExceptionMetadata.php @@ -0,0 +1,146 @@ +exception = $exception; + $this->severityError = BlunderErrorType::fromErrorLevel(1); + } + + /** + * Set a max trace level + * + * @param int $maxTraceLevel + * @return $this + */ + public function setMaxTraceLevel(int $maxTraceLevel): self + { + $this->maxTraceLevel = $maxTraceLevel; + return $this; + } + + /** + * Disable trace levels, (is enabled by default) + * + * @param bool $disableTraceLevel + * @return $this + */ + public function disableTraceLevel(bool $disableTraceLevel = true): self + { + $this->enableTraceLevel = $disableTraceLevel; + return $this; + } + + /** + * Get the max trace level + * + * @return int + */ + protected function getMaxTraceLevel(): int + { + return !is_null($this->maxTraceLevel) ? $this->maxTraceLevel : static::MAX_TRACE_LEVEL; + } + + /** + * Get current exception + * + * @return Throwable + */ + public function getException(): throwable + { + return $this->exception; + } + + /** + * Will return a expected severity type as enum + * + * @return BlunderErrorType + */ + public function getSeverityError(): BlunderErrorType + { + $this->severityError = BlunderErrorType::fromErrorLevel(1); + if ($this->exception instanceof ErrorException) { + $this->severityError = BlunderErrorType::fromErrorLevel($this->exception->getSeverity()); + } + return $this->severityError; + } + + /** + * Get severity flag title + * + * @return string|null + */ + public function getSeverityConstant(): ?string + { + return $this->getSeverityError()->getErrorLevelKey(); + } + + /** + * Create title from exception severity + * + * @return string|null + */ + public function getSeverityTitle(): ?string + { + return $this->getSeverityError()->getErrorLevelTitle(); + } + + + /** + * Get trace line with filtered arguments and max length + * + * @return array + */ + public function getTrace(): array + { + $new = []; + $trace = $this->exception->getTrace(); + + array_unshift($trace, $this->pollyFillException([ + 'file' => $this->exception->getFile(), + 'line' => $this->exception->getLine(), + 'class' => get_class($this->exception) + ])); + + foreach ($trace as $key => $stackPoint) { + $new[$key] = $stackPoint; + $new[$key]['args'] = array_map('gettype', (array)($new[$key]['args'] ?? [])); + if($key >= ($this->getMaxTraceLevel() - 1) || !$this->enableTraceLevel) { + break; + } + } + return $new; + } + + /** + * Get an exception array with right items + * + * @param array $arr + * @return array + */ + public function pollyFillException(array $arr): array + { + return array_merge([ + 'file' => "", + 'line' => "", + 'class' => "", + 'function' => null, + 'type' => null, + 'args' => [] + ], $arr); + } + +} \ No newline at end of file diff --git a/src/Handlers/AbstractHandler.php b/src/Handlers/AbstractHandler.php index 7a6f8ad..765e10d 100755 --- a/src/Handlers/AbstractHandler.php +++ b/src/Handlers/AbstractHandler.php @@ -11,6 +11,8 @@ use ErrorException; use MaplePHP\Blunder\BlunderErrorException; +use MaplePHP\Blunder\Enums\BlunderErrorType; +use MaplePHP\Blunder\ExceptionMetadata; use MaplePHP\Blunder\Interfaces\AbstractHandlerInterface; use MaplePHP\Blunder\Interfaces\HandlerInterface; use MaplePHP\Blunder\Interfaces\HttpMessagingInterface; @@ -30,7 +32,7 @@ abstract class AbstractHandler implements AbstractHandlerInterface protected const MAX_TRACE_LENGTH = 40; protected static ?int $exitCode = null; - protected static bool $enabledTraceLines = false; + protected static bool $enabledTraceLines = true; protected bool $throwException = true; protected ?Throwable $exception = null; @@ -39,15 +41,6 @@ abstract class AbstractHandler implements AbstractHandlerInterface protected ?SeverityLevelPool $severityLevelPool = null; protected int $severity = E_ALL; - /** - * Determine how the code block should look like - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - abstract protected function getCodeBlock(array $data, string $code, int $index = 0): string; - /** * Sets the exit code to be used when an error occurs. * If you want Blunder to trigger a specific exit code on error, @@ -195,7 +188,8 @@ public function redirectExceptionHandler( /** * Shutdown handler - * @throws ErrorException + * + * @throws Throwable */ public function shutdownHandler(): void { @@ -215,36 +209,9 @@ public function shutdownHandler(): void $this->sendExitCode(); } - /** - * Get trace line with filtered arguments and max length - * @param Throwable $exception - * @return array - */ - protected function getTrace(throwable $exception): array - { - $new = []; - $trace = $exception->getTrace(); - - array_unshift($trace, $this->pollyFillException([ - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - 'class' => get_class($exception) - ])); - - foreach ($trace as $key => $stackPoint) { - $new[$key] = $stackPoint; - $new[$key]['args'] = array_map('gettype', (array)($new[$key]['args'] ?? [])); - if($key >= (static::MAX_TRACE_LENGTH - 1) || !static::$enabledTraceLines) { - break; - } - } - - return $new; - } - - /** * Emit response + * * @param Throwable $exception * @param ExceptionItem|null $exceptionItem * @return void @@ -275,7 +242,7 @@ protected function emitter(throwable $exception, ?ExceptionItem $exceptionItem = } /** - * Will send a exit code if specied + * Will send a exit code if specified * * @return void */ @@ -287,179 +254,19 @@ protected function sendExitCode(): void } /** - * Get code between start and end span from file - * @param StreamInterface $stream - * @param int $errorLine - * @param int $startSpan - * @param int $endSpan - * @return string - */ - protected function getContentsBetween(StreamInterface $stream, int $errorLine, int $startSpan = 10, int $endSpan = 12): string - { - $index = 1; - $output = ''; - $startLine = $errorLine - $startSpan; - $endLine = $errorLine + $endSpan; - if($startLine < 1) { - $startLine = 1; - } - while (!$stream->eof()) { - $line = $stream->read((int)$stream->getSize()); - $lines = explode("\n", $line); - foreach ($lines as $lineContent) { - if ($index >= $startLine && $index <= $endLine) { - $output .= ''; - $output .= ''. $index .''; - if($errorLine === $index) { - $output .= "" . htmlspecialchars($lineContent) . "\n"; - } else { - $output .= "" . htmlspecialchars($lineContent) . "\n"; - } - $output .= ''; - } - if ($index > $endLine) { - break; - } - $index++; - } - } - - return $output; - } - - /** - * Will return the severity exception breadcrumb - * @param Throwable $exception - * @return string - */ - public function getSeverityBreadcrumb(throwable $exception): string - { - - $severityTitle = $this->getSeverityTitle($exception); - $breadcrumb = get_class($exception); - if(!is_null($severityTitle)) { - $breadcrumb .= " ($severityTitle)"; - } - - return "
$breadcrumb
"; - } - - /** - * Get severity flag title - * @param Throwable $exception - * @return string|null - */ - final public function getSeverityTitle(throwable $exception): ?string - { - $severityTitle = null; - if ($exception instanceof ErrorException) { - $severityTitle = SeverityLevelPool::getSeverityLevel($exception->getSeverity(), "Error"); - } - - return $severityTitle; - } - - /** - * This will add the code block structure - * If you wish to edit the block then you should edit the "getCodeBlock" method - * @param array $trace - * @return array - */ - final protected function getTraceCodeBlock(array $trace): array - { - $block = []; - foreach ($trace as $key => $stackPoint) { - if(is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { - $stream = $this->getStream($stackPoint['file']); - $code = $this->getContentsBetween($stream, (int)$stackPoint['line']); - $block[] = $this->getCodeBlock($stackPoint, $code, $key); - $stream->close(); - } - } - - return $block; - } - - /** - * Used to fetch valid asset - * @param string $file - * @return string - * @throws ErrorException - */ - public function getAssetContent(string $file): string - { - $ending = explode(".", $file); - $ending = end($ending); - - if(!($ending === "css" || $ending === "js")) { - throw new ErrorException("Only JS and CSS files are allowed as assets files"); - } - $filePath = (str_starts_with($file, "/") ? realpath($file) : realpath(__DIR__ . "/../") . "/" . $file); - $stream = $this->getStream($filePath); - - return $stream->getContents(); - } - - /** - * Generate error message + * Generate error message (placeholder) + * * @param Throwable $exception * @return string */ protected function getErrorMessage(Throwable $exception): string { - $traceLine = "#%s %s(%s): %s(%s)"; - $msg = "PHP Fatal error: Uncaught exception '%s (%s)' with message '%s' in %s:%s\nStack trace:\n%s\n thrown in %s on line %s"; - - $key = 0; - $result = []; - $trace = $this->getTrace($exception); - $severityLevel = (method_exists($exception, "getSeverity") ? $exception->getSeverity() : 0); - foreach ($trace as $key => $stackPoint) { - if(is_array($stackPoint)) { - $result[] = sprintf( - $traceLine, - $key, - (string)($stackPoint['file'] ?? 0), - (string)($stackPoint['line'] ?? 0), - (string)($stackPoint['function'] ?? "void"), - implode(', ', (array)$stackPoint['args']) - ); - } - } - - $result[] = '#' . ((int)$key + 1) . ' {main}'; - return sprintf( - $msg, - get_class($exception), - (string)SeverityLevelPool::getSeverityLevel((int)$severityLevel, "Error"), - $exception->getMessage(), - $exception->getFile(), - $exception->getLine(), - implode("\n", $result), - $exception->getFile(), - $exception->getLine() - ); - } - - /** - * Get an exception array with right items - * @param array $arr - * @return array - */ - public function pollyFillException(array $arr): array - { - return array_merge([ - 'file' => "", - 'line' => "", - 'class' => "", - 'function' => null, - 'type' => null, - 'args' => [] - ], $arr); + return ""; } /** * This will clean all active output buffers + * * @return void */ final public function cleanOutputBuffers(): void @@ -473,6 +280,7 @@ final public function cleanOutputBuffers(): void /** * Will get valid stream + * * @param mixed|null $stream * @param string $permission * @return StreamInterface @@ -486,4 +294,5 @@ final protected function getStream(mixed $stream = null, string $permission = "r return $this->http->stream($stream, $permission); } + } diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index 4630db2..d4256b5 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -9,6 +9,7 @@ namespace MaplePHP\Blunder\Handlers; +use MaplePHP\Blunder\ExceptionMetadata; use MaplePHP\Blunder\Interfaces\HandlerInterface; use MaplePHP\Blunder\SeverityLevelPool; use MaplePHP\Prompts\Ansi; @@ -37,6 +38,9 @@ public function exceptionHandler(Throwable $exception): void */ protected function getErrorMessage(Throwable $exception): string { + + $meta = new ExceptionMetadata($exception); + $msg = "\n"; $msg .= self::ansi()->red("%s ") . self::ansi()->italic("(%s)") . ": "; $msg .= self::ansi()->bold("%s ") . " \n\n"; @@ -45,7 +49,7 @@ protected function getErrorMessage(Throwable $exception): string $result = []; if(self::$enabledTraceLines) { - $trace = $this->getTrace($exception); + $trace = $meta->getTrace(); $result = $this->getTraceResult($trace); $msg .= self::ansi()->bold("Stack trace:") . "\n"; $msg .= "%s\n"; @@ -108,16 +112,4 @@ protected static function ansi(): Ansi return self::$ansi; } - - /** - * This is the visible code block - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - protected function getCodeBlock(array $data, string $code, int $index = 0): string - { - return $code; - } } diff --git a/src/Handlers/HtmlHandler.php b/src/Handlers/HtmlHandler.php index ab0f1e2..bcb7cae 100755 --- a/src/Handlers/HtmlHandler.php +++ b/src/Handlers/HtmlHandler.php @@ -10,11 +10,15 @@ namespace MaplePHP\Blunder\Handlers; use ErrorException; +use MaplePHP\Blunder\ExceptionMetadata; use MaplePHP\Blunder\Interfaces\HandlerInterface; +use MaplePHP\Blunder\Templates\HtmlHelperTrait; use Throwable; class HtmlHandler extends AbstractHandler implements HandlerInterface { + use HtmlHelperTrait; + /** @var string */ public const CSS_FILE = 'main.css'; /** @var string */ @@ -22,13 +26,9 @@ class HtmlHandler extends AbstractHandler implements HandlerInterface protected static bool $enabledTraceLines = true; - public function testssss($www1) - { - return "Lorem1"; - } - /** * Exception handler output + * * @param Throwable $exception * @return void * @throws ErrorException @@ -41,15 +41,17 @@ public function exceptionHandler(Throwable $exception): void /** * The pretty output template + * * @param Throwable $exception * @return string * @throws ErrorException */ protected function document(Throwable $exception): string { - $trace = $this->getTrace($exception); + $meta = new ExceptionMetadata($exception); + $meta->setMaxTraceLevel(static::$enabledTraceLines); + $trace = $meta->getTrace(); $codeBlockArr = $this->getTraceCodeBlock($trace); - $port = $this->getHttp()->request()->getUri()->getPort(); if(is_null($port)) { $port = 80; @@ -81,9 +83,12 @@ protected function document(Throwable $exception): string
' . implode("\n", $codeBlockArr) . ' @@ -109,115 +114,4 @@ protected function document(Throwable $exception): string '; } - - /** - * This is the visible code block - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - protected function getCodeBlock(array $data, string $code, int $index = 0): string - { - $class = (string)($data['class'] ?? ""); - $function = (string)($data['function'] ?? ""); - $functionName = ($function !== "") ? ' (' . $function . ')' : ''; - - return "
-
" . $class . $functionName . "
-
{$data['file']}
-
" . $code . "
-
"; - } - - /** - * This is a navigation item that will point to code block - * @param int $index - * @param int $length - * @param array $stack - * @return string - */ - protected function getNavBlock(int $index, int $length, array $stack): string - { - $active = ($index === 0) ? " active" : ""; - $class = (string)($stack['class'] ?? ""); - $function = (string)($stack['function'] ?? ""); - $functionName = ($function !== "") ? ' (' . $function . ')' : ''; - - return " - " . "" . ($length - $index) . ". $class . $functionName - " . ltrim((string)$stack['file'], "/") . ": {$stack['line']} - " - ; - } - - protected function getRows(string $title, ?array $rows): string - { - $out = '
'; - $out .= '

' . $title . '

'; - if(is_array($rows) && count($rows) > 0) { - foreach($rows as $key => $value) { - if(is_array($value)) { - $value = json_encode($value); - } - - $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); - $value = $this->excerpt($value, 400); - - $out .= ''; - } - } else { - $out .= '
None
'; - } - $out .= '
'; - - return $out; - } - - /** - * This will add the navigation block html structure - * If you wish to edit the block then you should edit the "getNavBlock" method - * @param array $trace - * @return string - */ - final protected function getTraceNavBlock(array $trace): string - { - $output = ""; - $length = count($trace); - foreach ($trace as $index => $stackPoint) { - if(is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { - $output .= $this->getNavBlock($index, $length, $stackPoint); - } - } - - return $output; - } - - /** - * This will return the MaplePHP logotype - * @return string - */ - final protected function getLogo(): string - { - return ''; - } - - /** - * Utilizing a "byte" excerpt - * Going for performance with byte calculation instead of precision with multibyte. - * @param string $value - * @param int $length - * @return string - */ - protected function excerpt(string $value, int $length): string - { - if(strlen($value) > $length) { - $value = trim(substr($value, 0, $length)) . "..."; - } - - return $value; - } } diff --git a/src/Handlers/JsonHandler.php b/src/Handlers/JsonHandler.php index a153a43..2622ff8 100755 --- a/src/Handlers/JsonHandler.php +++ b/src/Handlers/JsonHandler.php @@ -10,6 +10,7 @@ namespace MaplePHP\Blunder\Handlers; use MaplePHP\Blunder\ExceptionItem; +use MaplePHP\Blunder\ExceptionMetadata; use MaplePHP\Blunder\Interfaces\HandlerInterface; use Throwable; @@ -24,7 +25,8 @@ class JsonHandler extends AbstractHandler implements HandlerInterface */ public function exceptionHandler(Throwable $exception): void { - $trace = $this->getTrace($exception); + $meta = new ExceptionMetadata($exception); + $trace = $meta->getTrace(); $exceptionItem = new ExceptionItem($exception); $this->getHttp()->response()->getBody()->write(json_encode([ @@ -39,16 +41,4 @@ public function exceptionHandler(Throwable $exception): void $this->getHttp()->response()->withHeader('content-type', 'application/json; charset=utf-8'); $this->emitter($exception, $exceptionItem); } - - /** - * This is the visible code block - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - protected function getCodeBlock(array $data, string $code, int $index = 0): string - { - return $code; - } } diff --git a/src/Handlers/PlainTextHandler.php b/src/Handlers/PlainTextHandler.php index 85e0bc3..8860245 100755 --- a/src/Handlers/PlainTextHandler.php +++ b/src/Handlers/PlainTextHandler.php @@ -26,16 +26,4 @@ public function exceptionHandler(Throwable $exception): void $this->getHttp()->response()->getBody()->write(strip_tags($this->getErrorMessage($exception))); $this->emitter($exception); } - - /** - * This is the visible code block - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - protected function getCodeBlock(array $data, string $code, int $index = 0): string - { - return $code; - } } diff --git a/src/Handlers/SilentHandler.php b/src/Handlers/SilentHandler.php index 39d424c..ad1de8c 100755 --- a/src/Handlers/SilentHandler.php +++ b/src/Handlers/SilentHandler.php @@ -47,16 +47,4 @@ public function exceptionHandler(Throwable $exception): void } } } - - /** - * This is the visible code block - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - protected function getCodeBlock(array $data, string $code, int $index = 0): string - { - return $code; - } } diff --git a/src/Handlers/TextHandler.php b/src/Handlers/TextHandler.php index a2fcedb..1e9c57c 100755 --- a/src/Handlers/TextHandler.php +++ b/src/Handlers/TextHandler.php @@ -9,6 +9,7 @@ namespace MaplePHP\Blunder\Handlers; +use MaplePHP\Blunder\ExceptionMetadata; use MaplePHP\Blunder\Interfaces\HandlerInterface; use MaplePHP\Blunder\SeverityLevelPool; use Throwable; @@ -35,12 +36,13 @@ public function exceptionHandler(Throwable $exception): void */ protected function getErrorMessage(Throwable $exception): string { + $meta = new ExceptionMetadata($exception); $traceLine = "#%s %s(%s): %s(%s)"; $msg = "PHP Fatal error: Uncaught exception '%s (%s)' with message '%s' in %s:%s\nStack trace:\n%s\n thrown in %s on line %s"; $key = 0; $result = []; - $trace = $this->getTrace($exception); + $trace = $meta->getTrace(); $severityLevel = (method_exists($exception, "getSeverity") ? $exception->getSeverity() : 0); foreach ($trace as $key => $stackPoint) { @@ -72,16 +74,4 @@ protected function getErrorMessage(Throwable $exception): string $exception->getLine() ); } - - /** - * This is the visible code block - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - protected function getCodeBlock(array $data, string $code, int $index = 0): string - { - return $code; - } } diff --git a/src/Handlers/XmlHandler.php b/src/Handlers/XmlHandler.php index 97b5246..709f84b 100755 --- a/src/Handlers/XmlHandler.php +++ b/src/Handlers/XmlHandler.php @@ -10,6 +10,7 @@ namespace MaplePHP\Blunder\Handlers; use MaplePHP\Blunder\ExceptionItem; +use MaplePHP\Blunder\ExceptionMetadata; use MaplePHP\Blunder\Interfaces\HandlerInterface; use SimpleXMLElement; use Throwable; @@ -25,7 +26,8 @@ class XmlHandler extends AbstractHandler implements HandlerInterface */ public function exceptionHandler(Throwable $exception): void { - $trace = $this->getTrace($exception); + $meta = new ExceptionMetadata($exception); + $trace = $meta->getTrace(); $exceptionItem = new ExceptionItem($exception); $xml = new SimpleXMLElement(''); @@ -52,16 +54,4 @@ public function exceptionHandler(Throwable $exception): void $this->getHttp()->response()->getBody()->write($xmlOutput); $this->emitter($exception, $exceptionItem); } - - /** - * This is the visible code block - * @param array $data - * @param string $code - * @param int $index - * @return string - */ - protected function getCodeBlock(array $data, string $code, int $index = 0): string - { - return $code; - } } diff --git a/src/SeverityLevelPool.php b/src/SeverityLevelPool.php index 9008aae..b658a67 100755 --- a/src/SeverityLevelPool.php +++ b/src/SeverityLevelPool.php @@ -11,29 +11,11 @@ use Closure; use InvalidArgumentException; +use MaplePHP\Blunder\Enums\BlunderErrorType; use MaplePHP\Blunder\Interfaces\HandlerInterface; class SeverityLevelPool { - // List all supported error types - protected const SEVERITY_TYPES = [ - E_ERROR => 'E_ERROR', - E_WARNING => 'E_WARNING', - E_PARSE => 'E_PARSE', - E_NOTICE => 'E_NOTICE', - E_CORE_ERROR => 'E_CORE_ERROR', - E_CORE_WARNING => 'E_CORE_WARNING', - E_COMPILE_ERROR => 'E_COMPILE_ERROR', - E_COMPILE_WARNING => 'E_COMPILE_WARNING', - E_USER_ERROR => 'E_USER_ERROR', - E_USER_WARNING => 'E_USER_WARNING', - E_USER_NOTICE => 'E_USER_NOTICE', - E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', - E_DEPRECATED => 'E_DEPRECATED', - E_USER_DEPRECATED => 'E_USER_DEPRECATED', - E_ALL => 'E_ALL' - ]; - private array $allowedSeverityTypes = []; private array $removedSeverityTypes = []; private ?Closure $redirectCall = null; @@ -43,7 +25,7 @@ public function __construct(?array $allowedSeverityTypes = null) if(is_array($allowedSeverityTypes)) { $this->setSeverityLevels($allowedSeverityTypes); } else { - $this->allowedSeverityTypes = array_keys(self::SEVERITY_TYPES); + $this->allowedSeverityTypes = BlunderErrorType::getAllErrorLevels(); } } @@ -56,7 +38,8 @@ public function __construct(?array $allowedSeverityTypes = null) */ public static function getSeverityLevel(int $level, ?string $fallback = null): ?string { - return (self::SEVERITY_TYPES[$level] ?? $fallback); + $error = BlunderErrorType::fromErrorLevel($level)->getErrorLevelKey(); + return ($error !== "E_USER_ERROR") ? $error : $fallback; } /** @@ -66,7 +49,8 @@ public static function getSeverityLevel(int $level, ?string $fallback = null): ? */ public static function listAll(): array { - return self::SEVERITY_TYPES; + $list = BlunderErrorType::getAllErrorLevels(true); + return array_flip($list); } /** diff --git a/src/Templates/HtmlHelperTrait.php b/src/Templates/HtmlHelperTrait.php new file mode 100644 index 0000000..db5ce03 --- /dev/null +++ b/src/Templates/HtmlHelperTrait.php @@ -0,0 +1,228 @@ +'; + } + + /** + * This is the visible code block + * + * @param array $data + * @param string $code + * @param int $index + * @return string + */ + protected function getCodeBlock(array $data, string $code, int $index = 0): string + { + $class = (string)($data['class'] ?? ""); + $function = (string)($data['function'] ?? ""); + $functionName = ($function !== "") ? ' (' . $function . ')' : ''; + + return "
+
" . $class . $functionName . "
+
{$data['file']}
+
" . $code . "
+
"; + } + + /** + * This is a navigation item that will point to code block + * + * @param int $index + * @param int $length + * @param array $stack + * @return string + */ + protected function getNavBlock(int $index, int $length, array $stack): string + { + $active = ($index === 0) ? " active" : ""; + $class = (string)($stack['class'] ?? ""); + $function = (string)($stack['function'] ?? ""); + $functionName = ($function !== "") ? ' (' . $function . ')' : ''; + + return " + " . "" . ($length - $index) . ". $class . $functionName + " . ltrim((string)$stack['file'], "/") . ": {$stack['line']} + " + ; + } + + protected function getRows(string $title, ?array $rows): string + { + $out = '
'; + $out .= '

' . $title . '

'; + if(is_array($rows) && count($rows) > 0) { + foreach($rows as $key => $value) { + if(is_array($value)) { + $value = json_encode($value); + } + + $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + $value = $this->excerpt($value, 400); + + $out .= ''; + } + } else { + $out .= '
None
'; + } + $out .= '
'; + + return $out; + } + + /** + * This will add the navigation block html structure + * If you wish to edit the block then you should edit the "getNavBlock" method + * + * @param array $trace + * @return string + */ + protected function getTraceNavBlock(array $trace): string + { + $output = ""; + $length = count($trace); + foreach ($trace as $index => $stackPoint) { + if(is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { + $output .= $this->getNavBlock($index, $length, $stackPoint); + } + } + + return $output; + } + + /** + * Utilizing a "byte" excerpt + * Going for performance with byte calculation instead of precision with multibyte. + * + * @param string $value + * @param int $length + * @return string + */ + protected function excerpt(string $value, int $length): string + { + if(strlen($value) > $length) { + $value = trim(substr($value, 0, $length)) . "..."; + } + + return $value; + } + + /** + * Get code between start and end span from file + * + * @param StreamInterface $stream + * @param int $errorLine + * @param int $startSpan + * @param int $endSpan + * @return string + */ + protected function getContentsBetween(StreamInterface $stream, int $errorLine, int $startSpan = 10, int $endSpan = 12): string + { + $index = 1; + $output = ''; + $startLine = $errorLine - $startSpan; + $endLine = $errorLine + $endSpan; + if($startLine < 1) { + $startLine = 1; + } + while (!$stream->eof()) { + $line = $stream->read((int)$stream->getSize()); + $lines = explode("\n", $line); + foreach ($lines as $lineContent) { + if ($index >= $startLine && $index <= $endLine) { + $output .= ''; + $output .= ''. $index .''; + if($errorLine === $index) { + $output .= "" . htmlspecialchars($lineContent) . "\n"; + } else { + $output .= "" . htmlspecialchars($lineContent) . "\n"; + } + $output .= ''; + } + if ($index > $endLine) { + break; + } + $index++; + } + } + + return $output; + } + + /** + * Used to fetch valid asset + * + * @param string $file + * @return string + * @throws ErrorException + */ + public function getAssetContent(string $file): string + { + $ending = explode(".", $file); + $ending = end($ending); + + if(!($ending === "css" || $ending === "js")) { + throw new ErrorException("Only JS and CSS files are allowed as assets files"); + } + $filePath = (str_starts_with($file, "/") ? realpath($file) : realpath(__DIR__ . "/../") . "/" . $file); + $stream = $this->getStream($filePath); + + return $stream->getContents(); + } + + /** + * Will return the severity exception breadcrumb + * + * @param ExceptionMetadata $meta + * @return string + */ + public function getSeverityBreadcrumb(ExceptionMetadata $meta): string + { + $breadcrumb = get_class($meta->getException()); + $severityConstant = $meta->getSeverityConstant(); + if(!is_null($severityConstant)) { + $breadcrumb .= " ($severityConstant)"; + } + + return "
$breadcrumb
"; + } + + + /** + * This will add the code block structure + * If you wish to edit the block then you should edit the "getCodeBlock" method + * + * @param array $trace + * @return array + */ + final protected function getTraceCodeBlock(array $trace): array + { + $block = []; + foreach ($trace as $key => $stackPoint) { + if(is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { + $stream = $this->getStream($stackPoint['file']); + $code = $this->getContentsBetween($stream, (int)$stackPoint['line']); + $block[] = $this->getCodeBlock($stackPoint, $code, $key); + $stream->close(); + } + } + + return $block; + } +} \ No newline at end of file diff --git a/tests/unitary-blunder-handlers.php b/tests/unitary-blunder-handlers.php index 449cc4a..f89165d 100755 --- a/tests/unitary-blunder-handlers.php +++ b/tests/unitary-blunder-handlers.php @@ -4,8 +4,6 @@ * This is how a template test file should look like but * when used in MaplePHP framework you can skip the "bash code" at top and the "autoload file"! */ - -use MaplePHP\Blunder\ExceptionItem; use MaplePHP\Blunder\Handlers\CliHandler; use MaplePHP\Blunder\Handlers\HtmlHandler; use MaplePHP\Blunder\Handlers\JsonHandler; @@ -13,15 +11,9 @@ use MaplePHP\Blunder\Handlers\SilentHandler; use MaplePHP\Blunder\Handlers\TextHandler; use MaplePHP\Blunder\Handlers\XmlHandler; -use MaplePHP\Blunder\Interfaces\HandlerInterface; use MaplePHP\Blunder\Run; -use MaplePHP\Unitary\TestWrapper; use MaplePHP\Unitary\Unit; -// If you add true to Unit it will run in quite mode -// and only report if it finds any errors! - - $unit = new Unit(); $unit->case("MaplePHP Blunder handler test", function ($inst) { @@ -42,7 +34,7 @@ return ob_get_clean(); }); return $dispatch(new Exception("Mock exception")); - };; + }; $inst->add($func(HtmlHandler::class), [ 'length' => 100, @@ -56,15 +48,15 @@ $inst->add($func(TextHandler::class), [ 'length' => [10], - ], "TextHandler do not return a valid CLI string"); + ], "TextHandler do not return a valid string"); $inst->add($func(PlainTextHandler::class), [ 'length' => [10], - ], "PlainTextHandler do not return a valid CLI string"); + ], "PlainTextHandler do not return a valid string"); $inst->add($func(XmlHandler::class), [ 'length' => [10], - ], "CliHandler do not return a valid CLI string"); + ], "XmlHandler do not return a valid XML string"); $inst->add($func(CliHandler::class), [ 'length' => [1], From 8c32e265a44bfd3a2e8200a90fba4835f065b3de Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sat, 12 Apr 2025 13:27:10 +0200 Subject: [PATCH 02/14] Organize files, code, and project structure for better clarity and maintainability --- src/BlunderErrorException.php | 18 ++- src/Enums/BlunderErrorType.php | 16 +++ src/ExceptionItem.php | 133 ++++++++++++++++-- src/ExceptionMetadata.php | 146 -------------------- src/Handlers/AbstractHandler.php | 108 ++++++++------- src/Handlers/CliHandler.php | 34 +++-- src/Handlers/HtmlHandler.php | 53 ++++--- src/Handlers/JsonHandler.php | 25 ++-- src/Handlers/PlainTextHandler.php | 25 ++-- src/Handlers/SilentHandler.php | 27 ++-- src/Handlers/TextHandler.php | 33 +++-- src/Handlers/XmlHandler.php | 25 ++-- src/HttpMessaging.php | 18 ++- src/Interfaces/AbstractHandlerInterface.php | 17 ++- src/Interfaces/HandlerInterface.php | 16 ++- src/Interfaces/HttpMessagingInterface.php | 17 ++- src/Run.php | 18 ++- src/SeverityLevelPool.php | 18 ++- src/Templates/HtmlHelperTrait.php | 44 +++++- src/main.css | 6 +- 20 files changed, 482 insertions(+), 315 deletions(-) delete mode 100644 src/ExceptionMetadata.php diff --git a/src/BlunderErrorException.php b/src/BlunderErrorException.php index 1019826..22762a8 100644 --- a/src/BlunderErrorException.php +++ b/src/BlunderErrorException.php @@ -1,5 +1,21 @@ exception = $exception; + $this->severityError = BlunderErrorType::fromErrorLevel(1); $this->flag = (method_exists($exception, "getSeverity")) ? $exception->getSeverity() : 0; if(is_null($pool)) { $pool = new SeverityLevelPool(); @@ -38,6 +53,7 @@ public function __toString(): string /** * Access the exception + * * @param string $name * @param array $args * @return mixed @@ -45,9 +61,8 @@ public function __toString(): string public function __call(string $name, array $args): mixed { if(!method_exists($this->exception, $name)) { - throw new \BadMethodCallException("Method '$name' does not exist in Throwable class"); + throw new BadMethodCallException("Method '$name' does not exist in Throwable class"); } - return $this->exception->{$name}(...$args); } @@ -60,6 +75,16 @@ public function getException(): Throwable return $this->exception; } + /** + * Get Severity Pool + * + * @return SeverityLevelPool + */ + public function getSeverityPool(): SeverityLevelPool + { + return $this->pool; + } + /** * Get exception type * @return string @@ -127,7 +152,99 @@ public function getStatus(): string } /** - * Check if error is an fatal error + * Will return an expected severity type as enum + * + * @return BlunderErrorType + */ + public function getSeverityError(): BlunderErrorType + { + $this->severityError = BlunderErrorType::fromErrorLevel(1); + if ($this->exception instanceof ErrorException) { + $this->severityError = BlunderErrorType::fromErrorLevel($this->exception->getSeverity()); + } + return $this->severityError; + } + + /** + * Get severity flag title + * + * @return string|null + */ + public function getSeverityConstant(): ?string + { + return $this->getSeverityError()->getErrorLevelKey(); + } + + /** + * Create title from exception severity + * + * @return string|null + */ + public function getSeverityTitle(): ?string + { + return $this->getSeverityError()->getErrorLevelTitle(); + } + + + /** + * Get trace line with filtered arguments and max length + * + * @param int $maxTraceLevel + * @return array + */ + public function getTrace(int $maxTraceLevel = 0): array + { + $new = []; + $trace = $this->exception->getTrace(); + + $mainErrorClass = get_class($this->exception); + + // This will also place the main error to trace list + array_unshift($trace, $this->pollyFillException([ + 'file' => $this->exception->getFile(), + 'line' => $this->exception->getLine(), + 'class' => get_class($this->exception) + ])); + + + + foreach ($trace as $key => $stackPoint) { + + $class = ($stackPoint['class'] ?? ""); + $blunderErrorClass = "MaplePHP\Blunder\Handlers\AbstractHandler"; + + if($mainErrorClass !== $blunderErrorClass && $class !== $blunderErrorClass) { + $new[$key] = $stackPoint; + $new[$key]['args'] = array_map('gettype', (array)($new[$key]['args'] ?? [])); + if($maxTraceLevel > 0 && $key >= ($maxTraceLevel - 1)) { + break; + } + } + } + return $new; + } + + /** + * Get an exception array with right items + * + * @param array $arr + * @return array + */ + public function pollyFillException(array $arr): array + { + return array_merge([ + 'file' => "", + 'line' => "", + 'class' => "", + 'function' => null, + 'type' => null, + 'args' => [] + ], $arr); + } + + /** + * Check if error is a fatal error + * * @return bool */ final public function isLevelFatal(): bool diff --git a/src/ExceptionMetadata.php b/src/ExceptionMetadata.php deleted file mode 100644 index c7971a7..0000000 --- a/src/ExceptionMetadata.php +++ /dev/null @@ -1,146 +0,0 @@ -exception = $exception; - $this->severityError = BlunderErrorType::fromErrorLevel(1); - } - - /** - * Set a max trace level - * - * @param int $maxTraceLevel - * @return $this - */ - public function setMaxTraceLevel(int $maxTraceLevel): self - { - $this->maxTraceLevel = $maxTraceLevel; - return $this; - } - - /** - * Disable trace levels, (is enabled by default) - * - * @param bool $disableTraceLevel - * @return $this - */ - public function disableTraceLevel(bool $disableTraceLevel = true): self - { - $this->enableTraceLevel = $disableTraceLevel; - return $this; - } - - /** - * Get the max trace level - * - * @return int - */ - protected function getMaxTraceLevel(): int - { - return !is_null($this->maxTraceLevel) ? $this->maxTraceLevel : static::MAX_TRACE_LEVEL; - } - - /** - * Get current exception - * - * @return Throwable - */ - public function getException(): throwable - { - return $this->exception; - } - - /** - * Will return a expected severity type as enum - * - * @return BlunderErrorType - */ - public function getSeverityError(): BlunderErrorType - { - $this->severityError = BlunderErrorType::fromErrorLevel(1); - if ($this->exception instanceof ErrorException) { - $this->severityError = BlunderErrorType::fromErrorLevel($this->exception->getSeverity()); - } - return $this->severityError; - } - - /** - * Get severity flag title - * - * @return string|null - */ - public function getSeverityConstant(): ?string - { - return $this->getSeverityError()->getErrorLevelKey(); - } - - /** - * Create title from exception severity - * - * @return string|null - */ - public function getSeverityTitle(): ?string - { - return $this->getSeverityError()->getErrorLevelTitle(); - } - - - /** - * Get trace line with filtered arguments and max length - * - * @return array - */ - public function getTrace(): array - { - $new = []; - $trace = $this->exception->getTrace(); - - array_unshift($trace, $this->pollyFillException([ - 'file' => $this->exception->getFile(), - 'line' => $this->exception->getLine(), - 'class' => get_class($this->exception) - ])); - - foreach ($trace as $key => $stackPoint) { - $new[$key] = $stackPoint; - $new[$key]['args'] = array_map('gettype', (array)($new[$key]['args'] ?? [])); - if($key >= ($this->getMaxTraceLevel() - 1) || !$this->enableTraceLevel) { - break; - } - } - return $new; - } - - /** - * Get an exception array with right items - * - * @param array $arr - * @return array - */ - public function pollyFillException(array $arr): array - { - return array_merge([ - 'file' => "", - 'line' => "", - 'class' => "", - 'function' => null, - 'type' => null, - 'args' => [] - ], $arr); - } - -} \ No newline at end of file diff --git a/src/Handlers/AbstractHandler.php b/src/Handlers/AbstractHandler.php index 765e10d..fdebeb6 100755 --- a/src/Handlers/AbstractHandler.php +++ b/src/Handlers/AbstractHandler.php @@ -1,27 +1,35 @@ http; } + /** + * Will get valid stream + * + * @param mixed|null $stream + * @param string $permission + * @return StreamInterface + */ + final protected function getStream(mixed $stream = null, string $permission = "r+"): StreamInterface + { + if(is_null($this->http)) { + throw new BadMethodCallException("You Must initialize the stream before calling this method"); + } + return $this->http->stream($stream, $permission); + } + + /** + * Get exception if has been initiated + * + * @return Throwable|null + */ + public function getException(): ?Throwable + { + return $this->exception; + } + /** * Set expected severity mask * @param SeverityLevelPool $severity @@ -143,15 +187,6 @@ public function errorHandler(int $errNo, string $errStr, string $errFile, int $e return false; } - /** - * Get exception if has been initiated - * @return Throwable|null - */ - public function getException(): ?Throwable - { - return $this->exception; - } - /** * Handle the errorHandler or redirect to PHP error to a new handler or * @@ -198,6 +233,7 @@ public function shutdownHandler(): void if($error) { $item = new ExceptionItem(new ErrorException()); if ($item->isLevelFatal() && ($error['type'] & $this->severity) !== 0) { + $this->errorHandler( $error['type'], $error['message'], @@ -212,28 +248,22 @@ public function shutdownHandler(): void /** * Emit response * - * @param Throwable $exception - * @param ExceptionItem|null $exceptionItem + * @param ExceptionItem $exceptionItem * @return void */ - protected function emitter(throwable $exception, ?ExceptionItem $exceptionItem = null): void + protected function emitter(ExceptionItem $exceptionItem): void { //$this->cleanOutputBuffers(); - if (!headers_sent()) { header_remove('location'); header('HTTP/1.1 500 Internal Server Error'); } - $response = $this->getHttp()->response()->withoutHeader('location'); $response->createHeaders(); $response->executeHeaders(); $stream = $response->getBody(); if(is_callable($this->eventCallable)) { - if(is_null($exceptionItem)) { - $exceptionItem = new ExceptionItem($exception); - } call_user_func_array($this->eventCallable, [$exceptionItem, $this->http]); } $stream->rewind(); @@ -242,7 +272,7 @@ protected function emitter(throwable $exception, ?ExceptionItem $exceptionItem = } /** - * Will send a exit code if specified + * Will send an exit code if specified * * @return void */ @@ -256,10 +286,10 @@ protected function sendExitCode(): void /** * Generate error message (placeholder) * - * @param Throwable $exception + * @param ExceptionItem|Throwable $exception * @return string */ - protected function getErrorMessage(Throwable $exception): string + protected function getErrorMessage(ExceptionItem|Throwable $exception): string { return ""; } @@ -277,22 +307,4 @@ final public function cleanOutputBuffers(): void } } } - - /** - * Will get valid stream - * - * @param mixed|null $stream - * @param string $permission - * @return StreamInterface - */ - final protected function getStream(mixed $stream = null, string $permission = "r+"): StreamInterface - { - if(is_null($this->http)) { - throw new \BadMethodCallException("You Must initialize the stream before calling this method"); - } - - return $this->http->stream($stream, $permission); - } - - } diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index d4256b5..ffef95f 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -1,15 +1,24 @@ getHttp()->response()->getBody()->write($this->getErrorMessage($exception)); - $this->emitter($exception); + $exceptionItem = new ExceptionItem($exception); + $this->getHttp()->response()->getBody()->write($this->getErrorMessage($exceptionItem)); + $this->emitter($exceptionItem); } /** * Generate error message - * @param Throwable $exception + * + * @param ExceptionItem|Throwable $exception * @return string */ - protected function getErrorMessage(Throwable $exception): string + protected function getErrorMessage(ExceptionItem|Throwable $exception): string { - $meta = new ExceptionMetadata($exception); - $msg = "\n"; $msg .= self::ansi()->red("%s ") . self::ansi()->italic("(%s)") . ": "; $msg .= self::ansi()->bold("%s ") . " \n\n"; @@ -49,7 +59,7 @@ protected function getErrorMessage(Throwable $exception): string $result = []; if(self::$enabledTraceLines) { - $trace = $meta->getTrace(); + $trace = $exception->getTrace($this->getMaxTraceLevel()); $result = $this->getTraceResult($trace); $msg .= self::ansi()->bold("Stack trace:") . "\n"; $msg .= "%s\n"; diff --git a/src/Handlers/HtmlHandler.php b/src/Handlers/HtmlHandler.php index bcb7cae..7953be0 100755 --- a/src/Handlers/HtmlHandler.php +++ b/src/Handlers/HtmlHandler.php @@ -1,16 +1,24 @@ getHttp()->response()->getBody()->write($this->document($exception)); - $this->emitter($exception); + $exceptionItem = new ExceptionItem($exception); + $this->getHttp()->response()->getBody()->write($this->document($exceptionItem)); + $this->emitter($exceptionItem); } /** * The pretty output template * - * @param Throwable $exception + * @param ExceptionItem $exception * @return string * @throws ErrorException */ - protected function document(Throwable $exception): string + protected function document(ExceptionItem $exception): string { - $meta = new ExceptionMetadata($exception); - $meta->setMaxTraceLevel(static::$enabledTraceLines); - $trace = $meta->getTrace(); + $trace = $exception->getTrace($this->getMaxTraceLevel()); $codeBlockArr = $this->getTraceCodeBlock($trace); $port = $this->getHttp()->request()->getUri()->getPort(); if(is_null($port)) { @@ -71,22 +78,13 @@ protected function document(Throwable $exception): string
-
+
diff --git a/src/Handlers/JsonHandler.php b/src/Handlers/JsonHandler.php index 2622ff8..4f6886e 100755 --- a/src/Handlers/JsonHandler.php +++ b/src/Handlers/JsonHandler.php @@ -1,16 +1,24 @@ getTrace(); - $exceptionItem = new ExceptionItem($exception); + $trace = $exceptionItem->getTrace($this->getMaxTraceLevel()); + $this->getHttp()->response()->getBody()->write(json_encode([ "status" => $exceptionItem->getStatus(), "message" => $exception->getMessage(), @@ -39,6 +46,6 @@ public function exceptionHandler(Throwable $exception): void "trace" => $trace, ])); $this->getHttp()->response()->withHeader('content-type', 'application/json; charset=utf-8'); - $this->emitter($exception, $exceptionItem); + $this->emitter($exceptionItem); } } diff --git a/src/Handlers/PlainTextHandler.php b/src/Handlers/PlainTextHandler.php index 8860245..41311db 100755 --- a/src/Handlers/PlainTextHandler.php +++ b/src/Handlers/PlainTextHandler.php @@ -1,21 +1,29 @@ getHttp()->response()->getBody()->write(strip_tags($this->getErrorMessage($exception))); - $this->emitter($exception); + $exceptionItem = new ExceptionItem($exception); + $this->getHttp()->response()->getBody()->write(strip_tags($this->getErrorMessage($exceptionItem))); + $this->emitter($exceptionItem); } } diff --git a/src/Handlers/SilentHandler.php b/src/Handlers/SilentHandler.php index ad1de8c..08a4b3b 100755 --- a/src/Handlers/SilentHandler.php +++ b/src/Handlers/SilentHandler.php @@ -1,15 +1,24 @@ showFatalErrors && ($item->isLevelFatal() || $item->getStatus() === "error")) { + $exceptionItem = new ExceptionItem($exception); + if($this->showFatalErrors && ($exceptionItem->isLevelFatal() || $exceptionItem->getStatus() === "error")) { // Event is trigger inside "exceptionHandler". parent::exceptionHandler($exception); } else { if(is_callable($this->eventCallable)) { - call_user_func_array($this->eventCallable, [$item, $this->http]); + call_user_func_array($this->eventCallable, [$exceptionItem, $this->http]); } } } diff --git a/src/Handlers/TextHandler.php b/src/Handlers/TextHandler.php index 1e9c57c..7efbfa8 100755 --- a/src/Handlers/TextHandler.php +++ b/src/Handlers/TextHandler.php @@ -1,15 +1,24 @@ getHttp()->response()->getBody()->write("
" . $this->getErrorMessage($exception) . "
"); - $this->emitter($exception); + $exceptionItem = new ExceptionItem($exception); + $this->getHttp()->response()->getBody()->write("
" . $this->getErrorMessage($exceptionItem) . "
"); + $this->emitter($exceptionItem); } /** * Generate error message - * @param Throwable $exception + * + * @param ExceptionItem|Throwable $exception * @return string */ - protected function getErrorMessage(Throwable $exception): string + protected function getErrorMessage(ExceptionItem|Throwable $exception): string { - $meta = new ExceptionMetadata($exception); $traceLine = "#%s %s(%s): %s(%s)"; $msg = "PHP Fatal error: Uncaught exception '%s (%s)' with message '%s' in %s:%s\nStack trace:\n%s\n thrown in %s on line %s"; $key = 0; $result = []; - $trace = $meta->getTrace(); + $trace = $exception->getTrace($this->getMaxTraceLevel()); $severityLevel = (method_exists($exception, "getSeverity") ? $exception->getSeverity() : 0); foreach ($trace as $key => $stackPoint) { diff --git a/src/Handlers/XmlHandler.php b/src/Handlers/XmlHandler.php index 709f84b..ee3dba3 100755 --- a/src/Handlers/XmlHandler.php +++ b/src/Handlers/XmlHandler.php @@ -1,16 +1,23 @@ getTrace(); + $exceptionItem = new ExceptionItem($exception); + $trace = $exceptionItem->getTrace($this->getMaxTraceLevel()); $exceptionItem = new ExceptionItem($exception); $xml = new SimpleXMLElement(''); $xml->addChild('status', $exceptionItem->getStatus()); - $xml->addChild('status', $exception->getMessage()); + $xml->addChild('message', $exception->getMessage()); $xml->addChild('flag', $exceptionItem->getSeverity()); $xml->addChild('file', $exception->getFile()); $xml->addChild('line', (string)$exception->getLine()); @@ -52,6 +59,6 @@ public function exceptionHandler(Throwable $exception): void $xmlOutput = (string)$xml->asXML(); $this->getHttp()->response()->withHeader('content-type', 'application/xml; charset=utf-8'); $this->getHttp()->response()->getBody()->write($xmlOutput); - $this->emitter($exception, $exceptionItem); + $this->emitter($exceptionItem); } } diff --git a/src/HttpMessaging.php b/src/HttpMessaging.php index 9228cf1..fdc055d 100755 --- a/src/HttpMessaging.php +++ b/src/HttpMessaging.php @@ -1,12 +1,22 @@ '; + + /** + * Set a custom logo for HTML Handler + * + * @param string $logoHtml + * @return HtmlHandler|HtmlHelperTrait + */ + protected function setLogo(string $logoHtml): self + { + $this->logo = $logoHtml; + return $this; + } + /** * This will return the MaplePHP logotype * @@ -14,7 +46,7 @@ trait HtmlHelperTrait { */ protected function getLogo(): string { - return ''; + return $this->logo; } /** @@ -149,7 +181,7 @@ protected function getContentsBetween(StreamInterface $stream, int $errorLine, i $output .= ''; $output .= ''. $index .''; if($errorLine === $index) { - $output .= "" . htmlspecialchars($lineContent) . "\n"; + $output .= "" . htmlspecialchars($lineContent) . "\n"; } else { $output .= "" . htmlspecialchars($lineContent) . "\n"; } @@ -189,10 +221,10 @@ public function getAssetContent(string $file): string /** * Will return the severity exception breadcrumb * - * @param ExceptionMetadata $meta + * @param ExceptionItem $meta * @return string */ - public function getSeverityBreadcrumb(ExceptionMetadata $meta): string + public function getSeverityBreadcrumb(ExceptionItem $meta): string { $breadcrumb = get_class($meta->getException()); $severityConstant = $meta->getSeverityConstant(); @@ -203,7 +235,6 @@ public function getSeverityBreadcrumb(ExceptionMetadata $meta): string return "
$breadcrumb
"; } - /** * This will add the code block structure * If you wish to edit the block then you should edit the "getCodeBlock" method @@ -222,7 +253,6 @@ final protected function getTraceCodeBlock(array $trace): array $stream->close(); } } - return $block; } } \ No newline at end of file diff --git a/src/main.css b/src/main.css index f2e903a..030efe7 100755 --- a/src/main.css +++ b/src/main.css @@ -124,6 +124,10 @@ main, .flex { display: flex; } +main { + flex-direction: row-reverse; +} + .items-center { align-items: center; } @@ -248,7 +252,7 @@ main > article { z-index: 3; } -.code-block, .hide { display: none; } +.code-block, .hide, .d-none { display: none; } .show { display: block; } From 392003e7e1b841f2d6fe04774428df377769eba4 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Mon, 14 Apr 2025 19:56:33 +0200 Subject: [PATCH 03/14] Update README.md --- README.md | 2 +- src/Handlers/AbstractHandler.php | 1 - tests/unitary-blunder-handlers.php | 2 +- tests/unitary-blunder-redirect.php | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 602b335..fcc90ee 100755 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Instead of letting PHP handle the excluded severities, you can redirect them to ```php $run = new Run(new HtmlHandler()); $run->severity() - ->excludeSeverityLevels([E_WARNING, E_USER_WARNING]) + ->excludeSeverityLevels([E_DEPRECATED, E_USER_DEPRECATED]) ->redirectTo(function ($errNo, $errStr, $errFile, $errLine) { error_log("Custom log: $errStr in $errFile on line $errLine"); return true; // Suppresses output for excluded severities diff --git a/src/Handlers/AbstractHandler.php b/src/Handlers/AbstractHandler.php index fdebeb6..593dbf0 100755 --- a/src/Handlers/AbstractHandler.php +++ b/src/Handlers/AbstractHandler.php @@ -233,7 +233,6 @@ public function shutdownHandler(): void if($error) { $item = new ExceptionItem(new ErrorException()); if ($item->isLevelFatal() && ($error['type'] & $this->severity) !== 0) { - $this->errorHandler( $error['type'], $error['message'], diff --git a/tests/unitary-blunder-handlers.php b/tests/unitary-blunder-handlers.php index f89165d..02ee0c9 100755 --- a/tests/unitary-blunder-handlers.php +++ b/tests/unitary-blunder-handlers.php @@ -27,7 +27,7 @@ ->redirectTo(function ($errNo, $errStr, $errFile, $errLine) use ($inst) { $func = function (string $className) { - $dispatch = $this->wrapper($className)->bind(function ($exception) { + $dispatch = $this->wrap($className)->bind(function ($exception) { $this->setExitCode(null); ob_start(); $this->exceptionHandler($exception); diff --git a/tests/unitary-blunder-redirect.php b/tests/unitary-blunder-redirect.php index 7199bb7..ef04141 100755 --- a/tests/unitary-blunder-redirect.php +++ b/tests/unitary-blunder-redirect.php @@ -18,6 +18,7 @@ // SilentHandler will hide the error that I have added in this file // and is using to test the Blunder library + $run = new Run(new SilentHandler()); $run->severity() ->excludeSeverityLevels([E_WARNING, E_USER_WARNING]) From 5a624bf586d4125133255ee9fcf00b860a9e5254 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sun, 27 Apr 2025 21:12:49 +0200 Subject: [PATCH 04/14] Improve comment blocks in ExceptionItem class --- src/ExceptionItem.php | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/ExceptionItem.php b/src/ExceptionItem.php index b5f9224..e8139bb 100755 --- a/src/ExceptionItem.php +++ b/src/ExceptionItem.php @@ -43,7 +43,8 @@ public function __construct(Throwable $exception, ?SeverityLevelPool $pool = nul } /** - * Will return error message + * Will return an error message + * * @return string */ public function __toString(): string @@ -68,6 +69,7 @@ public function __call(string $name, array $args): mixed /** * Get Exception + * * @return Throwable */ public function getException(): Throwable @@ -86,7 +88,8 @@ public function getSeverityPool(): SeverityLevelPool } /** - * Get exception type + * Get an exception type + * * @return string */ public function getType(): string @@ -96,6 +99,7 @@ public function getType(): string /** * Delete a severity level + * * @return bool */ public function deleteSeverity(): bool @@ -104,7 +108,8 @@ public function deleteSeverity(): bool } /** - * Check if error type is supported + * Check if an error type is supported + * * @return bool */ public function hasSeverity(): bool @@ -113,7 +118,8 @@ public function hasSeverity(): bool } /** - * Get severity level title name if severity is callable else will return exception type + * Get severity level title name if severity is callable else will return an exception type + * * @return string|null */ public function getSeverity(): ?string @@ -137,6 +143,7 @@ public function getMask(): int /** * This will return a status for severity flag that will follow and return a * status title that follows PSR-3 log for easily logging errors + * * @return string */ public function getStatus(): string @@ -176,7 +183,7 @@ public function getSeverityConstant(): ?string } /** - * Create title from exception severity + * Create a title from exception severity * * @return string|null */ @@ -187,7 +194,7 @@ public function getSeverityTitle(): ?string /** - * Get trace line with filtered arguments and max length + * Get a trace line with filtered arguments and max length * * @param int $maxTraceLevel * @return array @@ -196,20 +203,16 @@ public function getTrace(int $maxTraceLevel = 0): array { $new = []; $trace = $this->exception->getTrace(); - $mainErrorClass = get_class($this->exception); - // This will also place the main error to trace list + // This will also place the main error to trace a list array_unshift($trace, $this->pollyFillException([ 'file' => $this->exception->getFile(), 'line' => $this->exception->getLine(), 'class' => get_class($this->exception) ])); - - foreach ($trace as $key => $stackPoint) { - $class = ($stackPoint['class'] ?? ""); $blunderErrorClass = "MaplePHP\Blunder\Handlers\AbstractHandler"; @@ -225,7 +228,7 @@ public function getTrace(int $maxTraceLevel = 0): array } /** - * Get an exception array with right items + * Get an exception array with the right items * * @param array $arr * @return array @@ -243,7 +246,7 @@ public function pollyFillException(array $arr): array } /** - * Check if error is a fatal error + * Check if the error is a fatal error * * @return bool */ From b0c52d67c1800eb195cfceee79e7041946541db8 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 30 Apr 2025 23:21:38 +0200 Subject: [PATCH 05/14] Show the expected exception class name --- src/Handlers/CliHandler.php | 6 ++++-- src/Handlers/TextHandler.php | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index ffef95f..179a1be 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -50,7 +50,9 @@ public function exceptionHandler(Throwable $exception): void */ protected function getErrorMessage(ExceptionItem|Throwable $exception): string { - + if($exception instanceof Throwable) { + $exception = new ExceptionItem($exception); + } $msg = "\n"; $msg .= self::ansi()->red("%s ") . self::ansi()->italic("(%s)") . ": "; $msg .= self::ansi()->bold("%s ") . " \n\n"; @@ -70,7 +72,7 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string return sprintf( $msg, - get_class($exception), + get_class($exception->getException()), (string)SeverityLevelPool::getSeverityLevel((int)$severityLevel, "Error"), $message, $exception->getFile(), diff --git a/src/Handlers/TextHandler.php b/src/Handlers/TextHandler.php index 7efbfa8..17abd5c 100755 --- a/src/Handlers/TextHandler.php +++ b/src/Handlers/TextHandler.php @@ -48,6 +48,10 @@ public function exceptionHandler(Throwable $exception): void */ protected function getErrorMessage(ExceptionItem|Throwable $exception): string { + if($exception instanceof Throwable) { + $exception = new ExceptionItem($exception); + } + $traceLine = "#%s %s(%s): %s(%s)"; $msg = "PHP Fatal error: Uncaught exception '%s (%s)' with message '%s' in %s:%s\nStack trace:\n%s\n thrown in %s on line %s"; @@ -75,7 +79,7 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string // write trace-lines into main template return sprintf( $msg, - get_class($exception), + get_class($exception->getException()), (string)SeverityLevelPool::getSeverityLevel((int)$severityLevel, "Error"), $exception->getMessage(), $exception->getFile(), From 5620c5ee5c799f4839d1f08d6f7740709408d276 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Thu, 1 May 2025 22:33:46 +0200 Subject: [PATCH 06/14] Quality code improvements --- src/BlunderErrorException.php | 2 +- src/Enums/BlunderErrorType.php | 6 ++-- src/ExceptionItem.php | 24 +++++++++------- src/Handlers/AbstractHandler.php | 31 ++++++++++---------- src/Handlers/CliHandler.php | 14 ++++----- src/Handlers/HtmlHandler.php | 4 +-- src/Handlers/JsonHandler.php | 13 ++++++--- src/Handlers/PlainTextHandler.php | 2 +- src/Handlers/SilentHandler.php | 6 ++-- src/Handlers/TextHandler.php | 6 ++-- src/Handlers/XmlHandler.php | 8 +++--- src/HttpMessaging.php | 9 +++--- src/Interfaces/AbstractHandlerInterface.php | 8 ++++++ src/Interfaces/HandlerInterface.php | 3 +- src/Run.php | 7 ++--- src/SeverityLevelPool.php | 28 +++++++++--------- src/Templates/HtmlHelperTrait.php | 32 ++++++++++----------- 17 files changed, 111 insertions(+), 92 deletions(-) diff --git a/src/BlunderErrorException.php b/src/BlunderErrorException.php index 22762a8..c0f51f0 100644 --- a/src/BlunderErrorException.php +++ b/src/BlunderErrorException.php @@ -23,7 +23,7 @@ use ReflectionClass; use Throwable; -class BlunderErrorException extends ErrorException +final class BlunderErrorException extends ErrorException { protected ?string $prettyMessage = null; diff --git a/src/Enums/BlunderErrorType.php b/src/Enums/BlunderErrorType.php index 4d420fc..de0d57e 100644 --- a/src/Enums/BlunderErrorType.php +++ b/src/Enums/BlunderErrorType.php @@ -60,7 +60,7 @@ enum BlunderErrorType 'E_ALL' => [E_ALL, 'E_ALL', 'All'], ]; - + /** * Retrieve all PHP constant values that are mapped, either preserving the original keys or as a flat list. * @@ -72,7 +72,7 @@ public static function getAllErrorLevels(bool $preserveKeys = false): array $items = self::MAP; array_shift($items); $arr = array_map(fn ($item) => $item[0], $items); - if($preserveKeys) { + if ($preserveKeys) { return $arr; } return array_values($arr); @@ -88,7 +88,7 @@ public function getErrorLevel(): int return self::MAP[$this->name][0]; } - + /** * Get the PHP constant key (name) associated with this error type. * diff --git a/src/ExceptionItem.php b/src/ExceptionItem.php index e8139bb..960a03d 100755 --- a/src/ExceptionItem.php +++ b/src/ExceptionItem.php @@ -1,4 +1,5 @@ exception = $exception; $this->severityError = BlunderErrorType::fromErrorLevel(1); $this->flag = (method_exists($exception, "getSeverity")) ? $exception->getSeverity() : 0; - if(is_null($pool)) { + if (is_null($pool)) { $pool = new SeverityLevelPool(); } $this->pool = $pool; @@ -61,7 +64,7 @@ public function __toString(): string */ public function __call(string $name, array $args): mixed { - if(!method_exists($this->exception, $name)) { + if (!method_exists($this->exception, $name)) { throw new BadMethodCallException("Method '$name' does not exist in Throwable class"); } return $this->exception->{$name}(...$args); @@ -124,7 +127,7 @@ public function hasSeverity(): bool */ public function getSeverity(): ?string { - if($this->flag === 0) { + if ($this->flag === 0) { return $this->getType(); } @@ -185,9 +188,9 @@ public function getSeverityConstant(): ?string /** * Create a title from exception severity * - * @return string|null + * @return string */ - public function getSeverityTitle(): ?string + public function getSeverityTitle(): string { return $this->getSeverityError()->getErrorLevelTitle(); } @@ -213,13 +216,14 @@ public function getTrace(int $maxTraceLevel = 0): array ])); foreach ($trace as $key => $stackPoint) { - $class = ($stackPoint['class'] ?? ""); + $class = isset($stackPoint['class']) ? (string) $stackPoint['class'] : ""; $blunderErrorClass = "MaplePHP\Blunder\Handlers\AbstractHandler"; - if($mainErrorClass !== $blunderErrorClass && $class !== $blunderErrorClass) { + /** @psalm-suppress RedundantCondition */ + if ($mainErrorClass !== $blunderErrorClass && $class !== $blunderErrorClass) { $new[$key] = $stackPoint; $new[$key]['args'] = array_map('gettype', (array)($new[$key]['args'] ?? [])); - if($maxTraceLevel > 0 && $key >= ($maxTraceLevel - 1)) { + if ($maxTraceLevel > 0 && $key >= ($maxTraceLevel - 1)) { break; } } diff --git a/src/Handlers/AbstractHandler.php b/src/Handlers/AbstractHandler.php index 593dbf0..2498081 100755 --- a/src/Handlers/AbstractHandler.php +++ b/src/Handlers/AbstractHandler.php @@ -127,7 +127,7 @@ public function getHttp(): HttpMessagingInterface */ final protected function getStream(mixed $stream = null, string $permission = "r+"): StreamInterface { - if(is_null($this->http)) { + if (is_null($this->http)) { throw new BadMethodCallException("You Must initialize the stream before calling this method"); } return $this->http->stream($stream, $permission); @@ -171,7 +171,7 @@ public function errorHandler(int $errNo, string $errStr, string $errFile, int $e if ($errNo & error_reporting()) { // Redirect to PHP error $redirectHandler = $this->redirectExceptionHandler($errNo, $errStr, $errFile, $errLine, $context); - if(!is_null($redirectHandler)) { + if (!is_null($redirectHandler)) { return $redirectHandler; } $this->cleanOutputBuffers(); @@ -204,18 +204,19 @@ public function redirectExceptionHandler( string $errFile, int $errLine = 0, array $context = [] - ): null|bool - { - if ($this->severityLevelPool->hasRemovedSeverity($errNo)) { + ): null|bool { + if ($this->severityLevelPool && $this->severityLevelPool->hasRemovedSeverity($errNo)) { $redirectCall = $this->severityLevelPool->getRedirectCall(); - $ret = $redirectCall($errNo, $errStr, $errFile, $errLine, $context); - if(!is_null($ret)) { - if ($ret instanceof HandlerInterface) { - $exception = new BlunderErrorException($errStr, 0, $errNo, $errFile, $errLine); - $ret->exceptionHandler($exception); - exit; + if (!is_null($redirectCall)) { + $ret = $redirectCall($errNo, $errStr, $errFile, $errLine, $context); + if (!is_null($ret)) { + if ($ret instanceof HandlerInterface) { + $exception = new BlunderErrorException($errStr, 0, $errNo, $errFile, $errLine); + $ret->exceptionHandler($exception); + exit; + } + return !!$ret; } - return $ret; } } return null; @@ -230,7 +231,7 @@ public function shutdownHandler(): void { $this->throwException = false; $error = error_get_last(); - if($error) { + if ($error) { $item = new ExceptionItem(new ErrorException()); if ($item->isLevelFatal() && ($error['type'] & $this->severity) !== 0) { $this->errorHandler( @@ -262,7 +263,7 @@ protected function emitter(ExceptionItem $exceptionItem): void $response->executeHeaders(); $stream = $response->getBody(); - if(is_callable($this->eventCallable)) { + if (is_callable($this->eventCallable)) { call_user_func_array($this->eventCallable, [$exceptionItem, $this->http]); } $stream->rewind(); @@ -277,7 +278,7 @@ protected function emitter(ExceptionItem $exceptionItem): void */ protected function sendExitCode(): void { - if(!is_null(self::$exitCode)) { + if (!is_null(self::$exitCode)) { exit(self::$exitCode); } } diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index 179a1be..3de9b45 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -24,7 +24,7 @@ use MaplePHP\Prompts\Ansi; use Throwable; -class CliHandler extends TextHandler implements HandlerInterface +final class CliHandler extends TextHandler implements HandlerInterface { protected static ?Ansi $ansi = null; protected static bool $enabledTraceLines = false; @@ -50,17 +50,17 @@ public function exceptionHandler(Throwable $exception): void */ protected function getErrorMessage(ExceptionItem|Throwable $exception): string { - if($exception instanceof Throwable) { + if ($exception instanceof Throwable) { $exception = new ExceptionItem($exception); } $msg = "\n"; $msg .= self::ansi()->red("%s ") . self::ansi()->italic("(%s)") . ": "; $msg .= self::ansi()->bold("%s ") . " \n\n"; $msg .= self::ansi()->bold("File: ") . "%s:(" . self::ansi()->bold("%s") . ")\n\n"; - $severityLevel = (method_exists($exception, "getSeverity") ? $exception->getSeverity() : 0); + $severityLevel = $exception->getSeverity(); $result = []; - if(self::$enabledTraceLines) { + if (self::$enabledTraceLines) { $trace = $exception->getTrace($this->getMaxTraceLevel()); $result = $this->getTraceResult($trace); $msg .= self::ansi()->bold("Stack trace:") . "\n"; @@ -68,7 +68,7 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string } $message = preg_replace('/\s+/', ' ', $exception->getMessage()); - $message = wordwrap($message, 110); + $message = wordwrap((string)$message, 110); return sprintf( $msg, @@ -94,7 +94,7 @@ protected function getTraceResult(array $traceArr): array $result = []; $traceLine = self::ansi()->bold("#%s ") . "%s(" . self::ansi()->bold("%s") . "): %s(%s)"; foreach ($traceArr as $key => $stackPoint) { - if(is_array($stackPoint)) { + if (is_array($stackPoint)) { $args = is_array($stackPoint['args']) ? $stackPoint['args'] : []; $result[] = sprintf( $traceLine, @@ -118,7 +118,7 @@ protected function getTraceResult(array $traceArr): array */ protected static function ansi(): Ansi { - if(is_null(self::$ansi)) { + if (is_null(self::$ansi)) { self::$ansi = new Ansi(); } diff --git a/src/Handlers/HtmlHandler.php b/src/Handlers/HtmlHandler.php index 7953be0..be39dec 100755 --- a/src/Handlers/HtmlHandler.php +++ b/src/Handlers/HtmlHandler.php @@ -23,7 +23,7 @@ use MaplePHP\Blunder\Templates\HtmlHelperTrait; use Throwable; -class HtmlHandler extends AbstractHandler implements HandlerInterface +final class HtmlHandler extends AbstractHandler implements HandlerInterface { use HtmlHelperTrait; @@ -60,7 +60,7 @@ protected function document(ExceptionItem $exception): string $trace = $exception->getTrace($this->getMaxTraceLevel()); $codeBlockArr = $this->getTraceCodeBlock($trace); $port = $this->getHttp()->request()->getUri()->getPort(); - if(is_null($port)) { + if (is_null($port)) { $port = 80; } diff --git a/src/Handlers/JsonHandler.php b/src/Handlers/JsonHandler.php index 4f6886e..1d5ca31 100755 --- a/src/Handlers/JsonHandler.php +++ b/src/Handlers/JsonHandler.php @@ -15,14 +15,13 @@ * Don't delete this comment, it's part of the license. */ - namespace MaplePHP\Blunder\Handlers; use MaplePHP\Blunder\ExceptionItem; use MaplePHP\Blunder\Interfaces\HandlerInterface; use Throwable; -class JsonHandler extends AbstractHandler implements HandlerInterface +final class JsonHandler extends AbstractHandler implements HandlerInterface { protected static bool $enabledTraceLines = true; @@ -36,7 +35,7 @@ public function exceptionHandler(Throwable $exception): void $exceptionItem = new ExceptionItem($exception); $trace = $exceptionItem->getTrace($this->getMaxTraceLevel()); - $this->getHttp()->response()->getBody()->write(json_encode([ + $jsonString = json_encode([ "status" => $exceptionItem->getStatus(), "message" => $exception->getMessage(), "flag" => $exceptionItem->getSeverity(), @@ -44,7 +43,13 @@ public function exceptionHandler(Throwable $exception): void "line" => $exception->getLine(), "code" => $exception->getCode(), "trace" => $trace, - ])); + ]); + + if ($jsonString === false) { + throw new \RuntimeException('JSON encoding failed: ' . json_last_error_msg(), json_last_error()); + } + + $this->getHttp()->response()->getBody()->write($jsonString); $this->getHttp()->response()->withHeader('content-type', 'application/json; charset=utf-8'); $this->emitter($exceptionItem); } diff --git a/src/Handlers/PlainTextHandler.php b/src/Handlers/PlainTextHandler.php index 41311db..f092e93 100755 --- a/src/Handlers/PlainTextHandler.php +++ b/src/Handlers/PlainTextHandler.php @@ -22,7 +22,7 @@ use MaplePHP\Blunder\Interfaces\HandlerInterface; use Throwable; -class PlainTextHandler extends TextHandler implements HandlerInterface +final class PlainTextHandler extends TextHandler implements HandlerInterface { /** * Exception handler output diff --git a/src/Handlers/SilentHandler.php b/src/Handlers/SilentHandler.php index 08a4b3b..a88a08d 100755 --- a/src/Handlers/SilentHandler.php +++ b/src/Handlers/SilentHandler.php @@ -23,7 +23,7 @@ use MaplePHP\Blunder\Interfaces\HandlerInterface; use Throwable; -class SilentHandler extends TextHandler implements HandlerInterface +final class SilentHandler extends TextHandler implements HandlerInterface { protected bool $showFatalErrors; protected static bool $enabledTraceLines = false; @@ -49,11 +49,11 @@ public function __construct(bool $showFatalErrors = false) public function exceptionHandler(Throwable $exception): void { $exceptionItem = new ExceptionItem($exception); - if($this->showFatalErrors && ($exceptionItem->isLevelFatal() || $exceptionItem->getStatus() === "error")) { + if ($this->showFatalErrors && ($exceptionItem->isLevelFatal() || $exceptionItem->getStatus() === "error")) { // Event is trigger inside "exceptionHandler". parent::exceptionHandler($exception); } else { - if(is_callable($this->eventCallable)) { + if (is_callable($this->eventCallable)) { call_user_func_array($this->eventCallable, [$exceptionItem, $this->http]); } } diff --git a/src/Handlers/TextHandler.php b/src/Handlers/TextHandler.php index 17abd5c..e0b13b8 100755 --- a/src/Handlers/TextHandler.php +++ b/src/Handlers/TextHandler.php @@ -48,7 +48,7 @@ public function exceptionHandler(Throwable $exception): void */ protected function getErrorMessage(ExceptionItem|Throwable $exception): string { - if($exception instanceof Throwable) { + if ($exception instanceof Throwable) { $exception = new ExceptionItem($exception); } @@ -58,10 +58,10 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string $key = 0; $result = []; $trace = $exception->getTrace($this->getMaxTraceLevel()); - $severityLevel = (method_exists($exception, "getSeverity") ? $exception->getSeverity() : 0); + $severityLevel = $exception->getSeverity(); foreach ($trace as $key => $stackPoint) { - if(is_array($stackPoint)) { + if (is_array($stackPoint)) { $result[] = sprintf( $traceLine, $key, diff --git a/src/Handlers/XmlHandler.php b/src/Handlers/XmlHandler.php index ee3dba3..299e6e9 100755 --- a/src/Handlers/XmlHandler.php +++ b/src/Handlers/XmlHandler.php @@ -22,7 +22,7 @@ use SimpleXMLElement; use Throwable; -class XmlHandler extends AbstractHandler implements HandlerInterface +final class XmlHandler extends AbstractHandler implements HandlerInterface { protected static bool $enabledTraceLines = true; @@ -46,9 +46,9 @@ public function exceptionHandler(Throwable $exception): void $xml->addChild('code', (string)$exception->getCode()); $xmlTrace = $xml->addChild('trace'); - if(!is_null($xmlTrace)) { - foreach($trace as $row) { - if(is_array($row)) { + if (!is_null($xmlTrace)) { + foreach ($trace as $row) { + if (is_array($row)) { $xmlTrace->addChild("file", (string)($row['file'] ?? "")); $xmlTrace->addChild("line", (string)($row['line'] ?? "")); $xmlTrace->addChild("class", (string)($row['class'] ?? "")); diff --git a/src/HttpMessaging.php b/src/HttpMessaging.php index fdc055d..496d7fa 100755 --- a/src/HttpMessaging.php +++ b/src/HttpMessaging.php @@ -16,7 +16,6 @@ * Don't delete this comment, it's part of the license. */ - namespace MaplePHP\Blunder; use MaplePHP\Blunder\Interfaces\HttpMessagingInterface; @@ -29,7 +28,7 @@ use MaplePHP\Http\Stream; use MaplePHP\Http\Uri; -class HttpMessaging implements HttpMessagingInterface +final class HttpMessaging implements HttpMessagingInterface { protected ?ResponseInterface $response = null; protected ?ServerRequestInterface $request = null; @@ -51,7 +50,7 @@ public function __construct(?ResponseInterface $response = null, ?ServerRequestI */ public function response(): ResponseInterface { - if(!($this->response instanceof ResponseInterface)) { + if (!($this->response instanceof ResponseInterface)) { $stream = new Stream(Stream::TEMP); $this->response = new Response($stream); } @@ -65,7 +64,7 @@ public function response(): ResponseInterface */ public function request(): ServerRequestInterface { - if(!($this->request instanceof ServerRequestInterface)) { + if (!($this->request instanceof ServerRequestInterface)) { $env = new Environment(); $this->request = new ServerRequest(new Uri($env->getUriParts()), $env); } @@ -81,7 +80,7 @@ public function request(): ServerRequestInterface */ public function stream(mixed $stream = null, string $permission = "r+"): StreamInterface { - if(!is_null($stream)) { + if (!is_null($stream)) { return new Stream($stream, $permission); } diff --git a/src/Interfaces/AbstractHandlerInterface.php b/src/Interfaces/AbstractHandlerInterface.php index 0e24de9..1fbf767 100755 --- a/src/Interfaces/AbstractHandlerInterface.php +++ b/src/Interfaces/AbstractHandlerInterface.php @@ -74,4 +74,12 @@ public function event(Closure $event): void; * @return self */ public function setSeverity(SeverityLevelPool $severity): self; + + /** + * You can disable exit code 1 so Blunder can be used in test cases + * + * @param int $code + * @return $this + */ + public function setExitCode(int $code): self; } diff --git a/src/Interfaces/HandlerInterface.php b/src/Interfaces/HandlerInterface.php index ca1f4d0..db2d551 100755 --- a/src/Interfaces/HandlerInterface.php +++ b/src/Interfaces/HandlerInterface.php @@ -17,8 +17,9 @@ namespace MaplePHP\Blunder\Interfaces; +use Throwable; interface HandlerInterface { - + public function exceptionHandler(Throwable $exception): void; } diff --git a/src/Run.php b/src/Run.php index 5b12512..cb60af2 100755 --- a/src/Run.php +++ b/src/Run.php @@ -16,14 +16,13 @@ * Don't delete this comment, it's part of the license. */ - namespace MaplePHP\Blunder; use MaplePHP\Blunder\Interfaces\AbstractHandlerInterface; use MaplePHP\Blunder\Interfaces\HttpMessagingInterface; use Closure; -class Run +final class Run { private AbstractHandlerInterface $handler; private ?SeverityLevelPool $severity = null; @@ -32,7 +31,7 @@ class Run public function __construct(AbstractHandlerInterface $handler, ?HttpMessagingInterface $http = null) { $this->handler = $handler; - if(!is_null($http)) { + if (!is_null($http)) { $this->handler->setHttp($http); } } @@ -68,7 +67,7 @@ public function removeLocationHeader(bool $removeRedirect): self */ public function severity(): SeverityLevelPool { - if(is_null($this->severity)) { + if (is_null($this->severity)) { $this->severity = new SeverityLevelPool(); } diff --git a/src/SeverityLevelPool.php b/src/SeverityLevelPool.php index 3dd07a9..25622bb 100755 --- a/src/SeverityLevelPool.php +++ b/src/SeverityLevelPool.php @@ -16,7 +16,6 @@ * Don't delete this comment, it's part of the license. */ - namespace MaplePHP\Blunder; use Closure; @@ -24,7 +23,7 @@ use MaplePHP\Blunder\Enums\BlunderErrorType; use MaplePHP\Blunder\Interfaces\HandlerInterface; -class SeverityLevelPool +final class SeverityLevelPool { private array $allowedSeverityTypes = []; private array $removedSeverityTypes = []; @@ -32,7 +31,7 @@ class SeverityLevelPool public function __construct(?array $allowedSeverityTypes = null) { - if(is_array($allowedSeverityTypes)) { + if (is_array($allowedSeverityTypes)) { $this->setSeverityLevels($allowedSeverityTypes); } else { $this->allowedSeverityTypes = BlunderErrorType::getAllErrorLevels(); @@ -86,9 +85,12 @@ public function redirectTo(Closure $call): self { $this->allowedSeverityTypes = array_merge($this->allowedSeverityTypes, $this->removedSeverityTypes); $this->redirectCall = function ( - int $errNo, string $errStr, string $errFile, int $errLine = 0, array $context = [] - ) use ($call): bool|null|HandlerInterface - { + int $errNo, + string $errStr, + string $errFile, + int $errLine = 0, + array $context = [] + ) use ($call): mixed { return $call($errNo, $errStr, $errFile, $errLine, $context); }; return $this; @@ -112,7 +114,7 @@ public function getRedirectCall(): ?Closure */ public function hasRemovedSeverity(int $level): bool { - return $this->has($level, true); + return (bool)$this->has($level, true); } /** @@ -126,7 +128,7 @@ public function excludeSeverityLevels(array $exclude): self { $this->validate($exclude); $this->deleteSeverityLevel(E_ALL); - foreach($exclude as $severityLevel) { + foreach ($exclude as $severityLevel) { $this->deleteSeverityLevel((int)$severityLevel); } return $this; @@ -140,7 +142,7 @@ public function excludeSeverityLevels(array $exclude): self */ public function deleteSeverityLevel(int $flag): bool { - if(($key = $this->has($flag)) !== false) { + if (($key = $this->has($flag)) !== false) { $this->removedSeverityTypes[$key] = $flag; unset($this->allowedSeverityTypes[$key]); return true; @@ -168,7 +170,7 @@ public function has(int $flag, bool $deleted = false): false|int|string */ public function getSeverityLevelMask(): int { - if($this->has(E_ALL) !== false) { + if ($this->has(E_ALL) !== false) { return E_ALL; } @@ -216,7 +218,7 @@ final public function isLevelFatal(int $level): bool */ final protected function validate(int|array $level): bool { - if(!is_array($level)) { + if (!is_array($level)) { $level = [$level]; } return $this->validateMultiple($level); @@ -230,9 +232,9 @@ final protected function validate(int|array $level): bool */ private function validateMultiple(array $levels): bool { - foreach($levels as $level) { + foreach ($levels as $level) { $level = (int)$level; - if($this->has($level) === false) { + if ($this->has($level) === false) { throw new InvalidArgumentException("The severity level '$level' does not exist."); } } diff --git a/src/Templates/HtmlHelperTrait.php b/src/Templates/HtmlHelperTrait.php index 2b966e9..1582479 100644 --- a/src/Templates/HtmlHelperTrait.php +++ b/src/Templates/HtmlHelperTrait.php @@ -23,15 +23,15 @@ use MaplePHP\Blunder\Handlers\HtmlHandler; use MaplePHP\Http\Interfaces\StreamInterface; -trait HtmlHelperTrait { - +trait HtmlHelperTrait +{ private string $logo = ''; /** * Set a custom logo for HTML Handler * * @param string $logoHtml - * @return HtmlHandler|HtmlHelperTrait + * @return self */ protected function setLogo(string $logoHtml): self { @@ -89,16 +89,16 @@ protected function getNavBlock(int $index, int $length, array $stack): string " . "" . ($length - $index) . ". $class . $functionName " . ltrim((string)$stack['file'], "/") . ": {$stack['line']} " - ; + ; } protected function getRows(string $title, ?array $rows): string { $out = '
'; $out .= '

' . $title . '

'; - if(is_array($rows) && count($rows) > 0) { - foreach($rows as $key => $value) { - if(is_array($value)) { + if (is_array($rows) && count($rows) > 0) { + foreach ($rows as $key => $value) { + if (is_array($value)) { $value = json_encode($value); } @@ -130,7 +130,7 @@ protected function getTraceNavBlock(array $trace): string $output = ""; $length = count($trace); foreach ($trace as $index => $stackPoint) { - if(is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { + if (is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { $output .= $this->getNavBlock($index, $length, $stackPoint); } } @@ -148,7 +148,7 @@ protected function getTraceNavBlock(array $trace): string */ protected function excerpt(string $value, int $length): string { - if(strlen($value) > $length) { + if (strlen($value) > $length) { $value = trim(substr($value, 0, $length)) . "..."; } @@ -170,7 +170,7 @@ protected function getContentsBetween(StreamInterface $stream, int $errorLine, i $output = ''; $startLine = $errorLine - $startSpan; $endLine = $errorLine + $endSpan; - if($startLine < 1) { + if ($startLine < 1) { $startLine = 1; } while (!$stream->eof()) { @@ -180,7 +180,7 @@ protected function getContentsBetween(StreamInterface $stream, int $errorLine, i if ($index >= $startLine && $index <= $endLine) { $output .= ''; $output .= ''. $index .''; - if($errorLine === $index) { + if ($errorLine === $index) { $output .= "" . htmlspecialchars($lineContent) . "\n"; } else { $output .= "" . htmlspecialchars($lineContent) . "\n"; @@ -209,10 +209,10 @@ public function getAssetContent(string $file): string $ending = explode(".", $file); $ending = end($ending); - if(!($ending === "css" || $ending === "js")) { + if (!($ending === "css" || $ending === "js")) { throw new ErrorException("Only JS and CSS files are allowed as assets files"); } - $filePath = (str_starts_with($file, "/") ? realpath($file) : realpath(__DIR__ . "/../") . "/" . $file); + $filePath = (str_starts_with($file, "/") ? realpath($file) : (string)realpath(__DIR__ . "/../") . "/" . $file); $stream = $this->getStream($filePath); return $stream->getContents(); @@ -228,7 +228,7 @@ public function getSeverityBreadcrumb(ExceptionItem $meta): string { $breadcrumb = get_class($meta->getException()); $severityConstant = $meta->getSeverityConstant(); - if(!is_null($severityConstant)) { + if (!is_null($severityConstant)) { $breadcrumb .= " ($severityConstant)"; } @@ -246,7 +246,7 @@ final protected function getTraceCodeBlock(array $trace): array { $block = []; foreach ($trace as $key => $stackPoint) { - if(is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { + if (is_array($stackPoint) && isset($stackPoint['file']) && is_file((string)$stackPoint['file'])) { $stream = $this->getStream($stackPoint['file']); $code = $this->getContentsBetween($stream, (int)$stackPoint['line']); $block[] = $this->getCodeBlock($stackPoint, $code, $key); @@ -255,4 +255,4 @@ final protected function getTraceCodeBlock(array $trace): array } return $block; } -} \ No newline at end of file +} From a1713e03757f773c78789401d1cfe9786bfb5e34 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sun, 4 May 2025 00:43:27 +0200 Subject: [PATCH 07/14] Updated ansi path --- src/Handlers/CliHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index 3de9b45..feffdbd 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -21,7 +21,7 @@ use MaplePHP\Blunder\ExceptionItem; use MaplePHP\Blunder\Interfaces\HandlerInterface; use MaplePHP\Blunder\SeverityLevelPool; -use MaplePHP\Prompts\Ansi; +use MaplePHP\Prompts\Themes\Ansi; use Throwable; final class CliHandler extends TextHandler implements HandlerInterface From b7640ad53a0bd6be0f2f669223015e32ec90d84f Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sun, 4 May 2025 16:45:11 +0200 Subject: [PATCH 08/14] Add a comment to Blunder main exception --- src/BlunderErrorException.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/BlunderErrorException.php b/src/BlunderErrorException.php index c0f51f0..d91590a 100644 --- a/src/BlunderErrorException.php +++ b/src/BlunderErrorException.php @@ -27,6 +27,19 @@ final class BlunderErrorException extends ErrorException { protected ?string $prettyMessage = null; + /* + public function __construct( + string $message = "", + int $code = 0, + int $severity = E_ERROR, + string $file = __FILE__, + int $line = __LINE__, + ?Throwable $previous = null + ) { + parent::__construct($message, $code, $severity, $file, $line, $previous); + } + */ + /** * Will return the default ErrorException message * From 5b824283ea7037e2669e11d4cafc86388da22c1d Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Wed, 7 May 2025 21:38:19 +0200 Subject: [PATCH 09/14] feat: Enable trace debugging by default in CLI Handler --- src/Handlers/CliHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index feffdbd..fba19e5 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -27,7 +27,7 @@ final class CliHandler extends TextHandler implements HandlerInterface { protected static ?Ansi $ansi = null; - protected static bool $enabledTraceLines = false; + protected static bool $enabledTraceLines = true; /** * Exception handler output From 84fd79183eefb6a68e20bad3cc4ff2f2bd384207 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Fri, 9 May 2025 21:58:07 +0200 Subject: [PATCH 10/14] refactor: replace function null checks with strict comparisons --- src/BlunderErrorException.php | 2 +- src/ExceptionItem.php | 2 +- src/Handlers/AbstractHandler.php | 10 +++++----- src/Handlers/CliHandler.php | 2 +- src/Handlers/HtmlHandler.php | 2 +- src/Handlers/XmlHandler.php | 2 +- src/HttpMessaging.php | 2 +- src/Run.php | 4 ++-- src/Templates/HtmlHelperTrait.php | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/BlunderErrorException.php b/src/BlunderErrorException.php index d91590a..a2e25e8 100644 --- a/src/BlunderErrorException.php +++ b/src/BlunderErrorException.php @@ -47,7 +47,7 @@ public function __construct( */ public function getPrettyMessage(): ?string { - return (!is_null($this->prettyMessage)) ? $this->prettyMessage : $this->message; + return ($this->prettyMessage !== null) ? $this->prettyMessage : $this->message; } /** diff --git a/src/ExceptionItem.php b/src/ExceptionItem.php index 960a03d..cbe4c03 100755 --- a/src/ExceptionItem.php +++ b/src/ExceptionItem.php @@ -39,7 +39,7 @@ public function __construct(Throwable $exception, ?SeverityLevelPool $pool = nul $this->exception = $exception; $this->severityError = BlunderErrorType::fromErrorLevel(1); $this->flag = (method_exists($exception, "getSeverity")) ? $exception->getSeverity() : 0; - if (is_null($pool)) { + if ($pool === null) { $pool = new SeverityLevelPool(); } $this->pool = $pool; diff --git a/src/Handlers/AbstractHandler.php b/src/Handlers/AbstractHandler.php index 2498081..6044cf2 100755 --- a/src/Handlers/AbstractHandler.php +++ b/src/Handlers/AbstractHandler.php @@ -127,7 +127,7 @@ public function getHttp(): HttpMessagingInterface */ final protected function getStream(mixed $stream = null, string $permission = "r+"): StreamInterface { - if (is_null($this->http)) { + if ($this->http === null) { throw new BadMethodCallException("You Must initialize the stream before calling this method"); } return $this->http->stream($stream, $permission); @@ -171,7 +171,7 @@ public function errorHandler(int $errNo, string $errStr, string $errFile, int $e if ($errNo & error_reporting()) { // Redirect to PHP error $redirectHandler = $this->redirectExceptionHandler($errNo, $errStr, $errFile, $errLine, $context); - if (!is_null($redirectHandler)) { + if ($redirectHandler !== null) { return $redirectHandler; } $this->cleanOutputBuffers(); @@ -207,9 +207,9 @@ public function redirectExceptionHandler( ): null|bool { if ($this->severityLevelPool && $this->severityLevelPool->hasRemovedSeverity($errNo)) { $redirectCall = $this->severityLevelPool->getRedirectCall(); - if (!is_null($redirectCall)) { + if ($redirectCall !== null) { $ret = $redirectCall($errNo, $errStr, $errFile, $errLine, $context); - if (!is_null($ret)) { + if ($ret !== null) { if ($ret instanceof HandlerInterface) { $exception = new BlunderErrorException($errStr, 0, $errNo, $errFile, $errLine); $ret->exceptionHandler($exception); @@ -278,7 +278,7 @@ protected function emitter(ExceptionItem $exceptionItem): void */ protected function sendExitCode(): void { - if (!is_null(self::$exitCode)) { + if (self::$exitCode !== null) { exit(self::$exitCode); } } diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index fba19e5..ee62ad3 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -118,7 +118,7 @@ protected function getTraceResult(array $traceArr): array */ protected static function ansi(): Ansi { - if (is_null(self::$ansi)) { + if (self::$ansi === null) { self::$ansi = new Ansi(); } diff --git a/src/Handlers/HtmlHandler.php b/src/Handlers/HtmlHandler.php index be39dec..c1434e0 100755 --- a/src/Handlers/HtmlHandler.php +++ b/src/Handlers/HtmlHandler.php @@ -60,7 +60,7 @@ protected function document(ExceptionItem $exception): string $trace = $exception->getTrace($this->getMaxTraceLevel()); $codeBlockArr = $this->getTraceCodeBlock($trace); $port = $this->getHttp()->request()->getUri()->getPort(); - if (is_null($port)) { + if ($port === null) { $port = 80; } diff --git a/src/Handlers/XmlHandler.php b/src/Handlers/XmlHandler.php index 299e6e9..43d06cd 100755 --- a/src/Handlers/XmlHandler.php +++ b/src/Handlers/XmlHandler.php @@ -46,7 +46,7 @@ public function exceptionHandler(Throwable $exception): void $xml->addChild('code', (string)$exception->getCode()); $xmlTrace = $xml->addChild('trace'); - if (!is_null($xmlTrace)) { + if ($xmlTrace !== null) { foreach ($trace as $row) { if (is_array($row)) { $xmlTrace->addChild("file", (string)($row['file'] ?? "")); diff --git a/src/HttpMessaging.php b/src/HttpMessaging.php index 496d7fa..8fbe79c 100755 --- a/src/HttpMessaging.php +++ b/src/HttpMessaging.php @@ -80,7 +80,7 @@ public function request(): ServerRequestInterface */ public function stream(mixed $stream = null, string $permission = "r+"): StreamInterface { - if (!is_null($stream)) { + if ($stream !== null) { return new Stream($stream, $permission); } diff --git a/src/Run.php b/src/Run.php index cb60af2..ffc8696 100755 --- a/src/Run.php +++ b/src/Run.php @@ -31,7 +31,7 @@ final class Run public function __construct(AbstractHandlerInterface $handler, ?HttpMessagingInterface $http = null) { $this->handler = $handler; - if (!is_null($http)) { + if ($http !== null) { $this->handler->setHttp($http); } } @@ -67,7 +67,7 @@ public function removeLocationHeader(bool $removeRedirect): self */ public function severity(): SeverityLevelPool { - if (is_null($this->severity)) { + if ($this->severity === null) { $this->severity = new SeverityLevelPool(); } diff --git a/src/Templates/HtmlHelperTrait.php b/src/Templates/HtmlHelperTrait.php index 1582479..4346abf 100644 --- a/src/Templates/HtmlHelperTrait.php +++ b/src/Templates/HtmlHelperTrait.php @@ -228,7 +228,7 @@ public function getSeverityBreadcrumb(ExceptionItem $meta): string { $breadcrumb = get_class($meta->getException()); $severityConstant = $meta->getSeverityConstant(); - if (!is_null($severityConstant)) { + if ($severityConstant !== null) { $breadcrumb .= " ($severityConstant)"; } From 3f39cd5ae3e4611a962c0cfc51558468f4e5d120 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Mon, 19 May 2025 22:57:25 +0200 Subject: [PATCH 11/14] bugfix: Make class::method work in trance --- src/Handlers/CliHandler.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index ee62ad3..d14bc20 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -101,7 +101,7 @@ protected function getTraceResult(array $traceArr): array $key, (string)($stackPoint['file'] ?? "0"), (string)($stackPoint['line'] ?? "0"), - (string)($stackPoint['function'] ?? "void"), + $this->getTracedMethodName($stackPoint), implode(', ', $args) ); } @@ -112,6 +112,20 @@ protected function getTraceResult(array $traceArr): array return $result; } + /** + * Get traced method name + * + * @param array $stackPoint + * @return string + */ + protected function getTracedMethodName(array $stackPoint): string + { + $class = ($stackPoint['class'] ?? ''); + $type = ($stackPoint['type'] ?? ''); + $function = ($stackPoint['function'] ?? 'void'); + return "{$class}{$type}{$function}"; + } + /** * Get ansi immutable instance * @return Ansi From d66b578ceeab06fda67c8a19e6b195d735f78a48 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Tue, 20 May 2025 22:18:39 +0200 Subject: [PATCH 12/14] Add a soft exception that can be used to throw notices --- src/Exceptions/BlunderSoftException.php | 26 +++++++++++++++++++++++++ src/Handlers/CliHandler.php | 7 +++++++ 2 files changed, 33 insertions(+) create mode 100644 src/Exceptions/BlunderSoftException.php diff --git a/src/Exceptions/BlunderSoftException.php b/src/Exceptions/BlunderSoftException.php new file mode 100644 index 0000000..9d1c8ea --- /dev/null +++ b/src/Exceptions/BlunderSoftException.php @@ -0,0 +1,26 @@ +red("%s ") . self::ansi()->italic("(%s)") . ": "; $msg .= self::ansi()->bold("%s ") . " \n\n"; @@ -70,6 +74,9 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string $message = preg_replace('/\s+/', ' ', $exception->getMessage()); $message = wordwrap((string)$message, 110); + if($exception->getException() instanceof BlunderSoftException) { + return self::ansi()->style(["bold", "red"], "Notice: ") . $message; + } return sprintf( $msg, get_class($exception->getException()), From 8ec606b4e90aa509756c9706285819c0e0e5e93d Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sun, 1 Jun 2025 16:09:14 +0200 Subject: [PATCH 13/14] Correct serverty flag on CLI, text and XML handlers --- .idea/.gitignore | 8 ++++++++ .idea/Blunder.iml | 18 +++++++++++++++++ .idea/laravel-idea.xml | 8 ++++++++ .idea/modules.xml | 8 ++++++++ .idea/php.xml | 34 ++++++++++++++++++++++++++++++++ .idea/vcs.xml | 6 ++++++ composer.json | 2 +- src/Handlers/AbstractHandler.php | 5 +++++ src/Handlers/CliHandler.php | 12 +++++------ src/Handlers/JsonHandler.php | 2 +- src/Handlers/TextHandler.php | 4 ++-- src/Handlers/XmlHandler.php | 2 +- 12 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/Blunder.iml create mode 100644 .idea/laravel-idea.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/php.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Blunder.iml b/.idea/Blunder.iml new file mode 100644 index 0000000..fba3303 --- /dev/null +++ b/.idea/Blunder.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/laravel-idea.xml b/.idea/laravel-idea.xml new file mode 100644 index 0000000..bd941a4 --- /dev/null +++ b/.idea/laravel-idea.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..dcd829c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..38c639c --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/composer.json b/composer.json index 5a416b6..3f0ab11 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ } ], "require": { - "php": ">=8.0", + "php": ">=8.2", "maplephp/http": "^1.0", "maplephp/prompts": "^1.0" }, diff --git a/src/Handlers/AbstractHandler.php b/src/Handlers/AbstractHandler.php index 6044cf2..fa5c1dd 100755 --- a/src/Handlers/AbstractHandler.php +++ b/src/Handlers/AbstractHandler.php @@ -174,6 +174,11 @@ public function errorHandler(int $errNo, string $errStr, string $errFile, int $e if ($redirectHandler !== null) { return $redirectHandler; } + /* + if (in_array($errNo, [E_USER_WARNING, E_USER_NOTICE, E_WARNING, E_NOTICE], true)) { + return true; + } + */ $this->cleanOutputBuffers(); $this->exception = new BlunderErrorException($errStr, 0, $errNo, $errFile, $errLine); if ($this->throwException) { diff --git a/src/Handlers/CliHandler.php b/src/Handlers/CliHandler.php index 7fc9bba..b8b3504 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -18,7 +18,6 @@ namespace MaplePHP\Blunder\Handlers; -use MaplePHP\Blunder\BlunderErrorException; use MaplePHP\Blunder\ExceptionItem; use MaplePHP\Blunder\Exceptions\BlunderSoftException; use MaplePHP\Blunder\Interfaces\HandlerInterface; @@ -56,12 +55,11 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string $exception = new ExceptionItem($exception); } - $msg = "\n"; $msg .= self::ansi()->red("%s ") . self::ansi()->italic("(%s)") . ": "; $msg .= self::ansi()->bold("%s ") . " \n\n"; - $msg .= self::ansi()->bold("File: ") . "%s:(" . self::ansi()->bold("%s") . ")\n\n"; - $severityLevel = $exception->getSeverity(); + $msg .= self::ansi()->bold("File: ") . "%s:" . self::ansi()->bold("%s") . "\n\n"; + //$severityLevel = $exception->getSeverity(); $result = []; if (self::$enabledTraceLines) { @@ -80,7 +78,7 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string return sprintf( $msg, get_class($exception->getException()), - (string)SeverityLevelPool::getSeverityLevel((int)$severityLevel, "Error"), + (string)$exception->getSeverityConstant(), $message, $exception->getFile(), $exception->getLine(), @@ -99,7 +97,7 @@ protected function getTraceResult(array $traceArr): array { $key = 0; $result = []; - $traceLine = self::ansi()->bold("#%s ") . "%s(" . self::ansi()->bold("%s") . "): %s(%s)"; + $traceLine = self::ansi()->bold("#%s ") . "%s:" . self::ansi()->bold("%s") . ": %s(%s)"; foreach ($traceArr as $key => $stackPoint) { if (is_array($stackPoint)) { $args = is_array($stackPoint['args']) ? $stackPoint['args'] : []; @@ -128,7 +126,7 @@ protected function getTraceResult(array $traceArr): array protected function getTracedMethodName(array $stackPoint): string { $class = ($stackPoint['class'] ?? ''); - $type = ($stackPoint['type'] ?? ''); + $type = ($stackPoint['type'] ?? ':'); $function = ($stackPoint['function'] ?? 'void'); return "{$class}{$type}{$function}"; } diff --git a/src/Handlers/JsonHandler.php b/src/Handlers/JsonHandler.php index 1d5ca31..33aa3c9 100755 --- a/src/Handlers/JsonHandler.php +++ b/src/Handlers/JsonHandler.php @@ -38,7 +38,7 @@ public function exceptionHandler(Throwable $exception): void $jsonString = json_encode([ "status" => $exceptionItem->getStatus(), "message" => $exception->getMessage(), - "flag" => $exceptionItem->getSeverity(), + "flag" => $exceptionItem->getSeverityConstant(), "file" => $exception->getFile(), "line" => $exception->getLine(), "code" => $exception->getCode(), diff --git a/src/Handlers/TextHandler.php b/src/Handlers/TextHandler.php index e0b13b8..101c723 100755 --- a/src/Handlers/TextHandler.php +++ b/src/Handlers/TextHandler.php @@ -58,7 +58,7 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string $key = 0; $result = []; $trace = $exception->getTrace($this->getMaxTraceLevel()); - $severityLevel = $exception->getSeverity(); + //$severityLevel = $exception->getSeverity(); foreach ($trace as $key => $stackPoint) { if (is_array($stackPoint)) { @@ -80,7 +80,7 @@ protected function getErrorMessage(ExceptionItem|Throwable $exception): string return sprintf( $msg, get_class($exception->getException()), - (string)SeverityLevelPool::getSeverityLevel((int)$severityLevel, "Error"), + (string)$exception->getSeverityConstant(), $exception->getMessage(), $exception->getFile(), $exception->getLine(), diff --git a/src/Handlers/XmlHandler.php b/src/Handlers/XmlHandler.php index 43d06cd..cccd088 100755 --- a/src/Handlers/XmlHandler.php +++ b/src/Handlers/XmlHandler.php @@ -40,7 +40,7 @@ public function exceptionHandler(Throwable $exception): void $xml = new SimpleXMLElement(''); $xml->addChild('status', $exceptionItem->getStatus()); $xml->addChild('message', $exception->getMessage()); - $xml->addChild('flag', $exceptionItem->getSeverity()); + $xml->addChild('flag', $exceptionItem->getSeverityConstant()); $xml->addChild('file', $exception->getFile()); $xml->addChild('line', (string)$exception->getLine()); $xml->addChild('code', (string)$exception->getCode()); From d354382cd31f442f57630d64d858a9e7ec980106 Mon Sep 17 00:00:00 2001 From: Daniel Ronkainen Date: Sun, 1 Jun 2025 16:10:58 +0200 Subject: [PATCH 14/14] Add .idea to ignore file --- .gitignore | 3 ++- .idea/.gitignore | 8 -------- .idea/Blunder.iml | 18 ------------------ .idea/laravel-idea.xml | 8 -------- .idea/modules.xml | 8 -------- .idea/php.xml | 34 ---------------------------------- .idea/vcs.xml | 6 ------ 7 files changed, 2 insertions(+), 83 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/Blunder.iml delete mode 100644 .idea/laravel-idea.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/php.xml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index c66fb76..2cb622c 100755 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .DS_Store .sass-cache composer.lock -vendor \ No newline at end of file +vendor +.idea/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/Blunder.iml b/.idea/Blunder.iml deleted file mode 100644 index fba3303..0000000 --- a/.idea/Blunder.iml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/laravel-idea.xml b/.idea/laravel-idea.xml deleted file mode 100644 index bd941a4..0000000 --- a/.idea/laravel-idea.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index dcd829c..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml deleted file mode 100644 index 38c639c..0000000 --- a/.idea/php.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file