Browse Source

Cleaning up, Fixing #9

2.1.0
Dustin Wilson 1 year ago
parent
commit
83eafab08a
  1. 56
      lib/Catcher.php
  2. 4
      lib/Catcher/Error.php
  3. 26
      lib/Catcher/Handler.php
  4. 4
      lib/Catcher/JSONHandler.php
  5. 25
      lib/Catcher/PlainTextHandler.php
  6. 24
      lib/Catcher/ThrowableController.php
  7. 4
      run

56
lib/Catcher.php

@ -1,8 +1,8 @@
<?php <?php
/** /**
* @license MIT * @license MIT
* Copyright 2022 Dustin Wilson, et al. * Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details * See LICENSE and AUTHORS files for details
*/ */
declare(strict_types=1); declare(strict_types=1);
@ -23,15 +23,15 @@ class Catcher {
/** When set to true Catcher will throw errors as throwables */ /** When set to true Catcher will throw errors as throwables */
public bool $throwErrors = true; public bool $throwErrors = true;
/** /**
* Stores the error reporting level set by Catcher to compare against when * Stores the error reporting level set by Catcher to compare against when
* unregistering * unregistering
*/ */
protected ?int $errorReporting = null; protected ?int $errorReporting = null;
/** /**
* Array of handlers the exceptions are passed to * Array of handlers the exceptions are passed to
* *
* @var Handler[] * @var Handler[]
*/ */
protected array $handlers = []; protected array $handlers = [];
/** Flag set when the shutdown handler is run */ /** Flag set when the shutdown handler is run */
@ -71,7 +71,7 @@ class Catcher {
if (count($this->handlers) === 1) { if (count($this->handlers) === 1) {
throw new \Exception("Popping the last handler will cause the Catcher to have zero handlers; there must be at least one\n"); throw new \Exception("Popping the last handler will cause the Catcher to have zero handlers; there must be at least one\n");
} }
return array_pop($this->handlers); return array_pop($this->handlers);
} }
@ -97,7 +97,7 @@ class Catcher {
return false; return false;
} }
// If the current error reporting level has E_ERROR then remove it and store for // If the current error reporting level has E_ERROR then remove it and store for
// comparison when unregistering // comparison when unregistering
$errorReporting = error_reporting(); $errorReporting = error_reporting();
if ($errorReporting & \E_ERROR) { if ($errorReporting & \E_ERROR) {
@ -121,7 +121,7 @@ class Catcher {
if (count($this->handlers) === 1) { if (count($this->handlers) === 1) {
throw new \Exception("Shifting the last handler will cause the Catcher to have zero handlers; there must be at least one\n"); throw new \Exception("Shifting the last handler will cause the Catcher to have zero handlers; there must be at least one\n");
} }
return array_shift($this->handlers); return array_shift($this->handlers);
} }
@ -133,7 +133,7 @@ class Catcher {
restore_error_handler(); restore_error_handler();
restore_exception_handler(); restore_exception_handler();
// If error reporting has been set when registering and the error reporting level // If error reporting has been set when registering and the error reporting level
// is the same as it was when it was set then add E_ERROR back to the error // is the same as it was when it was set then add E_ERROR back to the error
$errorReporting = error_reporting(); $errorReporting = error_reporting();
if ($this->errorReporting !== null && $this->errorReporting === $errorReporting) { if ($this->errorReporting !== null && $this->errorReporting === $errorReporting) {
@ -172,21 +172,21 @@ class Catcher {
} }
/** /**
* Converts regular errors into throwable Errors for easier handling; meant to be * Converts regular errors into throwable Errors for easier handling; meant to be
* used with set_error_handler. * used with set_error_handler.
* *
* @internal * @internal
*/ */
public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool { public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool {
if ($code && $code & error_reporting()) { if ($code && $code & error_reporting()) {
$error = new Error($message, $code, $file, $line); $error = new Error($message, $code, $file, $line);
if ($this->throwErrors) { if ($this->throwErrors) {
// The point of this library is to allow treating of errors as if they were // The point of this library is to allow treating of errors as if they were
// exceptions but instead have things like warnings, notices, etc. not stop // exceptions but instead have things like warnings, notices, etc. not stop
// execution. You normally can't have it both ways. So, what's going on here is // execution. You normally can't have it both ways. So, what's going on here is
// that if the error wouldn't normally stop execution the newly-created Error // that if the error wouldn't normally stop execution the newly-created Error
// throwable is thrown in a fork instead, allowing execution to resume in the // throwable is thrown in a fork instead, allowing execution to resume in the
// parent process. // parent process.
if ($this->isErrorFatal($code)) { if ($this->isErrorFatal($code)) {
throw $error; throw $error;
@ -199,7 +199,7 @@ class Catcher {
// This can't be covered because it happens in the fork // This can't be covered because it happens in the fork
throw $error; // @codeCoverageIgnore throw $error; // @codeCoverageIgnore
} }
pcntl_wait($status); pcntl_wait($status);
return true; return true;
} }
@ -208,14 +208,14 @@ class Catcher {
$this->handleThrowable($error); $this->handleThrowable($error);
return true; return true;
} }
// If preventing exit we don't want a false here to halt processing // If preventing exit we don't want a false here to halt processing
return ($this->preventExit); return ($this->preventExit);
} }
/** /**
* Handles both Exceptions and Errors; meant to be used with set_exception_handler. * Handles both Exceptions and Errors; meant to be used with set_exception_handler.
* *
* @internal * @internal
*/ */
public function handleThrowable(\Throwable $throwable): void { public function handleThrowable(\Throwable $throwable): void {
@ -235,7 +235,7 @@ class Catcher {
if ( if (
$this->isShuttingDown || $this->isShuttingDown ||
$controlCode & Handler::EXIT || $controlCode & Handler::EXIT ||
$throwable instanceof \Exception || $throwable instanceof \Exception ||
($throwable instanceof Error && $this->isErrorFatal($throwable->getCode())) || ($throwable instanceof Error && $this->isErrorFatal($throwable->getCode())) ||
(!$throwable instanceof Error && $throwable instanceof \Error) (!$throwable instanceof Error && $throwable instanceof \Error)
) { ) {
@ -248,7 +248,7 @@ class Catcher {
$this->lastThrowable = $throwable; $this->lastThrowable = $throwable;
// Don't want to exit here when shutting down so any shutdown functions further // Don't want to exit here when shutting down so any shutdown functions further
// down the stack still run. // down the stack still run.
if (!$this->preventExit && !$this->isShuttingDown) { if (!$this->preventExit && !$this->isShuttingDown) {
$this->exit(max($throwable->getCode(), 1)); $this->exit(max($throwable->getCode(), 1));
@ -258,9 +258,9 @@ class Catcher {
$this->lastThrowable = $throwable; $this->lastThrowable = $throwable;
} }
/** /**
* Handles shutdowns, passes all possible built-in error codes to the error handler. * Handles shutdowns, passes all possible built-in error codes to the error handler.
* *
* @internal * @internal
*/ */
public function handleShutdown(): void { public function handleShutdown(): void {

4
lib/Catcher/Error.php

@ -1,8 +1,8 @@
<?php <?php
/** /**
* @license MIT * @license MIT
* Copyright 2022 Dustin Wilson, et al. * Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details * See LICENSE and AUTHORS files for details
*/ */
declare(strict_types=1); declare(strict_types=1);

26
lib/Catcher/Handler.php

@ -1,8 +1,8 @@
<?php <?php
/** /**
* @license MIT * @license MIT
* Copyright 2022 Dustin Wilson, et al. * Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details * See LICENSE and AUTHORS files for details
*/ */
declare(strict_types=1); declare(strict_types=1);
@ -23,27 +23,27 @@ abstract class Handler {
public const NOW = 32; public const NOW = 32;
/** /**
* Array of HandlerOutputs the handler creates * Array of HandlerOutputs the handler creates
* *
* @var HandlerOutput[] * @var array[]
*/ */
protected array $outputBuffer = []; protected array $outputBuffer = [];
/** The number of backtrace frames in which to print arguments; defaults to 5 */ /** The number of backtrace frames in which to print arguments; defaults to 5 */
protected int $_backtraceArgFrameLimit = 5; protected int $_backtraceArgFrameLimit = 5;
/** /**
* The character encoding used for errors; only used if headers weren't sent before * The character encoding used for errors; only used if headers weren't sent before
* an error occurred * an error occurred
*/ */
protected string $_charset = 'UTF-8'; protected string $_charset = 'UTF-8';
/** If true the handler will force break the loop through the stack of handlers */ /** If true the handler will force break the loop through the stack of handlers */
protected bool $_forceBreak = false; protected bool $_forceBreak = false;
/** If true the handler will force an exit */ /** If true the handler will force an exit */
protected bool $_forceExit = false; protected bool $_forceExit = false;
/** /**
* If true the handler will output as soon as possible; however, if silent * If true the handler will output as soon as possible; however, if silent
* is true the handler will output nothing * is true the handler will output nothing
*/ */
protected bool $_forceOutputNow = false; protected bool $_forceOutputNow = false;
/** The HTTP code to be sent; possible values: 200, 400-599 */ /** The HTTP code to be sent; possible values: 200, 400-599 */
@ -60,9 +60,9 @@ abstract class Handler {
protected bool $_silent = false; protected bool $_silent = false;
/** The PHP-standard date format which to use for timestamps in output */ /** The PHP-standard date format which to use for timestamps in output */
protected string $_timeFormat = 'Y-m-d\TH:i:s.vO'; protected string $_timeFormat = 'Y-m-d\TH:i:s.vO';
/** /**
* A user-defined closure to use when printing arguments in backtraces * A user-defined closure to use when printing arguments in backtraces
* *
* @var ?(mixed): string|bool * @var ?(mixed): string|bool
*/ */
protected ?\Closure $_varExporter = null; protected ?\Closure $_varExporter = null;

4
lib/Catcher/JSONHandler.php

@ -1,8 +1,8 @@
<?php <?php
/** /**
* @license MIT * @license MIT
* Copyright 2022 Dustin Wilson, et al. * Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details * See LICENSE and AUTHORS files for details
*/ */
declare(strict_types=1); declare(strict_types=1);

25
lib/Catcher/PlainTextHandler.php

@ -1,8 +1,8 @@
<?php <?php
/** /**
* @license MIT * @license MIT
* Copyright 2022 Dustin Wilson, et al. * Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details * See LICENSE and AUTHORS files for details
*/ */
declare(strict_types=1); declare(strict_types=1);
@ -35,7 +35,7 @@ class PlainTextHandler extends Handler {
if ($o['outputCode'] & self::SILENT) { if ($o['outputCode'] & self::SILENT) {
continue; continue;
} }
$this->print($this->serializeOutputThrowable($o)); $this->print($this->serializeOutputThrowable($o));
} }
} }
@ -47,34 +47,35 @@ class PlainTextHandler extends Handler {
} }
protected function log(\Throwable $throwable, string $message): void { protected function log(\Throwable $throwable, string $message): void {
$context = [ 'exception' => $throwable ];
if ($throwable instanceof \Error) { if ($throwable instanceof \Error) {
switch ($throwable->getCode()) { switch ($throwable->getCode()) {
case \E_NOTICE: case \E_NOTICE:
case \E_USER_NOTICE: case \E_USER_NOTICE:
case \E_STRICT: case \E_STRICT:
$this->_logger->notice($message); $this->_logger->notice($message, $context);
break; break;
case \E_WARNING: case \E_WARNING:
case \E_COMPILE_WARNING: case \E_COMPILE_WARNING:
case \E_USER_WARNING: case \E_USER_WARNING:
case \E_DEPRECATED: case \E_DEPRECATED:
case \E_USER_DEPRECATED: case \E_USER_DEPRECATED:
$this->_logger->warning($message); $this->_logger->warning($message, $context);
break; break;
case \E_RECOVERABLE_ERROR: case \E_RECOVERABLE_ERROR:
$this->_logger->error($message); $this->_logger->error($message, $context);
break; break;
case \E_PARSE: case \E_PARSE:
case \E_CORE_ERROR: case \E_CORE_ERROR:
case \E_COMPILE_ERROR: case \E_COMPILE_ERROR:
$this->_logger->alert($message); $this->_logger->alert($message, $context);
break; break;
default: $this->_logger->critical($message); default: $this->_logger->critical($message, $context);
} }
} elseif ($throwable instanceof \Exception && ($throwable instanceof \PharException || $throwable instanceof \RuntimeException)) { } elseif ($throwable instanceof \Exception && ($throwable instanceof \PharException || $throwable instanceof \RuntimeException)) {
$this->_logger->alert($message); $this->_logger->alert($message, $context);
} else { } else {
$this->_logger->critical($message); $this->_logger->critical($message, $context);
} }
} }
@ -83,7 +84,7 @@ class PlainTextHandler extends Handler {
if ($class !== null && !empty($outputThrowable['errorType'])) { if ($class !== null && !empty($outputThrowable['errorType'])) {
$class = "{$outputThrowable['errorType']} ($class)"; $class = "{$outputThrowable['errorType']} ($class)";
} }
$output = sprintf( $output = sprintf(
'%s: %s in file %s on line %d' . \PHP_EOL, '%s: %s in file %s on line %d' . \PHP_EOL,
$class, $class,
@ -118,7 +119,7 @@ class PlainTextHandler extends Handler {
} elseif (!empty($frame['function'])) { } elseif (!empty($frame['function'])) {
$method = $frame['function']; $method = $frame['function'];
} }
$output .= sprintf("%{$maxDigits}d. %s %s:%d" . \PHP_EOL, $output .= sprintf("%{$maxDigits}d. %s %s:%d" . \PHP_EOL,
$key + 1, $key + 1,
$method, $method,

24
lib/Catcher/ThrowableController.php

@ -1,8 +1,8 @@
<?php <?php
/** /**
* @license MIT * @license MIT
* Copyright 2022 Dustin Wilson, et al. * Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details * See LICENSE and AUTHORS files for details
*/ */
declare(strict_types=1); declare(strict_types=1);
@ -37,7 +37,7 @@ class ThrowableController {
} }
switch ($this->throwable->getCode()) { switch ($this->throwable->getCode()) {
case \E_ERROR: case \E_ERROR:
$this->errorType = 'PHP Fatal Error'; $this->errorType = 'PHP Fatal Error';
break; break;
case \E_WARNING: case \E_WARNING:
@ -49,7 +49,7 @@ class ThrowableController {
case \E_NOTICE: case \E_NOTICE:
$this->errorType = 'PHP Notice'; $this->errorType = 'PHP Notice';
break; break;
case \E_CORE_ERROR: case \E_CORE_ERROR:
$this->errorType = 'PHP Core Error'; $this->errorType = 'PHP Core Error';
break; break;
case \E_CORE_WARNING: case \E_CORE_WARNING:
@ -99,17 +99,17 @@ class ThrowableController {
if ( if (
!$this->throwable instanceof \Error || !$this->throwable instanceof \Error ||
!in_array($this->throwable->getCode(), [ E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING ]) || !in_array($this->throwable->getCode(), [ E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING ]) ||
!extension_loaded('xdebug') || !extension_loaded('xdebug') ||
!function_exists('xdebug_info') || !function_exists('xdebug_info') ||
sizeof(\xdebug_info('mode')) === 0 sizeof(xdebug_info('mode')) === 0
) { ) {
$frames = $this->throwable->getTrace(); $frames = $this->throwable->getTrace();
} else { } else {
$frames = array_values(array_diff_key(xdebug_get_function_stack(), debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS))); $frames = array_values(array_diff_key(xdebug_get_function_stack(), debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)));
} }
// PHP for some stupid reason thinks it's okay not to provide line numbers and file // PHP for some stupid reason thinks it's okay not to provide line numbers and file
// names when using call_user_func_array; this fixes that. // names when using call_user_func_array; this fixes that.
// (https://bugs.php.net/bug.php?id=44428) // (https://bugs.php.net/bug.php?id=44428)
foreach ($frames as $key => $frame) { foreach ($frames as $key => $frame) {
if (empty($frame['file'])) { if (empty($frame['file'])) {
@ -119,9 +119,9 @@ class ThrowableController {
$next = $frames[$key + 1] ?? []; $next = $frames[$key + 1] ?? [];
if ( if (
!empty($next['file']) && !empty($next['file']) &&
!empty($next['function']) && !empty($next['function']) &&
!empty($next['line']) && !empty($next['line']) &&
str_contains($next['function'], 'call_user_func') str_contains($next['function'], 'call_user_func')
) { ) {
$file = $next['file']; $file = $next['file'];
@ -164,7 +164,7 @@ class ThrowableController {
if ($this->throwable instanceof \Error) { if ($this->throwable instanceof \Error) {
$errorType = $this->getErrorType(); $errorType = $this->getErrorType();
if ($errorType !== null) { if ($errorType !== null) {
$f['code'] = $this->throwable->getCode(); $f['code'] = $this->throwable->getCode();
$f['errorType'] = $errorType; $f['errorType'] = $errorType;
} }
} }

4
run

@ -10,7 +10,7 @@ function help($error = true): void {
Usage: Usage:
run test [additional_phpunit_options] run test [additional_phpunit_options]
run --help run --help
USAGE; USAGE;
if ($error) { if ($error) {
@ -48,7 +48,7 @@ switch ($argv[1]) {
case '--help': case '--help':
help(false); help(false);
break; break;
default: default:
help(); help();
} }

Loading…
Cancel
Save