Skip to content

Commit 6cf4872

Browse files
authored
Send 404 header for specified exceptions (#4)
1 parent aad8b7c commit 6cf4872

File tree

4 files changed

+52
-11
lines changed

4 files changed

+52
-11
lines changed

composer.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@
1616
"require-dev": {
1717
"phpstan/phpstan": "^0.12",
1818
"phpstan/phpstan-phpunit": "^0.12",
19-
"phpunit/phpunit": "^8.5",
20-
"roave/security-advisories": "dev-master",
19+
"phpunit/phpunit": "^9.2",
2120
"slam/php-cs-fixer-extensions": "^1.19",
2221
"slam/php-debug-r": "^1.6",
2322
"slam/phpstan-extensions": "^4.0",
24-
"symfony/console": "^5.0",
23+
"symfony/console": "^5.1",
2524
"thecodingmachine/phpstan-strict-rules": "^0.12"
2625
},
2726
"autoload": {

lib/ErrorHandler.php

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ final class ErrorHandler
8989
\E_WARNING => 'E_WARNING',
9090
];
9191

92+
/**
93+
* @var array<int, class-string<Throwable>>
94+
*/
95+
private $exceptionsTypesFor404 = [];
96+
9297
public function __construct(callable $emailCallback)
9398
{
9499
$this->emailCallback = $emailCallback;
@@ -296,7 +301,11 @@ public function exceptionHandler(Throwable $exception): void
296301

297302
// @codeCoverageIgnoreStart
298303
if (! \headers_sent()) {
299-
\header('HTTP/1.1 500 Internal Server Error');
304+
$header = 'HTTP/1.1 500 Internal Server Error';
305+
if (\in_array(\get_class($exception), $this->exceptionsTypesFor404, true)) {
306+
$header = 'HTTP/1.1 404 Not Found';
307+
}
308+
\header($header);
300309
}
301310
// @codeCoverageIgnoreEnd
302311

@@ -305,12 +314,16 @@ public function exceptionHandler(Throwable $exception): void
305314

306315
public function renderHtmlException(Throwable $exception): string
307316
{
308-
$ajax = (isset($_SERVER) && isset($_SERVER['X_REQUESTED_WITH']) && 'XMLHttpRequest' === $_SERVER['X_REQUESTED_WITH']);
309-
$output = '';
317+
$ajax = (isset($_SERVER) && isset($_SERVER['X_REQUESTED_WITH']) && 'XMLHttpRequest' === $_SERVER['X_REQUESTED_WITH']);
318+
$output = '';
319+
$errorType = '500: Internal Server Error';
320+
if (\in_array(\get_class($exception), $this->exceptionsTypesFor404, true)) {
321+
$errorType = '404: Not Found';
322+
}
310323
if (! $ajax) {
311-
$output .= '<!DOCTYPE html><html><head><title>500: Internal Server Error</title></head><body>';
324+
$output .= \sprintf('<!DOCTYPE html><html><head><title>%s</title></head><body>', $errorType);
312325
}
313-
$output .= '<h1>500: Internal Server Error</h1>';
326+
$output .= \sprintf('<h1>%s</h1>', $errorType);
314327
$output .= \PHP_EOL;
315328
if ($this->displayErrors()) {
316329
$currentEx = $exception;
@@ -457,4 +470,20 @@ private function purgeTrace(string $trace): string
457470
{
458471
return \defined('ROOT_PATH') ? \str_replace(ROOT_PATH, '.', $trace) : $trace;
459472
}
473+
474+
/**
475+
* @param array<int, class-string<Throwable>> $exceptionsTypesFor404
476+
*/
477+
public function set404ExceptionTypes(array $exceptionsTypesFor404): void
478+
{
479+
$this->exceptionsTypesFor404 = $exceptionsTypesFor404;
480+
}
481+
482+
/**
483+
* @return array<int, class-string<Throwable>>
484+
*/
485+
public function get404ExceptionTypes(): array
486+
{
487+
return $this->exceptionsTypesFor404;
488+
}
460489
}

phpstan.neon

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ parameters:
99
- lib/
1010
- tests/
1111
ignoreErrors:
12-
- '#Class Doctrine\\Common\\Util\\Debug not found#'
13-
- '#Call to static method export\(\) on an unknown class Doctrine\\Common\\Util\\Debug#'
1412
- '#Function \w+ is unsafe to use, rely on .+ instead#'
1513
- '#Variable \$_\w+ in isset\(\) always exists and is not nullable#'
1614
- '#Parameter \#1 \$error_handler of function set_error_handler expects#'

tests/ErrorHandlerTest.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use ErrorException;
88
use PHPUnit\Framework\TestCase;
9+
use RuntimeException;
910
use Slam\ErrorHandler\ErrorHandler;
1011
use Symfony\Component\Console\Terminal;
1112

@@ -124,7 +125,7 @@ public function testScream(): void
124125

125126
$warningMessage = \uniqid('warning_');
126127
$this->expectException(ErrorException::class);
127-
$this->expectExceptionMessageRegExp(\sprintf('/%s/', \preg_quote($warningMessage)));
128+
$this->expectExceptionMessageMatches(\sprintf('/%s/', \preg_quote($warningMessage)));
128129

129130
@ \trigger_error($warningMessage, \E_USER_WARNING);
130131
}
@@ -291,4 +292,18 @@ public function testTerminalWidthByEnv(): void
291292
$terminal = new Terminal();
292293
self::assertSame($terminal->getWidth(), $errorHandler->getTerminalWidth());
293294
}
295+
296+
public function test404SpecificExceptionForHeaders(): void
297+
{
298+
self::assertEmpty($this->errorHandler->get404ExceptionTypes());
299+
300+
self::assertStringNotContainsString('404: Not Found', $this->errorHandler->renderHtmlException(new RuntimeException()));
301+
302+
$exceptionTypes = [RuntimeException::class];
303+
$this->errorHandler->set404ExceptionTypes($exceptionTypes);
304+
305+
self::assertSame($exceptionTypes, $this->errorHandler->get404ExceptionTypes());
306+
307+
self::assertStringContainsString('404: Not Found', $this->errorHandler->renderHtmlException(new RuntimeException()));
308+
}
294309
}

0 commit comments

Comments
 (0)