'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Page Not Found', 409 => 'Conflict', 500 => 'Internal Server Error', ]; protected $allowedStatusCodeList = [ 200, 201, 400, 401, 403, 404, 409, 500, ]; protected $ignorePrintXStatusReasonExceptionClassNameList = [ 'PDOException', ]; protected $request; public function __construct(Request $request) { $this->request = $request; } public function process( Response $response, Throwable $exception, bool $toPrint = false, ?array $route = null, ?array $routeParams = null ) { $message = $exception->getMessage() ?? ''; $statusCode = $exception->getCode(); if ($route) { $requestBodyString = $this->request->getBodyContents(); $requestBodyString = $this->clearPasswords($requestBodyString); $routePattern = $route['route']; $routeParams = $routeParams ?? []; $logMessage = "API ($statusCode) "; $logMessageItemList = []; if ($message) $logMessageItemList[] = $message; $logMessageItemList[] .= $this->request->getMethod() . ' ' . $this->request->getResourcePath(); if ($requestBodyString) $logMessageItemList[] = "Input data: " . $requestBodyString; if ($routePattern) $logMessageItemList[] = "Route pattern: ". $routePattern; if (!empty($routeParams)) $logMessageItemList[] = "Route params: ". print_r($routeParams, true); $logMessage .= implode("; ", $logMessageItemList); $GLOBALS['log']->log('debug', $logMessage); } $logLevel = 'error'; $messageLineFile = null; if ($exception) { $messageLineFile = 'line: ' . $exception->getLine() . ', file: ' . $exception->getFile(); } if ($exception && !empty($exception->logLevel)) { $logLevel = $exception->logLevel; } $logMessageItemList = []; if ($message) $logMessageItemList[] = "{$message}"; $logMessageItemList[] = $this->request->getMethod() . ' ' . $this->request->getResourcePath(); if ($messageLineFile) { $logMessageItemList[] = $messageLineFile; } $logMessage = "($statusCode) " . implode("; ", $logMessageItemList); $GLOBALS['log']->log($logLevel, $logMessage); $toPrintXStatusReason = true; if ($exception && in_array(get_class($exception), $this->ignorePrintXStatusReasonExceptionClassNameList)) { $toPrintXStatusReason = false; } if (!in_array($statusCode, $this->allowedStatusCodeList)) { $statusCode = 500; } $response->setStatus($statusCode); if ($toPrintXStatusReason) { $response->setHeader('X-Status-Reason', $message); } if ($toPrint) { $statusText = $this->getCodeDescription($statusCode); $statusText = isset($statusText) ? $statusCode . ' '. $statusText : 'HTTP ' . $statusCode; if ($message) { $message = htmlspecialchars($message); } $response->writeBody(self::generateErrorBody($statusText, $message)); } } 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 = "