Added ignore option to Handler
This commit is contained in:
parent
ceb372d780
commit
6d23922b81
6 changed files with 130 additions and 52 deletions
|
@ -190,6 +190,7 @@ abstract class Handler {
|
|||
protected bool $_forceExit = false;
|
||||
protected bool $_forceOutputNow = false;
|
||||
protected int $_httpCode = 500;
|
||||
protected ?array $_ignore = null;
|
||||
protected ?LoggerInterface $_logger = null;
|
||||
protected bool $_logWhenSilent = true;
|
||||
protected bool $_outputBacktrace = false;
|
||||
|
@ -241,6 +242,7 @@ _forceBreak_: When set this will force the stack loop to break after the handler
|
|||
_forceExit_: When set this will force an exit after all handlers have been run. Defaults to _false_.
|
||||
_forceOutputNow_: When set this will force output of the handler immediately. Defaults to _false_.
|
||||
_httpCode_: The HTTP code to be sent; possible values are 200, 400-599. Defaults to _500_.
|
||||
_ignore_: An array of class strings or error codes to ignore. Defaults to _null_ (no ignoring).
|
||||
_logger_: The PSR-3 compatible logger in which to log to. Defaults to _null_ (no logging).
|
||||
_logWhenSilent_: When set to true the handler will still send logs when silent. Defaults to _true_.
|
||||
_outputBacktrace_: When true will output a stack trace. Defaults to _false_.
|
||||
|
|
|
@ -50,6 +50,11 @@ abstract class Handler {
|
|||
protected bool $_forceOutputNow = false;
|
||||
/** The HTTP code to be sent; possible values: 200, 400-599 */
|
||||
protected int $_httpCode = 500;
|
||||
/**
|
||||
* An array of class strings or error codes to ignore
|
||||
* @var int[]|string[]
|
||||
*/
|
||||
protected array $_ignore = [];
|
||||
/** The PSR-3 compatible logger in which to log to; defaults to null (no logging) */
|
||||
protected ?LoggerInterface $_logger = null;
|
||||
/** When set to true the handler will still send logs when silent */
|
||||
|
@ -117,6 +122,38 @@ abstract class Handler {
|
|||
}
|
||||
|
||||
public function handle(ThrowableController $controller): array {
|
||||
$ignore = false;
|
||||
if (count($this->_ignore) > 0) {
|
||||
$throwable = $controller->getThrowable();
|
||||
foreach ($this->_ignore as $i) {
|
||||
if (($throwable instanceof Error && is_int($i) && $throwable->getCode() === $i) || (is_string($i) && $throwable instanceof $i)) {
|
||||
$ignore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$code = 0;
|
||||
if ($this->_bubbles) {
|
||||
$code = self::BUBBLES;
|
||||
}
|
||||
if (!$ignore) {
|
||||
if ($this->_forceExit) {
|
||||
$code |= self::EXIT;
|
||||
}
|
||||
if ($this->_logger !== null && (!$this->_silent || ($this->_silent && $this->_logWhenSilent))) {
|
||||
$code |= self::LOG;
|
||||
}
|
||||
if ($this->_forceOutputNow) {
|
||||
$code |= self::NOW;
|
||||
}
|
||||
if (!$this->_silent) {
|
||||
$code |= self::OUTPUT;
|
||||
}
|
||||
} else {
|
||||
return [ 'code' => $code ];
|
||||
}
|
||||
|
||||
$output = $this->buildOutputArray($controller);
|
||||
|
||||
if ($this->_outputBacktrace) {
|
||||
|
@ -125,23 +162,6 @@ abstract class Handler {
|
|||
if ($this->_outputTime && $this->_timeFormat !== '') {
|
||||
$output['time'] = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
$code = 0;
|
||||
if ($this->_bubbles) {
|
||||
$code = self::BUBBLES;
|
||||
}
|
||||
if ($this->_forceExit) {
|
||||
$code |= self::EXIT;
|
||||
}
|
||||
if ($this->_logger !== null && (!$this->_silent || ($this->_silent && $this->_logWhenSilent))) {
|
||||
$code |= self::LOG;
|
||||
}
|
||||
if ($this->_forceOutputNow) {
|
||||
$code |= self::NOW;
|
||||
}
|
||||
if (!$this->_silent) {
|
||||
$code |= self::OUTPUT;
|
||||
}
|
||||
$output['code'] = $code;
|
||||
|
||||
$output = $this->handleCallback($output);
|
||||
|
@ -205,20 +225,33 @@ abstract class Handler {
|
|||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$name === 'httpCode' &&
|
||||
is_int($value) &&
|
||||
$value !== 200 &&
|
||||
max(400, min($value, 418)) !== $value &&
|
||||
max(421, min($value, 429)) !== $value &&
|
||||
$value !== 431 &&
|
||||
$value !== 451 &&
|
||||
max(500, min($value, 511)) !== $value &&
|
||||
// Cloudflare extensions
|
||||
max(520, min($value, 527)) !== $value &&
|
||||
$value !== 530
|
||||
) {
|
||||
throw new RangeException('Option "httpCode" can only be a valid HTTP 200, 4XX, or 5XX code');
|
||||
switch ($name) {
|
||||
case 'httpCode':
|
||||
if (
|
||||
is_int($value) &&
|
||||
$value !== 200 &&
|
||||
max(400, min($value, 418)) !== $value &&
|
||||
max(421, min($value, 429)) !== $value &&
|
||||
$value !== 431 &&
|
||||
$value !== 451 &&
|
||||
max(500, min($value, 511)) !== $value &&
|
||||
// Cloudflare extensions
|
||||
max(520, min($value, 527)) !== $value &&
|
||||
$value !== 530
|
||||
) {
|
||||
throw new RangeException('Option "httpCode" can only be a valid HTTP 200, 4XX, or 5XX code');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ignore':
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
if (!is_int($v) && !is_string($v)) {
|
||||
throw new InvalidArgumentException('Option "ignore" can only be an array of integers and/or strings');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$name = "_$name";
|
||||
|
@ -254,6 +287,7 @@ abstract class Handler {
|
|||
|
||||
protected function cleanOutputThrowable(array $outputThrowable): array {
|
||||
unset($outputThrowable['controller']);
|
||||
unset($outputThrowable['code']);
|
||||
|
||||
if (isset($outputThrowable['previous'])) {
|
||||
$outputThrowable['previous'] = $this->cleanOutputThrowable($outputThrowable['previous']);
|
||||
|
|
11
lib/Catcher/InvalidArgumentException.php
Normal file
11
lib/Catcher/InvalidArgumentException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
/**
|
||||
* @license MIT
|
||||
* Copyright 2022 Dustin Wilson, et al.
|
||||
* See LICENSE and AUTHORS files for details
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace MensBeam\Catcher;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException {}
|
|
@ -10,6 +10,7 @@ namespace MensBeam\Catcher\Test;
|
|||
use MensBeam\Catcher\{
|
||||
Error as CatcherError,
|
||||
Handler,
|
||||
InvalidArgumentException,
|
||||
RangeException,
|
||||
ThrowableController
|
||||
};
|
||||
|
@ -62,15 +63,12 @@ class TestHandler extends ErrorHandlingTestCase {
|
|||
$r = new \ReflectionProperty($this->handler::class, 'outputBuffer');
|
||||
$r->setAccessible(true);
|
||||
$this->assertEquals(1, count($r->getValue($this->handler)));
|
||||
$this->assertNull($this->handler->getLastOutputThrowable());
|
||||
|
||||
$h = $this->handler;
|
||||
$h();
|
||||
$this->assertEquals(0, count($r->getValue($h)));
|
||||
$this->assertSame(\Exception::class, $h->getLastOutputThrowable()['class']);
|
||||
$this->assertEquals(0, count($r->getValue($this->handler)));
|
||||
$h();
|
||||
$this->assertEquals(0, count($r->getValue($h)));
|
||||
$this->assertSame(\Exception::class, $h->getLastOutputThrowable()['class']);
|
||||
$this->assertEquals(0, count($r->getValue($this->handler)));
|
||||
}
|
||||
|
||||
/** @dataProvider provideLogTests */
|
||||
|
@ -160,6 +158,12 @@ class TestHandler extends ErrorHandlingTestCase {
|
|||
$l = new FailLogger();
|
||||
$l->error('Ook!');
|
||||
}
|
||||
],
|
||||
[
|
||||
InvalidArgumentException::class,
|
||||
function (Handler $h): void {
|
||||
$h->setOption('ignore', [ \M_PI ]);
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -260,6 +264,7 @@ class TestHandler extends ErrorHandlingTestCase {
|
|||
[ 'httpCode', 200 ],
|
||||
[ 'httpCode', 400 ],
|
||||
[ 'httpCode', 502 ],
|
||||
[ 'ignore', [ \Exception::class, \E_USER_ERROR ] ],
|
||||
[ 'logger', Phake::mock(LoggerInterface::class) ],
|
||||
[ 'logWhenSilent', false ],
|
||||
[ 'outputBacktrace', true ],
|
||||
|
|
|
@ -17,7 +17,10 @@ use Psr\Log\LoggerInterface,
|
|||
Phake;
|
||||
|
||||
|
||||
/** @covers \MensBeam\Catcher\JSONHandler */
|
||||
/**
|
||||
* @covers \MensBeam\Catcher\JSONHandler
|
||||
* @covers \MensBeam\Catcher\Handler
|
||||
*/
|
||||
class TestJSONHandler extends \PHPUnit\Framework\TestCase {
|
||||
protected ?Handler $handler = null;
|
||||
|
||||
|
@ -32,7 +35,7 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
|
|||
}
|
||||
|
||||
/** @dataProvider provideInvocationTests */
|
||||
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, int $line): void {
|
||||
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, ?array $ignore, int $line): void {
|
||||
$this->handler->setOption('outputToStderr', false);
|
||||
|
||||
if (!$silent) {
|
||||
|
@ -42,6 +45,9 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
|
|||
$l = Phake::mock(LoggerInterface::class);
|
||||
$this->handler->setOption('logger', $l);
|
||||
}
|
||||
if ($ignore !== null) {
|
||||
$this->handler->setOption('ignore', $ignore);
|
||||
}
|
||||
|
||||
$o = $this->handler->handle(new ThrowableController($throwable));
|
||||
$c = $o['class'] ?? null;
|
||||
|
@ -51,17 +57,20 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
|
|||
$h();
|
||||
$u = ob_get_clean();
|
||||
|
||||
if (!$silent) {
|
||||
if (!$silent && $ignore === null) {
|
||||
$u = json_decode($u, true);
|
||||
$this->assertEquals($c, $u['class']);
|
||||
$this->assertEquals(__FILE__, $u['file']);
|
||||
$this->assertEquals($line, $u['line']);
|
||||
} else {
|
||||
if ($ignore !== null) {
|
||||
$this->assertNull($h->getLastOutputThrowable());
|
||||
}
|
||||
$this->assertSame('', $u);
|
||||
}
|
||||
|
||||
if ($log) {
|
||||
Phake::verify($l, Phake::times(1))->$logMethodName;
|
||||
Phake::verify($l, Phake::times((int)(count($ignore ?? []) === 0)))->$logMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,10 +92,14 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
|
|||
|
||||
public static function provideInvocationTests(): iterable {
|
||||
$options = [
|
||||
[ new \Exception('Ook!'), false, true, 'critical' ],
|
||||
[ new \Error('Ook!'), true, false, null ],
|
||||
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error' ],
|
||||
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical' ]
|
||||
[ new \Exception('Ook!'), false, true, 'critical', null ],
|
||||
[ new \Exception('Ook!'), false, true, 'critical', [ \Exception::class ] ],
|
||||
[ new \Error('Ook!'), true, false, null, null ],
|
||||
[ new \Error('Ook!'), true, false, null, [ \Error::class ] ],
|
||||
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', null ],
|
||||
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', [ \E_ERROR ] ],
|
||||
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', null ],
|
||||
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', [ \Exception::class ] ]
|
||||
];
|
||||
|
||||
$l = count($options);
|
||||
|
|
|
@ -17,7 +17,10 @@ use Psr\Log\LoggerInterface,
|
|||
Phake;
|
||||
|
||||
|
||||
/** @covers \MensBeam\Catcher\PlainTextHandler */
|
||||
/**
|
||||
* @covers \MensBeam\Catcher\PlainTextHandler
|
||||
* @covers \MensBeam\Catcher\Handler
|
||||
*/
|
||||
class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
|
||||
protected ?Handler $handler = null;
|
||||
|
||||
|
@ -32,7 +35,7 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
|
|||
}
|
||||
|
||||
/** @dataProvider provideInvocationTests */
|
||||
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, int $line): void {
|
||||
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, ?array $ignore, int $line): void {
|
||||
$this->handler->setOption('outputToStderr', false);
|
||||
|
||||
if (!$silent) {
|
||||
|
@ -42,6 +45,9 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
|
|||
$l = Phake::mock(LoggerInterface::class);
|
||||
$this->handler->setOption('logger', $l);
|
||||
}
|
||||
if ($ignore !== null) {
|
||||
$this->handler->setOption('ignore', $ignore);
|
||||
}
|
||||
|
||||
$o = $this->handler->handle(new ThrowableController($throwable));
|
||||
|
||||
|
@ -56,14 +62,17 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
|
|||
$u = ob_get_clean();
|
||||
$u = substr($u, 0, strpos($u, \PHP_EOL) ?: 0);
|
||||
|
||||
if (!$silent) {
|
||||
if (!$silent && $ignore === null) {
|
||||
$this->assertMatchesRegularExpression(sprintf('/^\[[\d:]+\] %s: Ook\! in file %s on line %s$/', preg_quote($c, '/'), preg_quote(__FILE__, '/'), $line), $u);
|
||||
} else {
|
||||
if ($ignore !== null) {
|
||||
$this->assertNull($h->getLastOutputThrowable());
|
||||
}
|
||||
$this->assertSame('', $u);
|
||||
}
|
||||
|
||||
if ($log) {
|
||||
Phake::verify($l, Phake::times(1))->$logMethodName;
|
||||
Phake::verify($l, Phake::times((int)(count($ignore ?? []) === 0)))->$logMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,10 +94,14 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
|
|||
|
||||
public static function provideInvocationTests(): iterable {
|
||||
$options = [
|
||||
[ new \Exception('Ook!'), false, true, 'critical' ],
|
||||
[ new \Error('Ook!'), true, false, null ],
|
||||
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error' ],
|
||||
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical' ]
|
||||
[ new \Exception('Ook!'), false, true, 'critical', null ],
|
||||
[ new \Exception('Ook!'), false, true, 'critical', [ \Exception::class ] ],
|
||||
[ new \Error('Ook!'), true, false, null, null ],
|
||||
[ new \Error('Ook!'), true, false, null, [ \Error::class ] ],
|
||||
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', null ],
|
||||
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', [ \E_ERROR ] ],
|
||||
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', null ],
|
||||
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', [ \Exception::class ] ]
|
||||
];
|
||||
|
||||
$l = count($options);
|
||||
|
|
Loading…
Add table
Reference in a new issue