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/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/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/BlunderErrorException.php b/src/BlunderErrorException.php index 1019826..a2e25e8 100644 --- a/src/BlunderErrorException.php +++ b/src/BlunderErrorException.php @@ -1,5 +1,21 @@ prettyMessage)) ? $this->prettyMessage : $this->message; + return ($this->prettyMessage !== null) ? $this->prettyMessage : $this->message; } /** - * Set pretty message that can be used in execption handlers + * Set pretty message that can be used in exception handlers * * @param string $message * @return void diff --git a/src/Enums/BlunderErrorType.php b/src/Enums/BlunderErrorType.php new file mode 100644 index 0000000..de0d57e --- /dev/null +++ b/src/Enums/BlunderErrorType.php @@ -0,0 +1,133 @@ + + */ + 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/ExceptionItem.php b/src/ExceptionItem.php index 5ac7345..cbe4c03 100755 --- a/src/ExceptionItem.php +++ b/src/ExceptionItem.php @@ -1,34 +1,53 @@ 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; } /** - * Will return error message + * Will return an error message + * * @return string */ public function __toString(): string @@ -38,21 +57,22 @@ public function __toString(): string /** * Access the exception + * * @param string $name * @param array $args * @return mixed */ 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"); + if (!method_exists($this->exception, $name)) { + throw new BadMethodCallException("Method '$name' does not exist in Throwable class"); } - return $this->exception->{$name}(...$args); } /** * Get Exception + * * @return Throwable */ public function getException(): Throwable @@ -61,7 +81,18 @@ public function getException(): Throwable } /** - * Get exception type + * Get Severity Pool + * + * @return SeverityLevelPool + */ + public function getSeverityPool(): SeverityLevelPool + { + return $this->pool; + } + + /** + * Get an exception type + * * @return string */ public function getType(): string @@ -71,6 +102,7 @@ public function getType(): string /** * Delete a severity level + * * @return bool */ public function deleteSeverity(): bool @@ -79,7 +111,8 @@ public function deleteSeverity(): bool } /** - * Check if error type is supported + * Check if an error type is supported + * * @return bool */ public function hasSeverity(): bool @@ -88,12 +121,13 @@ 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 { - if($this->flag === 0) { + if ($this->flag === 0) { return $this->getType(); } @@ -112,6 +146,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 @@ -127,7 +162,96 @@ 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 a title from exception severity + * + * @return string + */ + public function getSeverityTitle(): string + { + return $this->getSeverityError()->getErrorLevelTitle(); + } + + + /** + * Get a 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 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 = isset($stackPoint['class']) ? (string) $stackPoint['class'] : ""; + $blunderErrorClass = "MaplePHP\Blunder\Handlers\AbstractHandler"; + + /** @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)) { + break; + } + } + } + return $new; + } + + /** + * Get an exception array with the 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 the error is a fatal error + * * @return bool */ final public function isLevelFatal(): bool 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 @@ +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 ($this->http === null) { + 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 @@ -134,9 +171,14 @@ 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; } + /* + 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) { @@ -150,15 +192,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 * @@ -176,18 +209,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 ($redirectCall !== null) { + $ret = $redirectCall($errNo, $errStr, $errFile, $errLine, $context); + if ($ret !== null) { + if ($ret instanceof HandlerInterface) { + $exception = new BlunderErrorException($errStr, 0, $errNo, $errFile, $errLine); + $ret->exceptionHandler($exception); + exit; + } + return !!$ret; } - return $ret; } } return null; @@ -195,13 +229,14 @@ public function redirectExceptionHandler( /** * Shutdown handler - * @throws ErrorException + * + * @throws Throwable */ 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( @@ -215,58 +250,25 @@ 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 + * + * @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); - } + if (is_callable($this->eventCallable)) { call_user_func_array($this->eventCallable, [$exceptionItem, $this->http]); } $stream->rewind(); @@ -275,191 +277,31 @@ protected function emitter(throwable $exception, ?ExceptionItem $exceptionItem = } /** - * Will send a exit code if specied + * Will send an exit code if specified * * @return void */ protected function sendExitCode(): void { - if(!is_null(self::$exitCode)) { + if (self::$exitCode !== null) { exit(self::$exitCode); } } /** - * 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 - * @param Throwable $exception + * Generate error message (placeholder) + * + * @param ExceptionItem|Throwable $exception * @return string */ - protected function getErrorMessage(Throwable $exception): string + protected function getErrorMessage(ExceptionItem|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 @@ -470,20 +312,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 4630db2..b8b3504 100755 --- a/src/Handlers/CliHandler.php +++ b/src/Handlers/CliHandler.php @@ -1,63 +1,84 @@ 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 { + 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); + $msg .= self::ansi()->bold("File: ") . "%s:" . self::ansi()->bold("%s") . "\n\n"; + //$severityLevel = $exception->getSeverity(); $result = []; - if(self::$enabledTraceLines) { - $trace = $this->getTrace($exception); + if (self::$enabledTraceLines) { + $trace = $exception->getTrace($this->getMaxTraceLevel()); $result = $this->getTraceResult($trace); $msg .= self::ansi()->bold("Stack trace:") . "\n"; $msg .= "%s\n"; } $message = preg_replace('/\s+/', ' ', $exception->getMessage()); - $message = wordwrap($message, 110); + $message = wordwrap((string)$message, 110); + if($exception->getException() instanceof BlunderSoftException) { + return self::ansi()->style(["bold", "red"], "Notice: ") . $message; + } return sprintf( $msg, - get_class($exception), - (string)SeverityLevelPool::getSeverityLevel((int)$severityLevel, "Error"), + get_class($exception->getException()), + (string)$exception->getSeverityConstant(), $message, $exception->getFile(), $exception->getLine(), @@ -76,16 +97,16 @@ 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)) { + if (is_array($stackPoint)) { $args = is_array($stackPoint['args']) ? $stackPoint['args'] : []; $result[] = sprintf( $traceLine, $key, (string)($stackPoint['file'] ?? "0"), (string)($stackPoint['line'] ?? "0"), - (string)($stackPoint['function'] ?? "void"), + $this->getTracedMethodName($stackPoint), implode(', ', $args) ); } @@ -96,28 +117,30 @@ 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 */ protected static function ansi(): Ansi { - if(is_null(self::$ansi)) { + if (self::$ansi === null) { self::$ansi = new 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..c1434e0 100755 --- a/src/Handlers/HtmlHandler.php +++ b/src/Handlers/HtmlHandler.php @@ -1,20 +1,32 @@ 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 { - $trace = $this->getTrace($exception); + $trace = $exception->getTrace($this->getMaxTraceLevel()); $codeBlockArr = $this->getTraceCodeBlock($trace); - $port = $this->getHttp()->request()->getUri()->getPort(); - if(is_null($port)) { + if ($port === null) { $port = 80; } @@ -69,21 +78,15 @@ protected function document(Throwable $exception): string
-
' . implode("\n", $codeBlockArr) . ' @@ -104,120 +107,18 @@ protected function document(Throwable $exception): string ' . $this->getRows("SERVER", $this->getHttp()->request()->getServerParams()) . '
+
'; } - - /** - * 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..33aa3c9 100755 --- a/src/Handlers/JsonHandler.php +++ b/src/Handlers/JsonHandler.php @@ -1,10 +1,18 @@ getTrace($exception); - $exceptionItem = new ExceptionItem($exception); - $this->getHttp()->response()->getBody()->write(json_encode([ + $trace = $exceptionItem->getTrace($this->getMaxTraceLevel()); + + $jsonString = json_encode([ "status" => $exceptionItem->getStatus(), "message" => $exception->getMessage(), - "flag" => $exceptionItem->getSeverity(), + "flag" => $exceptionItem->getSeverityConstant(), "file" => $exception->getFile(), "line" => $exception->getLine(), "code" => $exception->getCode(), "trace" => $trace, - ])); - $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; + 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 85e0bc3..f092e93 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); - } - - /** - * 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; + $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 39d424c..a88a08d 100755 --- a/src/Handlers/SilentHandler.php +++ b/src/Handlers/SilentHandler.php @@ -1,20 +1,29 @@ 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]); + if (is_callable($this->eventCallable)) { + call_user_func_array($this->eventCallable, [$exceptionItem, $this->http]); } } } - - /** - * 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..101c723 100755 --- a/src/Handlers/TextHandler.php +++ b/src/Handlers/TextHandler.php @@ -1,14 +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 { + 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"; $key = 0; $result = []; - $trace = $this->getTrace($exception); - $severityLevel = (method_exists($exception, "getSeverity") ? $exception->getSeverity() : 0); + $trace = $exception->getTrace($this->getMaxTraceLevel()); + //$severityLevel = $exception->getSeverity(); foreach ($trace as $key => $stackPoint) { - if(is_array($stackPoint)) { + if (is_array($stackPoint)) { $result[] = sprintf( $traceLine, $key, @@ -62,8 +79,8 @@ protected function getErrorMessage(Throwable $exception): string // write trace-lines into main template return sprintf( $msg, - get_class($exception), - (string)SeverityLevelPool::getSeverityLevel((int)$severityLevel, "Error"), + get_class($exception->getException()), + (string)$exception->getSeverityConstant(), $exception->getMessage(), $exception->getFile(), $exception->getLine(), @@ -72,16 +89,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..cccd088 100755 --- a/src/Handlers/XmlHandler.php +++ b/src/Handlers/XmlHandler.php @@ -1,10 +1,18 @@ getTrace($exception); + $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('flag', $exceptionItem->getSeverity()); + $xml->addChild('message', $exception->getMessage()); + $xml->addChild('flag', $exceptionItem->getSeverityConstant()); $xml->addChild('file', $exception->getFile()); $xml->addChild('line', (string)$exception->getLine()); $xml->addChild('code', (string)$exception->getCode()); $xmlTrace = $xml->addChild('trace'); - if(!is_null($xmlTrace)) { - foreach($trace as $row) { - if(is_array($row)) { + if ($xmlTrace !== null) { + 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'] ?? "")); @@ -50,18 +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 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; + $this->emitter($exceptionItem); } } diff --git a/src/HttpMessaging.php b/src/HttpMessaging.php index 9228cf1..8fbe79c 100755 --- a/src/HttpMessaging.php +++ b/src/HttpMessaging.php @@ -1,10 +1,19 @@ response instanceof ResponseInterface)) { + if (!($this->response instanceof ResponseInterface)) { $stream = new Stream(Stream::TEMP); $this->response = new Response($stream); } @@ -55,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); } @@ -71,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/Interfaces/AbstractHandlerInterface.php b/src/Interfaces/AbstractHandlerInterface.php index 358bbd9..1fbf767 100755 --- a/src/Interfaces/AbstractHandlerInterface.php +++ b/src/Interfaces/AbstractHandlerInterface.php @@ -1,10 +1,19 @@ handler = $handler; - if(!is_null($http)) { + if ($http !== null) { $this->handler->setHttp($http); } } @@ -58,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/SeverityLevelPool.php b/src/SeverityLevelPool.php index 9008aae..25622bb 100755 --- a/src/SeverityLevelPool.php +++ b/src/SeverityLevelPool.php @@ -1,49 +1,40 @@ '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; public function __construct(?array $allowedSeverityTypes = null) { - if(is_array($allowedSeverityTypes)) { + if (is_array($allowedSeverityTypes)) { $this->setSeverityLevels($allowedSeverityTypes); } else { - $this->allowedSeverityTypes = array_keys(self::SEVERITY_TYPES); + $this->allowedSeverityTypes = BlunderErrorType::getAllErrorLevels(); } } @@ -56,7 +47,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 +58,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); } /** @@ -92,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; @@ -118,7 +114,7 @@ public function getRedirectCall(): ?Closure */ public function hasRemovedSeverity(int $level): bool { - return $this->has($level, true); + return (bool)$this->has($level, true); } /** @@ -132,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; @@ -146,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; @@ -174,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; } @@ -222,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); @@ -236,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 new file mode 100644 index 0000000..4346abf --- /dev/null +++ b/src/Templates/HtmlHelperTrait.php @@ -0,0 +1,258 @@ +'; + + /** + * Set a custom logo for HTML Handler + * + * @param string $logoHtml + * @return self + */ + protected function setLogo(string $logoHtml): self + { + $this->logo = $logoHtml; + return $this; + } + + /** + * This will return the MaplePHP logotype + * + * @return string + */ + protected function getLogo(): string + { + return $this->logo; + } + + /** + * 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) : (string)realpath(__DIR__ . "/../") . "/" . $file); + $stream = $this->getStream($filePath); + + return $stream->getContents(); + } + + /** + * Will return the severity exception breadcrumb + * + * @param ExceptionItem $meta + * @return string + */ + public function getSeverityBreadcrumb(ExceptionItem $meta): string + { + $breadcrumb = get_class($meta->getException()); + $severityConstant = $meta->getSeverityConstant(); + if ($severityConstant !== null) { + $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; + } +} 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; } diff --git a/tests/unitary-blunder-handlers.php b/tests/unitary-blunder-handlers.php index 449cc4a..02ee0c9 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) { @@ -35,14 +27,14 @@ ->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); 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], 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])