'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Page Not Found', 409 => 'Conflict', 500 => 'Internal Server Error', 503 => 'Service Unavailable', ]; protected $allowedStatusCodeList = [ 200, 201, 400, 401, 403, 404, 409, 500, 503, ]; protected $ignorePrintXStatusReasonExceptionClassNameList = [ 'PDOException', ]; private $log; public function __construct(Log $log) { $this->log = $log; } public function process( Request $request, Response $response, Throwable $exception, ?string $route = null ): void { $this->processInternal($request, $response, $exception, $route, false); } public function processWithBodyPrinting( Request $request, Response $response, Throwable $exception, ?string $route = null ): void { $this->processInternal($request, $response, $exception, $route, true); } private function processInternal( Request $request, Response $response, Throwable $exception, ?string $route = null, bool $toPrintBody = false ): void { $message = $exception->getMessage() ?? ''; $statusCode = $exception->getCode(); if ($route) { $this->processRoute($route, $request, $exception); } $logLevel = 'error'; $messageLineFile = null; $messageLineFile = 'line: ' . $exception->getLine() . ', ' . 'file: ' . $exception->getFile(); if (!empty($exception->logLevel)) { $logLevel = $exception->logLevel; } $logMessageItemList = []; if ($message) { $logMessageItemList[] = "{$message}"; } $logMessageItemList[] = $request->getMethod() . ' ' . $request->getResourcePath(); if ($messageLineFile) { $logMessageItemList[] = $messageLineFile; } $logMessage = "($statusCode) " . implode("; ", $logMessageItemList); $this->log->log($logLevel, $logMessage); $toPrintBodyXStatusReason = !in_array( get_class($exception), $this->ignorePrintXStatusReasonExceptionClassNameList ); if (!in_array($statusCode, $this->allowedStatusCodeList)) { $statusCode = 500; } $response->setStatus($statusCode); if ($toPrintBodyXStatusReason) { $response->setHeader('X-Status-Reason', $this->stripInvalidCharactersFromHeaderValue($message)); } if ($this->doesExceptionHaveBody($exception)) { $response->writeBody($exception->getBody()); $toPrintBody = false; } if ($toPrintBody) { $codeDesription = $this->getCodeDescription($statusCode); $statusText = isset($codeDesription) ? $statusCode . ' '. $codeDesription : 'HTTP ' . $statusCode; if ($message) { $message = htmlspecialchars($message); } $response->writeBody(self::generateErrorBody($statusText, $message)); } } protected function doesExceptionHaveBody(Throwable $exception): bool { if ( !$exception instanceof Error && !$exception instanceof Conflict ) { return false; } $exceptionBody = null; if (method_exists($exception, 'getBody')) { $exceptionBody = $exception->getBody(); } return $exceptionBody !== null; } protected function getCodeDescription(int $statusCode): ?string { if (isset($this->errorDescriptions[$statusCode])) { return $this->errorDescriptions[$statusCode]; } return null; } protected function clearPasswords(string $string): string { return preg_replace('/"(.*?password.*?)":".*?"/i', '"$1":"*****"', $string); } protected static function generateErrorBody(string $header, string $text): string { $body = "