2024年5月5日日曜日

PHP の日本語ファイル名処理で文字化け

電子情報保存法に対応

請求書をサーバに保存して電子情報保存法に対応しましょ、という仕組み作りがあちこちで行われているかと思います。
過去の経験から、サーバー上に文書ファイルを保存しようとする場合、日本語のファイル名では保存しない方がいいと思いますよ。
たとえば、濁点、半濁点付きのファイル名「ぱぴぷぺぽ.pdf」ファイルをそのままサーバに保存しておいて、ブラウザでダウンロードできるようにしようとすると
Windows クライアントでは正しく表示できるけど、MacOS では正しく処理できない、といった問題が発生してしまいます。

そこで、サーバに保存するときには、ファイル名を半角英数字のみにして保存するようにしてみました。
ファイル名を拡張子と分離するには、PHP の pathinfo() 関数を使います。
半角英数ファイル名の構造には、BASE64 エンコードと使えない文字を置き換える base64UrlEncode() 関数を使って処理します。

BASE64を使ったエンコード処理

function base64UrlEncode($data) 
{ 
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 
} 

ファイル名の拡張子以外の部分を base64UrlEncode で構築する処理を用意してみました。
入力フィールドで拡張子を指定しなかった場合は、.pdf ファイルだという処理も入れてみています。

function buildFileName($filename, $def_ext = 'pdf')
{
    $file_parts = pathinfo($filename);
    $filenode = $file_parts['filename'];
    $ext = $file_parts['extension'];
    if(strlen($ext) == 0)
    {
        $ext = $def_ext;
    }
    $name = $this->AsCommon->base64UrlEncode($filenode) . '.' . $ext;
    return $name;
}

日本語ファイル名が正しく処理されない

$filename = uildFileName("ぱぴぷぺぽ.pdf");
を実行すると、".pdf" という文字列が返ってきてしまいました・・・
またもや日本語処理に別の対応が必要になってしまいます・・・こまったもんだ・・・
調べると、ロケールの設定に依存しているため、というのがわかりまして。
setlocale(LC_ALL, 'ja_JP.UTF-8');
を実行してやると、上手くいくようになりました。

cakePHP での対応

pathinfo() やそのたぐいの関数を使うソースプログラムに、毎回 setlocale を記述するのは無駄を感じますね。
ましてや、プログラムを国際化対応しようとすると、あちこち変更しなければならなくなってしまいます。

cakePHP の config/app.php を見ると、次の記述があります。

'App' => [
    'encoding' => env('APP_ENCODING', 'UTF-8'),
    'defaultLocale' => env('APP_DEFAULT_LOCALE', 'ja_JP'),

これを利用して、config/bootstrap.php の 112 行目あたりに以下を追加しました。

setlocale(LC_ALL, Configure::read('App.defaultLocale') . '.' . Configure::read('App.encoding'));

まとめ

毎度のことながら、日本語対応は問題が多く発生しやすいですね。
国際化対応時に、上記対策でうまくいくのかどうかまでは試しておりません。あしからず。
今回も苦しめられましたが、なんとか事なきを得ました。

めでたしめでたし。

参考になったサイト
https://qiita.com/REAS07/items/3f86a0834d612edaecd6