Browse Source

Modified supplemental error handling

2.1.0
Dustin Wilson 9 months ago
parent
commit
6e8ac67d4e
  1. 19
      lib/Catcher.php
  2. 11
      lib/Catcher/Handler.php
  3. 60
      tests/cases/TestHandler.php
  4. 6
      tests/lib/FailLogger.php

19
lib/Catcher.php

@ -59,6 +59,14 @@ class Catcher {
/** Checks if the error code is fatal */
public static function isErrorFatal(int $code): bool {
return in_array($code, [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR, \E_RECOVERABLE_ERROR ]);
}
public function getHandlers(): array { public function getHandlers(): array {
return $this->handlers; return $this->handlers;
} }
@ -159,7 +167,7 @@ class Catcher {
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->errorHandlingMethod > self::THROW_NO_ERRORS && ($this->errorHandlingMethod === self::THROW_ALL_ERRORS || $this->isErrorFatal($code)) && !$this->isShuttingDown) { if ($this->errorHandlingMethod > self::THROW_NO_ERRORS && ($this->errorHandlingMethod === self::THROW_ALL_ERRORS || self::isErrorFatal($code)) && !$this->isShuttingDown) {
$this->lastThrowable = $error; $this->lastThrowable = $error;
throw $error; throw $error;
} else { } else {
@ -198,7 +206,7 @@ class Catcher {
$exit || $exit ||
$this->isShuttingDown || $this->isShuttingDown ||
$throwable instanceof \Exception || $throwable instanceof \Exception ||
($throwable instanceof Error && $this->isErrorFatal($throwable->getCode())) || ($throwable instanceof Error && self::isErrorFatal($throwable->getCode())) ||
(!$throwable instanceof Error && $throwable instanceof \Error) (!$throwable instanceof Error && $throwable instanceof \Error)
) { ) {
foreach ($this->handlers as $h) { foreach ($this->handlers as $h) {
@ -230,7 +238,7 @@ class Catcher {
$this->isShuttingDown = true; $this->isShuttingDown = true;
if ($error = $this->getLastError()) { if ($error = $this->getLastError()) {
if ($this->isErrorFatal($error['type'])) { if (self::isErrorFatal($error['type'])) {
$errorReporting = error_reporting(); $errorReporting = error_reporting();
if ($this->errorReporting !== null && $this->errorReporting === $errorReporting && ($this->errorReporting & \E_ERROR) === 0) { if ($this->errorReporting !== null && $this->errorReporting === $errorReporting && ($this->errorReporting & \E_ERROR) === 0) {
error_reporting($errorReporting | \E_ERROR); error_reporting($errorReporting | \E_ERROR);
@ -251,11 +259,6 @@ class Catcher {
exit($status); // @codeCoverageIgnore exit($status); // @codeCoverageIgnore
} }
/** Checks if the error code is fatal */
protected function isErrorFatal(int $code): bool {
return in_array($code, [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR, \E_RECOVERABLE_ERROR ]);
}
/** Exists so the method may be replaced when mocking in tests */ /** Exists so the method may be replaced when mocking in tests */
protected function getLastError(): ?array { protected function getLastError(): ?array {
return error_get_last(); return error_get_last();

11
lib/Catcher/Handler.php

@ -153,7 +153,7 @@ abstract class Handler {
set_exception_handler($exceptionHandler); set_exception_handler($exceptionHandler);
// If the current exception handler happens to not be Catcher use PHP's handler // If the current exception handler happens to not be Catcher use PHP's handler
// instead; this shouldn't happen in normal operation but is here just in case // instead.
if (!is_array($exceptionHandler) || !$exceptionHandler[0] instanceof Catcher) { if (!is_array($exceptionHandler) || !$exceptionHandler[0] instanceof Catcher) {
return false; return false;
} }
@ -174,11 +174,10 @@ abstract class Handler {
// If all of the handlers are silent then use PHP's handler instead; this is // If all of the handlers are silent then use PHP's handler instead; this is
// because a valid use for Catcher is to have it be silent but instead have the // because a valid use for Catcher is to have it be silent but instead have the
// logger print the errors to stderr/stdout; if there is an error in the logger // logger print the errors to stderr/stdout. This should only apply to fatal
// then it wouldn't print. // errors; this shouldn't happen in normal operation but is here just in case
if ($silentCount === $handlersCount) { if (Catcher::isErrorFatal($code) && $silentCount === $handlersCount) {
// TODO: Output an error here to state that Catcher failed? return false; //@codeCoverageIgnore
return false;
} }
$catcher->handleError($code, $message, $file, $line); $catcher->handleError($code, $message, $file, $line);

60
tests/cases/TestHandler.php

@ -8,7 +8,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace MensBeam\Catcher\Test; namespace MensBeam\Catcher\Test;
use MensBeam\Catcher\{ use MensBeam\Catcher\{
Error, Error as CatcherError,
Handler, Handler,
RangeException, RangeException,
ThrowableController ThrowableController
@ -128,9 +128,10 @@ class TestHandler extends ErrorHandlingTestCase {
} }
public function testFatalError(): void { /** @dataProvider provideFatalErrorTests */
$this->expectException(RangeException::class); public function testFatalErrors(string $throwableClassName, \Closure $closure): void {
$this->handler->setOption('httpCode', 42); $this->expectException($throwableClassName);
$closure($this->handler);
} }
/** @dataProvider provideNonFatalErrorTests */ /** @dataProvider provideNonFatalErrorTests */
@ -141,6 +142,29 @@ class TestHandler extends ErrorHandlingTestCase {
} }
public static function provideFatalErrorTests(): iterable {
$iterable = [
[
RangeException::class,
function (Handler $h): void {
$h->setOption('httpCode', 42);
}
],
[
CatcherError::class,
function (Handler $h): void {
$c = new Catcher();
$l = new FailLogger();
$l->error('Ook!');
}
]
];
foreach ($iterable as $i) {
yield $i;
}
}
public static function provideArgumentSerializationTests(): iterable { public static function provideArgumentSerializationTests(): iterable {
$options = [ $options = [
[ fn() => true ], [ fn() => true ],
@ -162,7 +186,7 @@ class TestHandler extends ErrorHandlingTestCase {
[ new \Exception('Ook!'), Handler::BUBBLES | Handler::OUTPUT | Handler::EXIT, [ 'forceExit' => true ] ], [ new \Exception('Ook!'), Handler::BUBBLES | Handler::OUTPUT | Handler::EXIT, [ 'forceExit' => true ] ],
[ new \Error('Ook!'), Handler::BUBBLES | Handler::OUTPUT ], [ new \Error('Ook!'), Handler::BUBBLES | Handler::OUTPUT ],
[ new \Exception('Ook!'), Handler::BUBBLES, [ 'silent' => true ] ], [ new \Exception('Ook!'), Handler::BUBBLES, [ 'silent' => true ] ],
[ new Error('Ook!', \E_ERROR, '/dev/null', 42, new \Error('Eek!')), Handler::BUBBLES | Handler::OUTPUT | Handler::NOW, [ 'forceOutputNow' => true ] ], [ new CatcherError('Ook!', \E_ERROR, '/dev/null', 42, new \Error('Eek!')), Handler::BUBBLES | Handler::OUTPUT | Handler::NOW, [ 'forceOutputNow' => true ] ],
[ new \Exception('Ook!'), Handler::BUBBLES, [ 'silent' => true, 'logger' => Phake::mock(LoggerInterface::class), 'logWhenSilent' => false ] ], [ new \Exception('Ook!'), Handler::BUBBLES, [ 'silent' => true, 'logger' => Phake::mock(LoggerInterface::class), 'logWhenSilent' => false ] ],
[ new \Error('Ook!'), Handler::BUBBLES | Handler::OUTPUT | Handler::LOG, [ 'forceOutputNow' => true, 'logger' => Phake::mock(LoggerInterface::class) ] ] [ new \Error('Ook!'), Handler::BUBBLES | Handler::OUTPUT | Handler::LOG, [ 'forceOutputNow' => true, 'logger' => Phake::mock(LoggerInterface::class) ] ]
]; ];
@ -178,19 +202,19 @@ class TestHandler extends ErrorHandlingTestCase {
public static function provideLogTests(): iterable { public static function provideLogTests(): iterable {
$options = [ $options = [
[ new Error('Ook!', \E_NOTICE, '/dev/null', 0, new \Error('Eek!')), 'notice' ], [ new CatcherError('Ook!', \E_NOTICE, '/dev/null', 0, new \Error('Eek!')), 'notice' ],
[ new Error('Ook!', \E_USER_NOTICE, '/dev/null', 0), 'notice' ], [ new CatcherError('Ook!', \E_USER_NOTICE, '/dev/null', 0), 'notice' ],
[ new Error('Ook!', \E_STRICT, '/dev/null', 0, new \Error('Eek!')), 'notice' ], [ new CatcherError('Ook!', \E_STRICT, '/dev/null', 0, new \Error('Eek!')), 'notice' ],
[ new Error('Ook!', \E_WARNING, '/dev/null', 0), 'warning' ], [ new CatcherError('Ook!', \E_WARNING, '/dev/null', 0), 'warning' ],
[ new Error('Ook!', \E_COMPILE_WARNING, '/dev/null', 0, new \Error('Eek!')), 'warning' ], [ new CatcherError('Ook!', \E_COMPILE_WARNING, '/dev/null', 0, new \Error('Eek!')), 'warning' ],
[ new Error('Ook!', \E_USER_WARNING, '/dev/null', 0), 'warning' ], [ new CatcherError('Ook!', \E_USER_WARNING, '/dev/null', 0), 'warning' ],
[ new Error('Ook!', \E_DEPRECATED, '/dev/null', 0, new \Error('Eek!')), 'warning' ], [ new CatcherError('Ook!', \E_DEPRECATED, '/dev/null', 0, new \Error('Eek!')), 'warning' ],
[ new Error('Ook!', \E_USER_DEPRECATED, '/dev/null', 0), 'warning' ], [ new CatcherError('Ook!', \E_USER_DEPRECATED, '/dev/null', 0), 'warning' ],
[ new Error('Ook!', \E_PARSE, '/dev/null', 0, new \Error('Eek!')), 'critical' ], [ new CatcherError('Ook!', \E_PARSE, '/dev/null', 0, new \Error('Eek!')), 'critical' ],
[ new Error('Ook!', \E_CORE_ERROR, '/dev/null', 0), 'critical' ], [ new CatcherError('Ook!', \E_CORE_ERROR, '/dev/null', 0), 'critical' ],
[ new Error('Ook!', \E_COMPILE_ERROR, '/dev/null', 0, new \Error('Eek!')), 'critical' ], [ new CatcherError('Ook!', \E_COMPILE_ERROR, '/dev/null', 0, new \Error('Eek!')), 'critical' ],
[ new Error('Ook!', \E_ERROR, '/dev/null', 0), 'error' ], [ new CatcherError('Ook!', \E_ERROR, '/dev/null', 0), 'error' ],
[ new Error('Ook!', \E_USER_ERROR, '/dev/null', 0, new \Error('Eek!')), 'error' ], [ new CatcherError('Ook!', \E_USER_ERROR, '/dev/null', 0, new \Error('Eek!')), 'error' ],
[ new \PharException('Ook!'), 'alert' ], [ new \PharException('Ook!'), 'alert' ],
[ new \Exception('Ook!'), 'critical' ], [ new \Exception('Ook!'), 'critical' ],
]; ];

6
tests/lib/FailLogger.php

@ -19,5 +19,11 @@ class FailLogger implements LoggerInterface {
$v = vfsStream::setup('ook'); $v = vfsStream::setup('ook');
$d = vfsStream::newDirectory('ook', 0777)->at($v); $d = vfsStream::newDirectory('ook', 0777)->at($v);
file_put_contents($d->url(), $message); file_put_contents($d->url(), $message);
echo "Ook!\n";
}
public function error(string|\Stringable $message, array $context = []): void {
trigger_error('Ook!', \E_USER_ERROR);
} }
} }

Loading…
Cancel
Save