PHP에서 path에 대한 정보를 얻기 위해 pathinfo()와 basename()을 많이 사용한다. 그런데 path 내에 한글과 같이 UTF-8 문자열이 포함될 경우 결과가 이상하게 나올 수 있다. 일단 아래의 소스 코드와 실행 결과를 보자.
<?php
$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";
$filepath = '/home/가 나 다.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';
echo '<br />';
$locales = explode("\n", trim(shell_exec('locale -a')));
foreach ($locales as $locale) {
setlocale(LC_ALL, $locale);
$info = pathinfo($filepath);
if (strcmp($info['basename'], '가 나 다.txt') != 0) {
echo $locale . ' -> ' . $info['basename'] . '<br />';
}
}
/*
실행 결과
Current Location : C
C -> 나 다.txt
C -> 나 다.txt
POSIX -> 나 다.txt
*/
위 코드는 현재 system 내에서 사용 가능한 모든 locale에 대해 실행 결과를 테스트한 결과다. 결과를 보면 정상적으로 처리되지 않고 첫글자가 잘려나간 잘못된 결과를 반환하는 경우가 있다. 바로 locale이 C
또는 POSIX
인 경우다.
서버 환경 구성에 따른 차이가 있겠지만 나의 경우는 nobody 권한으로 실행되는 웹서버 상에서 현재 locale이 C
로 나왔다. 따라서 한글 파일명이 포함된 path에 대해서는 pathinfo()를 사용할 수 없다.
이 문제에 대해 검색하다 Make PHP pathinfo() return the correct filename if the filename is UTF-8 라는 글을 찾았다. 여기서 질문을 한 aä.pdf
path에 대해서는 현재 시점(PHP 7)에서 locale C
와 POSIX
에서도 문제가 없었다. 이 버그와 관련하여 패치가 되었지만 아직 한글에 대한 버그는 남아 있는 것으로 보인다. 혹시나 해서 아래와 같이 일본어로도 동일한 테스트를 해보니 역시나 문제가 발생한다.
<?php
$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";
$filepath = '/home/うながす.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';
echo '<br />';
$locales = explode("\n", trim(shell_exec('locale -a')));
foreach ($locales as $locale) {
setlocale(LC_ALL, $locale);
$info = pathinfo($filepath);
if (strcmp($info['basename'], 'うながす.txt') != 0) {
echo $locale . ' -> ' . $info['basename'] . '<br />';
}
}
/*
실행 결과
Current Location : C
C -> .txt
C -> .txt
POSIX -> .txt
ja_JP -> ��ながす.txt
ja_JP.eucjp -> ��ながす.txt
ja_JP.ujis -> ��ながす.txt
japanese -> ��ながす.txt
japanese.euc -> ��ながす.txt
ko_KR -> ��ながす.txt
ko_KR.euckr -> ��ながす.txt
korean -> ��ながす.txt
korean.euc -> ��ながす.txt
mt_MT -> ��ながす.txt
mt_MT.iso88593 -> ��ながす.txt
yi_US -> �ながす.txt
yi_US.cp1255 -> �ながす.txt
zh_CN -> .txt
zh_CN.gb2312 -> .txt
zh_HK -> �がす.txt
zh_HK.big5hkscs -> �がす.txt
zh_SG -> .txt
zh_SG.gb2312 -> .txt
zh_TW -> �がす.txt
zh_TW.big5 -> �がす.txt
zh_TW.euctw -> .txt
*/
결론적으로 이 문제를 해결하기 위해서는 아래와 같이 간단히 해결 가능하다.
<?php
setlocale(LC_ALL,'ko_KR.UTF-8');
$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";
$filepath = '/home/가 나 다.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';
/*
실행 결과
Current Location : ko_KR.UTF-8
ko_KR.UTF-8 -> 가 나 다.txt
*/
setlocale(LC_ALL,'ko_KR.UTF-8')
와 같이 UTF-8
을 사용하는 locale로 변경하면 한글 파일명을 가진 path에 대해서도 pathinfo는 정상적으로 동작한다(ko_KR
임에도 불구하고 일본어도 정상 동작한다).
이 문제에서의 특징은 한가지가 더 있다. 파일명 앞에 영문자와 같이 한글이 아닌 글자가 있다면 이 문제가 발생하지 않는다는 것이다. 아래의 예시를 보자.
<?php
$currentLocal = setlocale(LC_ALL, 0);
echo 'Current Location : ' . $currentLocal . "<br/>";
$filepath = '/home/a가 나 다.txt';
$info = pathinfo($filepath);
echo $currentLocal . ' -> ' . $info['basename'] . '<br />';
/*
실행 결과
Current Location : C
C -> a가 나 다.txt
*/
이러한 원리를 이용한 방법으로 Make PHP pathinfo() return the correct filename if the filename is UTF-8에서는 아래와 같이 처리하는 방법을 제시하고 있다.
<?php
function getFilename($path)
{
// if there's no '/', we're probably dealing with just a filename
// so just put an 'a' in front of it
if (strpos($path, '/') === false)
{
$path_parts = pathinfo('a'.$path);
}
else
{
$path= str_replace('/', '/a', $path);
$path_parts = pathinfo($path);
}
return substr($path_parts["filename"],1);
}
하지만 이 방법 보다는 아래의 방법이 조금 더 나아 보인다(urlencode()를 이용하는 방법이다).
<php
function _pathinfo($path, $options = null)
{
$path = urlencode($path);
$parts = null === $options ? pathinfo($path) : pathinfo($path, $options);
foreach ($parts as $field => $value) {
$parts[$field] = urldecode($value);
}
return $parts;
}
결론적으로는 위 두가지 방법 보다는 setlocale(LC_ALL,'ko_KR.UTF-8')
을 이용해서 locale을 변경하는 것을 권장한다. 사용하는 라이브러리 내부 등에서도 pathinfo를 사용하는 경우가 있을 수 있으므로 프로그램이 시작시 locale을 변경하는 것이 좋을 것이다(단, locale이 다른 프로그램 영역에 영향을 줄 수 있는지에 대해서는 검토를 해보는 것이 좋다).