PHP8のmb_detect_encoding()

こんにちは。
先日、CSVをPHPで読み込む処理を書いていたときに、ちょっと驚くことがありました。

複数文字コード指定で思わぬ判定

CSVで商品情報などを一括登録する機能を作成していたところ、
いつものように、アップロードされたCSVを取り込む時にカラムの整合性をチェックしていたら、
元のカラムと変更していないのにエラーになってしまいました。

$csv = file_get_contents('data.csv');
$enc = mb_detect_encoding($csv, "ASCII,JIS,UTF-8,EUC-JP,SJIS", true);

この CSV は SJIS で作られていたので、「いつもどおりSJIS と判定されるはず」と思っていました。なんかでも PHP8 で実行したら ASCII になったな……?
実際のCSVの中身は日本語も含まれているのに、何故かASCIIと認識されてしまいました……。
調べてみると、PHP8では mb_detect_encoding() の内部ロジックが変更され、複数の文字コードを指定している場合、最初に「安全に読める」と判断されたものが返る傾向があります。
つまり、SJISも指定していても、文字コード上問題がないASCIIが優先され、結果としてASCII判定になったようでした。

実務での対応

文字化けが起きるとCSVの読み込み処理が止まってしまうので、今回は対応策として複数文字コード指定をやめ、ファイルごとに文字コードを明示的に指定することにしました。

// SJIS ファイルなら $csv = file_get_contents('data.csv');
$csv = mb_convert_encoding($csv, 'UTF-8', 'SJIS');

この方法で、文字コードの判定に悩むことなく、安定してCSVを読み込めるようになりました。

まとめ

  • PHP8 では mb_detect_encoding() の複数文字コード指定で、期待と違う判定が返ることがある
  • CSVの中身次第では、SJISファイルでもASCII判定される場合がある……
  • 安定させるには、複数文字コード指定をやめて、ファイルごとにエンコーディングを明示する

ちょっとした仕様の変化ですが、業務で使うCSVの読み込みでは影響が大きく、予想外の結果に戸惑いました。