$message] + $extra, $status); } public static function noContent(): never { http_response_code(204); header('Cache-Control: no-store'); exit; } /** Decoded JSON request body or an error response when it is not an object. */ public static function body(): array { $data = json_decode(self::rawBody(), true); if (!is_array($data)) { self::error('Request body must be a JSON object', 400); } return $data; } /** * The request body as bytes (uploads send images, not JSON). * * An empty body is answered here rather than by every caller, because PHP also reports * a body that exceeded `post_max_size` as empty – a truncated upload must not look like * a malformed one. */ public static function rawBody(): string { $raw = file_get_contents('php://input'); if ($raw === false || $raw === '') { self::error('Request body is empty or exceeds the upload limit', 400); } return $raw; } }