From 062854d5f4e9d5783802b44f0d632df380d14ea9 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Thu, 6 Aug 2020 09:13:04 +0200 Subject: [PATCH] Send 404 header for specified exceptions --- composer.json | 5 ++--- lib/ErrorHandler.php | 39 +++++++++++++++++++++++++++++++++----- phpstan.neon | 2 -- tests/ErrorHandlerTest.php | 17 ++++++++++++++++- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index dd92f3a..ed82bc6 100644 --- a/composer.json +++ b/composer.json @@ -16,12 +16,11 @@ "require-dev": { "phpstan/phpstan": "^0.12", "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "roave/security-advisories": "dev-master", + "phpunit/phpunit": "^9.2", "slam/php-cs-fixer-extensions": "^1.19", "slam/php-debug-r": "^1.6", "slam/phpstan-extensions": "^4.0", - "symfony/console": "^5.0", + "symfony/console": "^5.1", "thecodingmachine/phpstan-strict-rules": "^0.12" }, "autoload": { diff --git a/lib/ErrorHandler.php b/lib/ErrorHandler.php index 84ca0b4..78c94cb 100644 --- a/lib/ErrorHandler.php +++ b/lib/ErrorHandler.php @@ -89,6 +89,11 @@ final class ErrorHandler \E_WARNING => 'E_WARNING', ]; + /** + * @var array> + */ + private $exceptionsTypesFor404 = []; + public function __construct(callable $emailCallback) { $this->emailCallback = $emailCallback; @@ -296,7 +301,11 @@ public function exceptionHandler(Throwable $exception): void // @codeCoverageIgnoreStart if (! \headers_sent()) { - \header('HTTP/1.1 500 Internal Server Error'); + $header = 'HTTP/1.1 500 Internal Server Error'; + if (\in_array(\get_class($exception), $this->exceptionsTypesFor404, true)) { + $header = 'HTTP/1.1 404 Not Found'; + } + \header($header); } // @codeCoverageIgnoreEnd @@ -305,12 +314,16 @@ public function exceptionHandler(Throwable $exception): void public function renderHtmlException(Throwable $exception): string { - $ajax = (isset($_SERVER) && isset($_SERVER['X_REQUESTED_WITH']) && 'XMLHttpRequest' === $_SERVER['X_REQUESTED_WITH']); - $output = ''; + $ajax = (isset($_SERVER) && isset($_SERVER['X_REQUESTED_WITH']) && 'XMLHttpRequest' === $_SERVER['X_REQUESTED_WITH']); + $output = ''; + $errorType = '500: Internal Server Error'; + if (\in_array(\get_class($exception), $this->exceptionsTypesFor404, true)) { + $errorType = '404: Not Found'; + } if (! $ajax) { - $output .= '500: Internal Server Error'; + $output .= \sprintf('%s', $errorType); } - $output .= '

500: Internal Server Error

'; + $output .= \sprintf('

%s

', $errorType); $output .= \PHP_EOL; if ($this->displayErrors()) { $currentEx = $exception; @@ -457,4 +470,20 @@ private function purgeTrace(string $trace): string { return \defined('ROOT_PATH') ? \str_replace(ROOT_PATH, '.', $trace) : $trace; } + + /** + * @param array> $exceptionsTypesFor404 + */ + public function set404ExceptionTypes(array $exceptionsTypesFor404): void + { + $this->exceptionsTypesFor404 = $exceptionsTypesFor404; + } + + /** + * @return array> + */ + public function get404ExceptionTypes(): array + { + return $this->exceptionsTypesFor404; + } } diff --git a/phpstan.neon b/phpstan.neon index f14d2bc..21b2233 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,8 +9,6 @@ parameters: - lib/ - tests/ ignoreErrors: - - '#Class Doctrine\\Common\\Util\\Debug not found#' - - '#Call to static method export\(\) on an unknown class Doctrine\\Common\\Util\\Debug#' - '#Function \w+ is unsafe to use, rely on .+ instead#' - '#Variable \$_\w+ in isset\(\) always exists and is not nullable#' - '#Parameter \#1 \$error_handler of function set_error_handler expects#' diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index 1f5ea8f..0bad68b 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -6,6 +6,7 @@ use ErrorException; use PHPUnit\Framework\TestCase; +use RuntimeException; use Slam\ErrorHandler\ErrorHandler; use Symfony\Component\Console\Terminal; @@ -124,7 +125,7 @@ public function testScream(): void $warningMessage = \uniqid('warning_'); $this->expectException(ErrorException::class); - $this->expectExceptionMessageRegExp(\sprintf('/%s/', \preg_quote($warningMessage))); + $this->expectExceptionMessageMatches(\sprintf('/%s/', \preg_quote($warningMessage))); @ \trigger_error($warningMessage, \E_USER_WARNING); } @@ -291,4 +292,18 @@ public function testTerminalWidthByEnv(): void $terminal = new Terminal(); self::assertSame($terminal->getWidth(), $errorHandler->getTerminalWidth()); } + + public function test404SpecificExceptionForHeaders(): void + { + self::assertEmpty($this->errorHandler->get404ExceptionTypes()); + + self::assertStringNotContainsString('404: Not Found', $this->errorHandler->renderHtmlException(new RuntimeException())); + + $exceptionTypes = [RuntimeException::class]; + $this->errorHandler->set404ExceptionTypes($exceptionTypes); + + self::assertSame($exceptionTypes, $this->errorHandler->get404ExceptionTypes()); + + self::assertStringContainsString('404: Not Found', $this->errorHandler->renderHtmlException(new RuntimeException())); + } }