diff --git a/src/FormatNegotiator.php b/src/FormatNegotiator.php index e0febad..3f0c807 100644 --- a/src/FormatNegotiator.php +++ b/src/FormatNegotiator.php @@ -44,7 +44,7 @@ public static function getPreferredFormat(ServerRequestInterface $request): stri $acceptType = $acceptTypes[0]; // as many formats may match for a given Accept header, - // look for the one that fits the best + // look for the one that fits the best $counters = []; foreach (self::$formats as $format => $values) { foreach ($values as $value) { diff --git a/src/WhoopsErrorHandler.php b/src/WhoopsErrorHandler.php index fb83720..ed1286c 100644 --- a/src/WhoopsErrorHandler.php +++ b/src/WhoopsErrorHandler.php @@ -28,218 +28,218 @@ */ class WhoopsErrorHandler implements MiddlewareInterface { - use CallableMiddlewareTrait; - - /** @var callable|string */ - private $outputFormat; - - /** @var bool */ - private $handleGlobalErrors; - - /** @var bool */ - private $showTrace; - - /** - * @param callable|string $outputFormat (optional) - * @param bool $handleGlobalErrors (optional) - * @param bool $showTrace (optional) - */ - public function __construct( - $outputFormat = 'auto', - bool $handleGlobalErrors = false, - bool $showTrace = false - ) - { - $this->outputFormat = $outputFormat; - $this->handleGlobalErrors = $handleGlobalErrors; - $this->showTrace = $showTrace; - } - - /** + use CallableMiddlewareTrait; + + /** @var callable|string */ + private $outputFormat; + + /** @var bool */ + private $handleGlobalErrors; + + /** @var bool */ + private $showTrace; + + /** + * @param callable|string $outputFormat (optional) + * @param bool $handleGlobalErrors (optional) + * @param bool $showTrace (optional) + */ + public function __construct( + $outputFormat = 'auto', + bool $handleGlobalErrors = false, + bool $showTrace = false + ) + { + $this->outputFormat = $outputFormat; + $this->handleGlobalErrors = $handleGlobalErrors; + $this->showTrace = $showTrace; + } + + /** * {@inheritdoc} - * - * @throws \TypeError - * @throws \UnexpectedValueException + * + * @throws \TypeError + * @throws \UnexpectedValueException */ public function process( - ServerRequestInterface $request, - RequestHandlerInterface $handler - ): ResponseInterface + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { - // unregister all previously registered handlers - $whoops = (self::getWhoopsInstance($request, $this->outputFormat, $this->showTrace)); - $whoops->popHandler(); - $whoops->unregister(); - - // handle all PHP errors globally? - if ($this->handleGlobalErrors) { - // register whoops handler - $this->handleThrowable(null, $request); - - // handle all subsequent requests, if any - return $handler->handle($request); - } - - // check if there are other middlewares in the chain that need to be processed - // (i.e. the error handler was setup as a part of the current chain); in such - // instance handle errors specifically within the middleware chain - set_error_handler($this->createErrorHandler($request)); + // unregister all previously registered handlers + $whoops = (self::getWhoopsInstance($request, $this->outputFormat, $this->showTrace)); + $whoops->popHandler(); + $whoops->unregister(); + + // handle all PHP errors globally? + if ($this->handleGlobalErrors) { + // register whoops handler + $this->handleThrowable(null, $request); + + // handle all subsequent requests, if any + return $handler->handle($request); + } + + // check if there are other middlewares in the chain that need to be processed + // (i.e. the error handler was setup as a part of the current chain); in such + // instance handle errors specifically within the middleware chain + set_error_handler($this->createErrorHandler($request)); - try { - if (! $request instanceof ServerRequestInterface) { - throw new \TypeError(sprintf( - 'Expecting a "%s" instance; instance of "%s" provided', - ServerRequestInterface::class, - get_class($request) - )); - } + try { + if (! $request instanceof ServerRequestInterface) { + throw new \TypeError(sprintf( + 'Expecting a "%s" instance; instance of "%s" provided', + ServerRequestInterface::class, + get_class($request) + )); + } - // continue processing all requests - $response = $handler->handle($request); + // continue processing all requests + $response = $handler->handle($request); - // invalid response received? - if (! ($response instanceof ResponseInterface)) { - throw new \UnexpectedValueException(sprintf( - 'Application must return a valid "%s" instance', - ResponseInterface::class - )); - } + // invalid response received? + if (! ($response instanceof ResponseInterface)) { + throw new \UnexpectedValueException(sprintf( + 'Application must return a valid "%s" instance', + ResponseInterface::class + )); + } - // catch errors if there are any - } catch (\Throwable | \Exception $e) { - $response = $this->handleThrowable($e, $request); - } + // catch errors if there are any + } catch (\Throwable | \Exception $e) { + $response = $this->handleThrowable($e, $request); + } - restore_error_handler(); - - return $response; + restore_error_handler(); + + return $response; } - - /** - * Get Whoops object instance. - * - * @param ServerRequestInterface $request - * @param string|callable $format (optional) - * @param bool $showTrace (optional) - * - * @return \Whoops\Run - */ + + /** + * Get Whoops object instance. + * + * @param ServerRequestInterface $request + * @param string|callable $format (optional) + * @param bool $showTrace (optional) + * + * @return \Whoops\Run + */ public static function getWhoopsInstance( - ServerRequestInterface $request, - $format = 'auto', - bool $showTrace = false - ): Run + ServerRequestInterface $request, + $format = 'auto', + bool $showTrace = false + ): Run { $whoops = new Run(); - - // custom handler specified? - if (is_callable($format)) { - $handler = $format; - } else { - // select appropriate handler as per the requested format - $format = ((PHP_SAPI === 'cli')) ? 'text' : ( - ($format === 'auto') ? ( - (\Whoops\Util\Misc::isAjaxRequest()) ? 'json' : FormatNegotiator::getPreferredFormat($request) - ) : $format - ); + + // custom handler specified? + if (is_callable($format)) { + $handler = $format; + } else { + // select appropriate handler as per the requested format + $format = ((PHP_SAPI === 'cli')) ? 'text' : ( + ($format === 'auto') ? ( + (\Whoops\Util\Misc::isAjaxRequest()) ? 'json' : FormatNegotiator::getPreferredFormat($request) + ) : $format + ); - switch ($format) { - case 'json': - $handler = new JsonResponseHandler; - $handler->addTraceToOutput($showTrace); - $handler->setJsonApi(true); - break; - case 'html': - $handler = new PrettyPageHandler; - break; - case 'txt': - case 'text': - case 'plain': - $handler = new PlainTextHandler; - $handler->addTraceToOutput($showTrace); - break; - case 'xml': - $handler = new XmlResponseHandler; - $handler->addTraceToOutput($showTrace); - break; - default: - if (empty($format)) { - $handler = new PrettyPageHandler; - } else { - $handler = new PlainTextHandler; - $handler->addTraceToOutput($showTrace); - } - break; - } - - // extra attributes to add to whoops pretty page handler... - if ($handler instanceof PrettyPageHandler) { - $handler->addDataTable('Application/Request Data', [ - 'HTTP Method' => $request->getMethod(), - 'URI' => (string) $request->getUri(), - 'Script' => $request->getServerParams()['SCRIPT_NAME'], - 'Headers' => $request->getHeaders(), - 'Cookies' => $request->getCookieParams(), - 'Attributes' => $request->getAttributes(), - 'Query String Arguments' => $request->getQueryParams(), - 'Body Params' => $request->getParsedBody(), - ]); - } - } - - // register handler + switch ($format) { + case 'json': + $handler = new JsonResponseHandler; + $handler->addTraceToOutput($showTrace); + $handler->setJsonApi(true); + break; + case 'html': + $handler = new PrettyPageHandler; + break; + case 'txt': + case 'text': + case 'plain': + $handler = new PlainTextHandler; + $handler->addTraceToOutput($showTrace); + break; + case 'xml': + $handler = new XmlResponseHandler; + $handler->addTraceToOutput($showTrace); + break; + default: + if (empty($format)) { + $handler = new PrettyPageHandler; + } else { + $handler = new PlainTextHandler; + $handler->addTraceToOutput($showTrace); + } + break; + } + + // extra attributes to add to whoops pretty page handler... + if ($handler instanceof PrettyPageHandler) { + $handler->addDataTable('Application/Request Data', [ + 'HTTP Method' => $request->getMethod(), + 'URI' => (string) $request->getUri(), + 'Script' => $request->getServerParams()['SCRIPT_NAME'], + 'Headers' => $request->getHeaders(), + 'Cookies' => $request->getCookieParams(), + 'Attributes' => $request->getAttributes(), + 'Query String Arguments' => $request->getQueryParams(), + 'Body Params' => $request->getParsedBody(), + ]); + } + } + + // register handler $whoops->pushHandler($handler); - + return $whoops; } - - /** - * Handle caught exceptions/throwables. - * - * @param Exception|Throwable $error - * @param ServerRequestInterface $request - * - * @return ResponseInterface - */ - private function handleThrowable( - $error, - ServerRequestInterface $request - ): ResponseInterface + + /** + * Handle caught exceptions/throwables. + * + * @param Exception|Throwable $error + * @param ServerRequestInterface $request + * + * @return ResponseInterface + */ + private function handleThrowable( + $error, + ServerRequestInterface $request + ): ResponseInterface { - $whoops = self::getWhoopsInstance($request, $this->outputFormat, $this->showTrace); - - // note: php errors handled by Whoops are converted to ErrorException, but getCode() returns - // the same value as getSeverity() (e.g. E_WARNING would return 2) so we fix this by setting - // ErrorExceptions to code 500 - // @see https://github.com/filp/whoops/issues/267 - $initialCode = ($error === null || $error instanceof \ErrorException || ($code = $error->getCode()) < 100 || $code >= 600) ? 500 : $code; - + $whoops = self::getWhoopsInstance($request, $this->outputFormat, $this->showTrace); + + // note: php errors handled by Whoops are converted to ErrorException, but getCode() returns + // the same value as getSeverity() (e.g. E_WARNING would return 2) so we fix this by setting + // ErrorExceptions to code 500 + // @see https://github.com/filp/whoops/issues/267 + $initialCode = ($error === null || $error instanceof \ErrorException || ($code = $error->getCode()) < 100 || $code >= 600) ? 500 : $code; + // output is managed by the middleware pipeline $whoops->allowQuit((PHP_SAPI !== 'cli')); - $whoops->writeToOutput(true); - $whoops->sendHttpCode($initialCode); - - // register an internal whoops instance as an error/exception/shutdown handler - $whoopsInternalHandler = self::getWhoopsInstance($request, function($exception, $inspector, $error_handler) use ($whoops, $initialCode) { - // get exception code - $code = $exception->getCode(); + $whoops->writeToOutput(true); + $whoops->sendHttpCode($initialCode); + + // register an internal whoops instance as an error/exception/shutdown handler + $whoopsInternalHandler = self::getWhoopsInstance($request, function($exception, $inspector, $error_handler) use ($whoops, $initialCode) { + // get exception code + $code = $exception->getCode(); - $whoops->sendHttpCode(($initialCode !== $code && $code >= 100 && $code < 600 && ($initialCode === 500 || $initialCode === 200 || $initialCode < 100 || $initialCode >= 600)) ? $code : $initialCode); + $whoops->sendHttpCode(($initialCode !== $code && $code >= 100 && $code < 600 && ($initialCode === 500 || $initialCode === 200 || $initialCode < 100 || $initialCode >= 600)) ? $code : $initialCode); - $whoops->{Run::SHUTDOWN_HANDLER}(); - // handling specific errors within the error_reporting mask? - // delegate to whoops error handler method and output immediately - $whoops->{Run::EXCEPTION_HANDLER}($exception); + $whoops->{Run::SHUTDOWN_HANDLER}(); + // handling specific errors within the error_reporting mask? + // delegate to whoops error handler method and output immediately + $whoops->{Run::EXCEPTION_HANDLER}($exception); - return \Whoops\Handler\Handler::QUIT; - }, $this->showTrace); + return \Whoops\Handler\Handler::QUIT; + }, $this->showTrace); - $whoopsInternalHandler->register(); - - return \BitFrame\Factory\HttpMessageFactory::createResponse($initialCode); + $whoopsInternalHandler->register(); + + return \BitFrame\Factory\HttpMessageFactory::createResponse($initialCode); } - - /** + + /** * Creates and returns a callable error handler that raises exceptions. * * Only raises exceptions for errors that are within the error_reporting mask. @@ -248,27 +248,27 @@ private function handleThrowable( */ private function createErrorHandler(ServerRequestInterface $request): callable { - $whoops = self::getWhoopsInstance($request, $this->outputFormat, $this->showTrace); - $errorHandlerMethod = Run::ERROR_HANDLER; - + $whoops = self::getWhoopsInstance($request, $this->outputFormat, $this->showTrace); + $errorHandlerMethod = Run::ERROR_HANDLER; + /** * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline - * + * * @return void - * + * * @throws \ErrorException if error is not within the error_reporting mask. */ return function ($errno, $errstr, $errfile, $errline) use ($whoops, $errorHandlerMethod) - { - if (! (error_reporting() & $errno)) { + { + if (! (error_reporting() & $errno)) { // error_reporting does not include this error return; } - - $whoops->{$errorHandlerMethod}($errno, $errstr, $errfile, $errline); + + $whoops->{$errorHandlerMethod}($errno, $errstr, $errfile, $errline); }; } } diff --git a/test/WhoopsErrorHandlerTest.php b/test/WhoopsErrorHandlerTest.php index a7a25e9..c5b53b2 100644 --- a/test/WhoopsErrorHandlerTest.php +++ b/test/WhoopsErrorHandlerTest.php @@ -26,81 +26,81 @@ class WhoopsErrorHandlerTest extends TestCase { /** @var \BitFrame\ErrorHandler\WhoopsErrorHandler */ private $errorHandler; - + /** @var \Psr\Http\Message\ServerRequestInterface */ private $request; protected function setUp() { $this->errorHandler = new WhoopsErrorHandler(function() { - // do not output any error - return \Whoops\Handler\Handler::QUIT; - }); - $this->request = HttpMessageFactory::createServerRequest(); + // do not output any error + return \Whoops\Handler\Handler::QUIT; + }); + $this->request = HttpMessageFactory::createServerRequest(); } - public function testProcessMiddleware() + public function testProcessMiddleware() { - $handler = $this->getMockBuilder(RequestHandlerInterface::class)->setMethods(['handle'])->getMock(); - $handler->method('handle')->willReturn(HttpMessageFactory::createResponse(200)); + $handler = $this->getMockBuilder(RequestHandlerInterface::class)->setMethods(['handle'])->getMock(); + $handler->method('handle')->willReturn(HttpMessageFactory::createResponse(200)); $response = $this->errorHandler->process($this->request, $handler); $this->assertEquals(200, $response->getStatusCode()); } - - public function testProcessMiddlewareWith403Exception() + + public function testProcessMiddlewareWith403Exception() { - $handler = $this->getMockBuilder(RequestHandlerInterface::class)->setMethods(['handle'])->getMock(); - $handler->method('handle')->willThrowException(new \BitFrame\Exception\ForbiddenException()); + $handler = $this->getMockBuilder(RequestHandlerInterface::class)->setMethods(['handle'])->getMock(); + $handler->method('handle')->willThrowException(new \BitFrame\Exception\ForbiddenException()); $response = $this->errorHandler->process($this->request, $handler); $this->assertEquals(403, $response->getStatusCode()); } - - public function testProcessMiddlewareWithDefaultException() + + public function testProcessMiddlewareWithDefaultException() { - $handler = $this->getMockBuilder(RequestHandlerInterface::class)->setMethods(['handle'])->getMock(); - $handler->method('handle')->willThrowException(new \Exception()); + $handler = $this->getMockBuilder(RequestHandlerInterface::class)->setMethods(['handle'])->getMock(); + $handler->method('handle')->willThrowException(new \Exception()); $response = $this->errorHandler->process($this->request, $handler); $this->assertEquals(500, $response->getStatusCode()); } - - public function testWhoopsErrorHandlerErrorException() + + public function testWhoopsErrorHandlerErrorException() { - try { - $run = $this->errorHandler->getWhoopsInstance($this->request); - $run->handleError(E_USER_ERROR, 'Testing', 'Test File', 44); - $this->fail("Missing expected exception"); - } catch (\ErrorException $e) { - $this->assertSame(E_USER_ERROR, $e->getSeverity()); - // see https://github.com/filp/whoops/issues/267 - $this->assertSame(E_USER_ERROR, $e->getCode(), "For BC reasons getCode() should match getSeverity()"); - $this->assertSame('Testing', $e->getMessage()); - $this->assertSame('Test File', $e->getFile()); - $this->assertSame(44, $e->getLine()); - } + try { + $run = $this->errorHandler->getWhoopsInstance($this->request); + $run->handleError(E_USER_ERROR, 'Testing', 'Test File', 44); + $this->fail("Missing expected exception"); + } catch (\ErrorException $e) { + $this->assertSame(E_USER_ERROR, $e->getSeverity()); + // see https://github.com/filp/whoops/issues/267 + $this->assertSame(E_USER_ERROR, $e->getCode(), "For BC reasons getCode() should match getSeverity()"); + $this->assertSame('Testing', $e->getMessage()); + $this->assertSame('Test File', $e->getFile()); + $this->assertSame(44, $e->getLine()); + } } - - public function testProcessMiddlewareWithTriggerError() + + public function testProcessMiddlewareWithTriggerError() { - $response = $this->errorHandler->process($this->request, new class($this) implements RequestHandlerInterface { - /** @var \PHPUnit\Framework\TestCase */ - private $test; - - public function __construct($testCaseInstance) - { - $this->test = $testCaseInstance; - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - trigger_error('Testing', E_USER_ERROR); - - return HttpMessageFactory::createResponse(200); - } - }); - - $this->assertEquals(500, $response->getStatusCode()); + $response = $this->errorHandler->process($this->request, new class($this) implements RequestHandlerInterface { + /** @var \PHPUnit\Framework\TestCase */ + private $test; + + public function __construct($testCaseInstance) + { + $this->test = $testCaseInstance; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + trigger_error('Testing', E_USER_ERROR); + + return HttpMessageFactory::createResponse(200); + } + }); + + $this->assertEquals(500, $response->getStatusCode()); } }