한글을 포함한 데이터를 다룰 때 문자 깨짐이나 인코딩 오류를 경험해봤을까? 특히 외부 API에서 받은 데이터나 사용자 입력값을 처리할 때 갑자기 '?'나 알 수 없는 기호로 변환되는 현상을 목격한 개발자라면 알 것이다. 다만 대부분은 그 원인이 정확히 뭔지, 왜 인코딩이 깨지는지, 어떻게 해야 하는지 제대로 알지 못한 채 인터넷에서 가져온 코드를 대충 복사해 붙인다. 이번에는 PHP에서 문자 인코딩 변환이 실패하는 정확한 원인, 언제 필요한지, 그리고 mb_convert_encoding으로 완벽하게 해결하는 방법을 차근차근 설명하겠다.

 

1단계: 문자 인코딩이란 정확히 뭔가

문자 인코딩은 '문자'라는 추상적인 개념을 컴퓨터가 이해할 수 있는 '숫자(바이트)로 변환하는 규칙'이다. 같은 '한'이라는 글자도 인코딩 방식에 따라 전혀 다른 바이트로 저장된다.

흔히 마주치는 인코딩 방식들:

  • UTF-8: 가장 현대적인 표준. 문자마다 1~4바이트를 사용. 한글 한 글자는 보통 3바이트.
  • EUC-KR: 한국에서 오래 쓰던 인코딩. 한글 한 글자는 2바이트. 일부 구형 시스템에서 아직도 사용.
  • CP949: 윈도우의 한국어 인코딩. EUC-KR의 확장 버전.
  • ISO-8859-1(Latin-1): 영문/서유럽 문자용. 한글 미지원.

웹 개발에서 대부분은 UTF-8을 사용하지만, 외부 API나 CSV 파일, 구형 데이터베이스에서는 다른 인코딩으로 데이터가 들어온다. 이때 변환을 놓치면 문자가 깨진다.

 

2단계: 인코딩 변환이 실패하는 원인

문자 깨짐은 이런 상황에서 발생한다:

상황원인증상
외부 API 응답API가 보낸 인코딩과 PHP 기본 인코딩이 다름'한글' → '????'
파일 업로드사용자 OS의 기본 인코딩(Windows는 CP949)으로 인코딩된 파일명파일명 깨짐
CSV/Excel 임포트Excel의 기본값(CP949/EUC-KR)과 웹서버(UTF-8) 불일치한글 문자 깨짐
오래된 DBMySQL이 EUC-KR로 설정, 하지만 PHP는 UTF-8 기대저장/조회 시 '?'로 변환
수동 변환 누락받은 데이터의 원래 인코딩을 명시하지 않음모르는 기호 또는 ?

 

3단계: mb_convert_encoding 사용 방법

PHP의 mb_convert_encoding() 함수는 문자열을 한 인코딩에서 다른 인코딩으로 변환한다. 기본 문법은 이렇다:

mb_convert_encoding($string, $to_encoding, $from_encoding);
  • $string: 변환할 문자열
  • $to_encoding: 변환할 대상 인코딩 (보통 'UTF-8')
  • $from_encoding: 원본 인코딩 (예: 'EUC-KR', 'CP949', 'ISO-8859-1')

가장 흔한 사용 예시를 보자.

예제 1: Windows에서 업로드된 파일명 변환

✗ 잘못된 코드 - 인코딩을 명시하지 않음:

// 사용자가 Windows에서 "한글파일.txt"를 업로드
$filename = $_FILES['file']['name']; // "????.txt" 상태
echo $filename; // 화면에 깨진 글자 출력

✓ 올바른 코드 - 원본 인코딩을 CP949로 명시:

$filename = $_FILES['file']['name'];
// Windows 기본값인 CP949에서 UTF-8로 변환
$filename = mb_convert_encoding($filename, 'UTF-8', 'CP949');
echo $filename; // "한글파일.txt" 정상 출력

예제 2: 외부 API 응답이 EUC-KR로 올 때

✗ 잘못된 코드:

$response = file_get_contents('http://example-api.com/data?query=한글');
// API가 EUC-KR로 응답했는데 변환하지 않음
$data = json_decode($response, true);
echo $data['name']; // "????" 깨짐

✓ 올바른 코드:

$response = file_get_contents('http://example-api.com/data?query=한글');
// API 응답이 EUC-KR라면 UTF-8로 변환
$response = mb_convert_encoding($response, 'UTF-8', 'EUC-KR');
$data = json_decode($response, true);
echo $data['name']; // "한글" 정상 출력

예제 3: 원본 인코딩을 모를 때 - 자동 감지

✗ 잘못된 코드:

$unknown_string = '혼란스러운 데이터';
$converted = mb_convert_encoding($unknown_string, 'UTF-8'); // 원본 인코딩을 모름
echo $converted; // 예측 불가

✓ 올바른 코드 - mb_detect_encoding()으로 자동 감지:

$unknown_string = '혼란스러운 데이터';
// 자동으로 인코딩 감지 (감지 우선순위 지정)
$from_encoding = mb_detect_encoding(
    $unknown_string, 
    ['UTF-8', 'EUC-KR', 'CP949', 'ISO-8859-1'],
    true
);
$converted = mb_convert_encoding($unknown_string, 'UTF-8', $from_encoding);
echo $converted; // 정상 출력

결과: UTF-8로 정상 변환됨

 

4단계: 실전 예제 - CSV 파일 임포트

Excel에서 다운로드한 CSV는 보통 Windows의 기본 인코딩(CP949)으로 저장된다. 이를 웹에 올바르게 표시하려면:

<?php
// CSV 파일 읽기
if (($handle = fopen('upload/data.csv', 'r')) !== false) {
    $data = [];
    while (($row = fgetcsv($handle)) !== false) {
        // CSV의 각 셀이 CP949로 인코딩되어 있음
        // UTF-8로 변환
        $converted_row = array_map(function($cell) {
            return mb_convert_encoding($cell, 'UTF-8', 'CP949');
        }, $row);
        $data[] = $converted_row;
    }
    fclose($handle);
}

// 결과: 한글이 정상적으로 표시됨
foreach ($data as $row) {
    echo $row[0] . ' | ' . $row[1] . '<br />';
}
?>

실행 결과: 원본 CSV의 "김철수|서울"이 정상 출력됨

 

5단계: 주의사항과 흔한 실수

실수 1: mb_ 함수를 쓰기 전에 라이브러리 확인 안 함

✗ 잘못된 코드:

// mb_convert_encoding 함수가 없을 수도 있음
$result = mb_convert_encoding($string, 'UTF-8', 'EUC-KR');

✓ 올바른 코드:

// 함수 존재 확인
if (!extension_loaded('mbstring')) {
    die('Error: mbstring extension not loaded');
}
$result = mb_convert_encoding($string, 'UTF-8', 'EUC-KR');

실수 2: 이미 UTF-8인 문자열을 다시 변환

✗ 잘못된 코드:

$string = '이미 UTF-8인 한글';
// 다시 UTF-8로 변환하면 깨질 수 있음
$result = mb_convert_encoding($string, 'UTF-8', 'EUC-KR');
echo $result; // 예측 불가능한 결과

✓ 올바른 코드:

$string = '이미 UTF-8인 한글';
// 먼저 원본 인코딩을 확인
$detected = mb_detect_encoding($string, ['UTF-8', 'EUC-KR'], true);
if ($detected !== 'UTF-8') {
    $string = mb_convert_encoding($string, 'UTF-8', $detected);
}
echo $string; // 항상 정상 출력

실수 3: 인코딩 이름 오타

✗ 잘못된 코드:

// 'UTF8'은 잘못된 이름
$result = mb_convert_encoding($string, 'UTF8', 'EUC-KR');

✓ 올바른 코드:

// 정확한 인코딩명 (하이픈 포함)
$result = mb_convert_encoding($string, 'UTF-8', 'EUC-KR');
// 또는 'utf-8', 'euc-kr'도 동작 (대소문자 무관)

 

6단계: 프로젝트 전체에 적용하는 방법

매번 mb_convert_encoding을 쓰기보다는, 프로젝트 입구에서 일괄 처리하는 게 낫다:

<?php
// config.php 또는 index.php 맨 처음
header('Content-Type: text/html; charset=utf-8');
mb_internal_encoding('UTF-8');
mb_http_input('UTF-8');
mb_http_output('UTF-8');

// 모든 $_GET, $_POST 데이터를 자동으로 UTF-8로 변환
foreach ($_POST as $key => $value) {
    $_POST[$key] = mb_convert_encoding($value, 'UTF-8', mb_detect_encoding($value, ['UTF-8', 'EUC-KR', 'CP949'], true));
}
foreach ($_GET as $key => $value) {
    $_GET[$key] = mb_convert_encoding($value, 'UTF-8', mb_detect_encoding($value, ['UTF-8', 'EUC-KR', 'CP949'], true));
}
?>

 

7단계: 핵심 정리

문자 인코딩 변환은 국제화 웹 개발에서 필수다. mb_convert_encoding()은 단순해 보이지만, 원본 인코딩을 정확히 알고 사용해야만 제대로 작동한다.

체크리스트:

  • ✓ 데이터의 원본 인코딩이 뭔지 명확히 파악했는가?
  • ✓ mb_detect_encoding으로 자동 감지하거나, API 문서에서 명시된 인코딩을 확인했는가?
  • ✓ 변환 후에는 정말 UTF-8인지 테스트했는가?
  • ✓ 프로젝트 입구(config/index)에서 일괄 처리하도록 구조화했는가?

이 글의 예제 2(API 응답 변환)와 예제 3(자동 감지)를 참고해서 지금 당신의 프로젝트에서 문자가 깨지는 부분을 찾아내고 mb_convert_encoding을 적용하면, 한글 포함 모든 인코딩 문제를 깔끔하게 해결할 수 있을 것이다.