PHP에서 문자열을 일정한 길이의 배열로 쪼갤 때 str_split()을 흔히 사용한다. 다만 대부분의 개발자들은 한글이나 이모지 같은 멀티바이트 문자를 처리할 때 예상과 다른 결과를 얻으며, 청크 길이 설정이 정확히 뭐하는 건지 모른 채 인터넷에서 가져온 코드를 그냥 복사해 붙인다. 이번에는 str_split()이 정확히 뭔지, 왜 문제가 생기는지, 어떻게 제대로 사용하는지 완벽하게 정리해서 소개하겠다.

 

str_split() 기초 개념 이해하기

str_split()은 문자열을 지정된 길이의 배열로 변환하는 함수다. 기본적으로 문자 단위로 1글자씩 끊는데, 두 번째 인자로 청크 길이를 설정하면 그 길이만큼 묶어낸다.

함수 원형은 str_split(string $string, int $length = 1): array다. 반환값은 배열이고, 마지막 요소가 설정된 길이보다 작을 수 있다.

 

ASCII 문자열에서는 제대로 작동하나?

영문과 숫자만 사용하는 문자열이라면 str_split()은 완벽하게 작동한다.

<?php
$str = "Hello World";
$result = str_split($str, 5);
var_dump($result);
// 결과: array(3) { [0]=> string(5) "Hello" [1]=> string(1) " " [2]=> string(5) "World" }
?>

보다시피 5글자씩 깔끔하게 끊어진다. 그런데 한글은?

 

한글과 멀티바이트 문자에서 깨지는 이유

✗ 잘못된 코드

<?php
$str = "안녕하세요 PHP";
$result = str_split($str, 5);
var_dump($result);
// 결과를 보면 한글이 글자 단위가 아닌 바이트 단위로 끊김
// array(5) {
//   [0]=> string(5) "안녕"
//   [1]=> string(5) "하세"
//   [2]=> string(5) "요 P"
//   [3]=> string(5) "HP"
//   ...
// }
?>

왜 이런 일이 생길까. str_split()은 바이트 단위로 문자열을 자르기 때문이다. UTF-8 인코딩에서 한글 한 글자는 3바이트를 차지한다. 5바이트를 자르라고 지시하면 한 글자의 중간을 끝낼 수 있고, 그러면 깨진 글자가 표시된다. 다만 MySQL 등에서는 이 깨진 부분이 에러를 내지만, PHP에서는 그냥 출력되거나 무시될 수 있다.

 

멀티바이트 문자 안전한 처리법

str_split()의 한계를 벗어나려면 두 가지 접근이 있다. 첫째는 mb_str_split() 함수를 사용하는 것이고, 둘째는 직접 mb_substr()로 글자 단위로 끊는 것이다.

 

방법 1: mb_str_split() 사용하기 (권장)

PHP 7.4.0 이상이면 mb_str_split()을 쓸 수 있다. 이 함수는 멀티바이트 문자를 글자 단위로 제대로 끊어낸다.

✓ 올바른 코드

<?php
$str = "안녕하세요 PHP";
$result = mb_str_split($str, 5);
var_dump($result);
// 결과: array(3) {
//   [0]=> string(15) "안녕하세요"
//   [1]=> string(5) " PHP"
// }

// 기본값은 1글자씩
$result2 = mb_str_split($str);
var_dump($result2);
// 결과: array(11) {
//   [0]=> string(3) "안"
//   [1]=> string(3) "녕"
//   [2]=> string(3) "하"
//   ...
// }
?>

mb_str_split()은 문자 개수 단위로 청크를 나누므로 한글도 안전하다.

 

방법 2: mb_substr() 루프로 직접 구현하기 (PHP 7.4 이하)

구형 PHP를 쓰는 경우라면 mb_substr()과 mb_strlen()으로 직접 구현해야 한다.

✓ 올바른 코드

<?php
function mb_str_split_custom($str, $length = 1, $encoding = 'UTF-8') {
    $result = [];
    $strlen = mb_strlen($str, $encoding);
    
    for ($i = 0; $i < $strlen; $i += $length) {
        $result[] = mb_substr($str, $i, $length, $encoding);
    }
    
    return $result;
}

$str = "안녕하세요 PHP";
$result = mb_str_split_custom($str, 5);
var_dump($result);
// 결과: array(3) {
//   [0]=> string(15) "안녕하세요"
//   [1]=> string(5) " PHP"
// }
?>

이 방식은 mb_strlen()으로 정확한 문자 개수를 파악한 후 mb_substr()로 글자 단위로 끊어내므로 멀티바이트 문자도 안전하다.

 

실전 예제와 흔한 실수

 

예제 1: 비밀번호 마스킹 처리

사용자 정보 표시 시 비밀번호나 핸드폰 뒷자리를 마스킹할 때가 있다. 이때 mb_str_split()을 응용하면 깔끔하다.

✗ 잘못된 코드 (str_split 사용)

<?php
$password = "비밀번호123";
$chunks = str_split($password, 3);
$masked = implode('*', $chunks);
echo $masked;
// 결과: 깨진 한글*자리*...
?>

✓ 올바른 코드 (mb_str_split 사용)

<?php
$password = "비밀번호123";
$chunks = mb_str_split($password, 3);
$masked = implode('*', $chunks);
echo $masked;
// 결과: 비밀*번호*123
?>

 

예제 2: 바코드 또는 시리얼 번호 포맷팅

16자리 바코드를 4자리씩 묶어서 표시할 때도 있다. ASCII 문자만 쓰면 str_split()도 되지만, 혹시 한글 제조사명이 들어갈 수 있다면?

✓ 올바른 코드

<?php
$barcode = "1234567890abcdef";
$formatted = implode('-', mb_str_split($barcode, 4));
echo $formatted;
// 결과: 1234-5678-90ab-cdef

// 한글 포함 케이스
$barcode_kr = "한글바코드0123456";
$formatted_kr = implode('-', mb_str_split($barcode_kr, 3));
echo $formatted_kr;
// 결과: 한글-바코-드01-23456
?>

 

예제 3: CSV 또는 로그 라인 길이 제한

로그 메시지나 CSV 셀이 너무 길 때 일정 길이씩 줄 바꿈해서 표시해야 할 수 있다.

✗ 잘못된 코드

<?php
$message = "사용자 로그인 실패: 계정이 잠겨있습니다.";
$lines = str_split($message, 10);
foreach ($lines as $line) {
    echo $line . "\n";
}
// 결과: 깨진 한글 출력
?>

✓ 올바른 코드

<?php
$message = "사용자 로그인 실패: 계정이 잠겨있습니다.";
$lines = mb_str_split($message, 10);
foreach ($lines as $line) {
    echo $line . "\n";
}
// 결과:
// 사용자 로그인
// 실패: 계정이
// 잠겨있습니다.
?>

 

주의사항과 성능

 

mb_str_split() 미지원 시 대안

만약 호스팅 서버가 PHP 7.3 이하고 mb_str_split()이 없다면, 위에서 보여준 mb_str_split_custom() 함수를 프로젝트에 추가해서 사용하면 된다. 또는 strlen() 대신 mb_strlen()을 반드시 쓰고, substr() 대신 mb_substr()을 써야 한다는 규칙만 지키면 된다.

 

인코딩 명시의 중요성

mb_str_split($str, $length, 'UTF-8')처럼 세 번째 인자로 인코딩을 반드시 명시하는 게 좋다. php.ini에서 mbstring.internal_encoding이 UTF-8로 설정되어 있어도, 명시적으로 쓰면 예기치 않은 인코딩 오류를 방지할 수 있다.

 

성능 비교

대량의 문자열을 처리할 때라면 mb_str_split()과 mb_substr() 루프의 성능 차이는 미미하다. 다만 빅 데이터 처리 시에는 mb_substr() 루프가 약간 더 메모리 효율적일 수 있으니, 필요하면 프로파일링으로 확인해보자.

 

핵심 정리

str_split()은 ASCII 문자는 안전하지만 한글, 이모지 같은 멀티바이트 문자에서는 바이트 단위로 끊어서 글자를 깨뜨린다. PHP 7.4 이상이면 mb_str_split()을 쓰고, 그 이하면 mb_substr()과 mb_strlen()으로 직접 구현하면 된다. 한국어를 다루는 프로젝트라면 str_split()은 쓰지 말고 처음부터 mb_str_split() 또는 mb_substr() 루프만 사용하는 습관을 들이자. 이 글의 예제 2번(바코드 포맷팅)이나 예제 3번(메시지 줄바꿈)을 참고해서 실제 프로젝트에 적용하면, 데이터가 깨지는 버그로부터 완전히 벗어날 수 있을 것이다.