*/ private $errorDescriptions = [ 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Page Not Found', 409 => 'Conflict', 500 => 'Internal Server Error', 503 => 'Service Unavailable', ]; /** * @var int[] */ private $allowedStatusCodeList = [ 200, 201, 400, 401, 403, 404, 409, 500, 503, ]; /** * @var class-string[] */ private $ignorePrintXStatusReasonExceptionClassNameList = [ 'PDOException', ]; private Log $log; private Config $config; public function __construct(Log $log, Config $config) { $this->log = $log; $this->config = $config; } 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(); $logMessageItemList[] = $messageLineFile; $logMessage = "($statusCode) " . implode("; ", $logMessageItemList); if ($this->toPrintTrace()) { $logMessage .= " :: " . $exception->getTraceAsString(); } $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 ($exception instanceof HasBody && $this->exceptionHasBody($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)); } } private function exceptionHasBody(Throwable $exception): bool { if (!$exception instanceof HasBody) { return false; } $exceptionBody = $exception->getBody(); return $exceptionBody !== null; } private function getCodeDescription(int $statusCode): ?string { if (isset($this->errorDescriptions[$statusCode])) { return $this->errorDescriptions[$statusCode]; } return null; } private function clearPasswords(string $string): string { return preg_replace('/"(.*?password.*?)":".*?"/i', '"$1":"*****"', $string); } private static function generateErrorBody(string $header, string $text): string { $body = "