PHP에서 문자열을 일정한 길이의 배열로 쪼갤 때 str_split()을 흔히 사용한다. 다만 대부분의 개발자들은 한글이나 이모지 같은 멀티바이트 문자를 처리할 때 예상과 다른 결과를 얻으며, 청크 길이 설정이 정확히 뭐하는 건지 모른 채 인터넷에서 가져온 코드를 그냥 복사해 붙인다. 이번에는 str_split()이 정확히 뭔지, 왜 문제가 생기는지, 어떻게 제대로 사용하는지 완벽하게 정리해서 소개하겠다.
str_split()은 문자열을 지정된 길이의 배열로 변환하는 함수다. 기본적으로 문자 단위로 1글자씩 끊는데, 두 번째 인자로 청크 길이를 설정하면 그 길이만큼 묶어낸다.
함수 원형은 str_split(string $string, int $length = 1): array다. 반환값은 배열이고, 마지막 요소가 설정된 길이보다 작을 수 있다.
영문과 숫자만 사용하는 문자열이라면 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()로 글자 단위로 끊는 것이다.
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()은 문자 개수 단위로 청크를 나누므로 한글도 안전하다.
구형 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()로 글자 단위로 끊어내므로 멀티바이트 문자도 안전하다.
사용자 정보 표시 시 비밀번호나 핸드폰 뒷자리를 마스킹할 때가 있다. 이때 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
?>
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
?>
로그 메시지나 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";
}
// 결과:
// 사용자 로그인
// 실패: 계정이
// 잠겨있습니다.
?>
만약 호스팅 서버가 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번(메시지 줄바꿈)을 참고해서 실제 프로젝트에 적용하면, 데이터가 깨지는 버그로부터 완전히 벗어날 수 있을 것이다.