PHP REST API 응답 포맷 오류 문제와 해결책

프리터코더·2025년 6월 1일
0

php 문제 해결

목록 보기
51/79

PHP REST API에서 응답 포맷이 일관되지 않거나 잘못된 형식으로 전송되는 문제는 클라이언트와의 통신에 심각한 문제를 일으킵니다. 다음은 주요 원인과 해결책들입니다.

1. 일관된 JSON 응답 포맷

문제: API 응답 형식이 엔드포인트마다 다름

해결책:

class ApiResponse {
    public static function success($data = null, $message = 'Success', $code = 200) {
        http_response_code($code);
        header('Content-Type: application/json');
        
        return json_encode([
            'success' => true,
            'code' => $code,
            'message' => $message,
            'data' => $data,
            'timestamp' => date('c')
        ]);
    }
    
    public static function error($message = 'Error', $code = 400, $errors = null) {
        http_response_code($code);
        header('Content-Type: application/json');
        
        return json_encode([
            'success' => false,
            'code' => $code,
            'message' => $message,
            'errors' => $errors,
            'timestamp' => date('c')
        ]);
    }
}

// 사용 예시
echo ApiResponse::success(['users' => $users], 'Users retrieved successfully');
echo ApiResponse::error('User not found', 404);

2. Content-Type 헤더 누락 문제

문제: JSON 데이터를 보내지만 Content-Type 헤더가 설정되지 않음

해결책:

function sendJsonResponse($data, $statusCode = 200) {
    // 출력 버퍼 정리
    while (ob_get_level()) {
        ob_end_clean();
    }
    
    // 헤더 설정
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-cache, no-store, must-revalidate');
    http_response_code($statusCode);
    
    // JSON 인코딩 옵션
    $jsonOptions = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
    
    if (defined('JSON_THROW_ON_ERROR')) {
        $jsonOptions |= JSON_THROW_ON_ERROR;
    }
    
    echo json_encode($data, $jsonOptions);
    exit();
}

3. JSON 인코딩 오류 처리

문제: 특수문자나 인코딩 문제로 JSON 생성 실패

해결책:

function safeJsonEncode($data) {
    // UTF-8 인코딩 확인 및 변환
    $data = convertToUtf8($data);
    
    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    
    if (json_last_error() !== JSON_ERROR_NONE) {
        $error = json_last_error_msg();
        
        // 일반적인 JSON 오류 처리
        switch (json_last_error()) {
            case JSON_ERROR_UTF8:
                $data = utf8_encode_deep($data);
                $json = json_encode($data);
                break;
            case JSON_ERROR_DEPTH:
                throw new Exception('JSON encoding failed: Maximum stack depth exceeded');
            case JSON_ERROR_SYNTAX:
                throw new Exception('JSON encoding failed: Syntax error');
            default:
                throw new Exception('JSON encoding failed: ' . $error);
        }
    }
    
    return $json;
}

function convertToUtf8($data) {
    if (is_string($data)) {
        return mb_convert_encoding($data, 'UTF-8', 'auto');
    } elseif (is_array($data)) {
        return array_map('convertToUtf8', $data);
    }
    return $data;
}

4. HTTP 상태 코드 일관성

문제: 적절하지 않은 HTTP 상태 코드 사용

해결책:

class HttpStatus {
    const OK = 200;
    const CREATED = 201;
    const NO_CONTENT = 204;
    const BAD_REQUEST = 400;
    const UNAUTHORIZED = 401;
    const FORBIDDEN = 403;
    const NOT_FOUND = 404;
    const METHOD_NOT_ALLOWED = 405;
    const CONFLICT = 409;
    const UNPROCESSABLE_ENTITY = 422;
    const INTERNAL_SERVER_ERROR = 500;
    
    public static function getStatusText($code) {
        $statusTexts = [
            200 => 'OK',
            201 => 'Created',
            204 => 'No Content',
            400 => 'Bad Request',
            401 => 'Unauthorized',
            403 => 'Forbidden',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            409 => 'Conflict',
            422 => 'Unprocessable Entity',
            500 => 'Internal Server Error'
        ];
        
        return $statusTexts[$code] ?? 'Unknown Status';
    }
}

// 사용 예시
function createUser($userData) {
    try {
        $user = saveUser($userData);
        echo ApiResponse::success($user, 'User created successfully', HttpStatus::CREATED);
    } catch (ValidationException $e) {
        echo ApiResponse::error('Validation failed', HttpStatus::UNPROCESSABLE_ENTITY, $e->getErrors());
    }
}

5. 에러 응답 표준화

문제: 에러 응답이 일관되지 않아 클라이언트에서 처리 어려움

해결책:

class ApiError {
    public static function validationError($errors) {
        return ApiResponse::error('Validation failed', 422, [
            'validation_errors' => $errors
        ]);
    }
    
    public static function notFound($resource = 'Resource') {
        return ApiResponse::error("$resource not found", 404);
    }
    
    public static function unauthorized($message = 'Unauthorized access') {
        return ApiResponse::error($message, 401);
    }
    
    public static function forbidden($message = 'Access forbidden') {
        return ApiResponse::error($message, 403);
    }
    
    public static function serverError($message = 'Internal server error') {
        error_log("API Server Error: $message");
        return ApiResponse::error('An error occurred. Please try again later.', 500);
    }
}

// 사용 예시
if (!$user) {
    echo ApiError::notFound('User');
    exit();
}

if (!hasPermission($user, 'read')) {
    echo ApiError::forbidden('You do not have permission to access this resource');
    exit();
}

6. 페이지네이션 응답 포맷

문제: 페이지네이션 정보가 일관되지 않게 제공됨

해결책:

function paginatedResponse($data, $page, $perPage, $total) {
    $totalPages = ceil($total / $perPage);
    
    return ApiResponse::success([
        'items' => $data,
        'pagination' => [
            'current_page' => (int)$page,
            'per_page' => (int)$perPage,
            'total_items' => (int)$total,
            'total_pages' => (int)$totalPages,
            'has_next' => $page < $totalPages,
            'has_prev' => $page > 1
        ]
    ]);
}

// 사용 예시
$page = $_GET['page'] ?? 1;
$perPage = $_GET['per_page'] ?? 10;
$users = getUsers($page, $perPage);
$totalUsers = getTotalUsers();

echo paginatedResponse($users, $page, $perPage, $totalUsers);

7. CORS 헤더 설정

문제: 크로스 오리진 요청 시 응답이 차단됨

해결책:

function setCorsHeaders() {
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
    
    // 허용된 도메인 체크
    $allowedOrigins = ['https://myapp.com', 'https://api.myapp.com'];
    
    if (in_array($origin, $allowedOrigins) || $origin === 'http://localhost:3000') {
        header("Access-Control-Allow-Origin: $origin");
    }
    
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 86400');
    
    // OPTIONS 요청 처리
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        http_response_code(200);
        exit();
    }
}

// 모든 API 요청 시작 시 호출
setCorsHeaders();

8. 데이터 필터링 및 변환

문제: 민감한 정보가 API 응답에 포함됨

해결책:

class DataTransformer {
    public static function filterUserData($user, $includePrivate = false) {
        $publicFields = ['id', 'name', 'email', 'created_at'];
        $privateFields = ['phone', 'address', 'last_login'];
        
        $fields = $includePrivate ? array_merge($publicFields, $privateFields) : $publicFields;
        
        return array_intersect_key($user, array_flip($fields));
    }
    
    public static function transformCollection($items, $transformer) {
        return array_map($transformer, $items);
    }
}

// 사용 예시
$users = getUsers();
$transformedUsers = DataTransformer::transformCollection($users, function($user) {
    return DataTransformer::filterUserData($user, hasAdminPermission());
});

echo ApiResponse::success($transformedUsers);

9. API 버전 관리

문제: API 버전별로 다른 응답 포맷 필요

해결책:

class ApiVersionManager {
    private $version;
    
    public function __construct() {
        $this->version = $_SERVER['HTTP_API_VERSION'] ?? 'v1';
    }
    
    public function formatResponse($data) {
        switch ($this->version) {
            case 'v1':
                return $this->formatV1($data);
            case 'v2':
                return $this->formatV2($data);
            default:
                return ApiResponse::error('Unsupported API version', 400);
        }
    }
    
    private function formatV1($data) {
        return [
            'status' => 'success',
            'result' => $data
        ];
    }
    
    private function formatV2($data) {
        return [
            'success' => true,
            'data' => $data,
            'meta' => [
                'version' => 'v2',
                'timestamp' => time()
            ]
        ];
    }
}

// 사용 예시
$versionManager = new ApiVersionManager();
echo json_encode($versionManager->formatResponse($userData));
profile
일용직 개발자. freetercoder@gmail.com

0개의 댓글