PHP にはストリームという仕組みがあります。ざっくばらんに言えばこれは部分的にリソースを読み書きする仕組みです。メモリに優しく、また大量の動作を全て完了させずとも必要な部分だけ抜き出したりできます。
PHP: ストリーム – Manual
PHP でストリームを使うタイミングで最も多いのは恐らく fopen 関数を使ったファイルやURLの読み書きでしょう。
PHP: fopen – Manual
そして日本人で文字コードの変換を頻繁に行うのは SJIS と UTF-8 でしょう。Excel で読み書きする CSV ファイルのデフォルトがこれです。
よくある mb_convert_encoding 関数を用いてストリームの SJIS で書かれた CSV を UTF-8 として読む処理は次の様になります。
/**
* CSVファイルを読んで二次元配列化して返す
* @param string $file_path
* @return array CSVファイルの中身の二次元配列
*/
function get_csv_array($file_path)
{
$csv_body = [];
// ファイルオープン
if (($handle = fopen($file_path, 'rb')) !== false) {
// ファイルの1行をCSVフィールドとして配列で読み込み
while (($line = fgetcsv($handle)) !== false) {
$csv_body[] = array_map(
static function ($cell) {
// 読み込んだ配列の一つ一つ(セル)を SJIS から UTF-8に変換
return mb_convert_encoding($cell, 'UTF-8', 'sjis-win');
},
$line
);
}
// 読み込む行がなくなったのでクローズ
fclose($handle);
}
return $csv_body;
}
動くは動きますし正確な動作なのですが、行と列の二重ループの中で都度関数を呼び出すのが気に食わないです(読み込み対象のCSVファイルがよっぽど巨大でなければ実害はないです)。そこでこの二重ループを考えなくとも文字コードを変換する方法を紹介します。
ソースコードは以下です。
/**
* CSVファイルを読んで二次元配列化して返す
* @param string $file_path
* @return array CSVファイルの中身の二次元配列
*/
function get_csv_array($file_path)
{
$csv_body = [];
// ファイルオープン
if (($handle = fopen($file_path, 'rb')) !== false) {
// SJIS-winからUTF-8へ変換するフィルター
// @see https://www.php.net/manual/ja/function.stream-filter-append.php PHP: stream_filter_append - Manual
// @see https://www.php.net/manual/ja/function.iconv.php PHP: iconv - Manual
stream_filter_append($handle, 'convert.iconv.CP932/UTF-8//TRANSLIT', STREAM_FILTER_READ);
while (($line = fgetcsv($handle)) !== false) {
$csv_body[] = $line;// 既に UTF-8 として読み込まれたセルの配列を返り値用配列に追加
}
fclose($handle);// 読み込む行がなくなったのでクローズ
}
return $csv_body;
}
PHP にはストリームに対して文字コードを変換するフィルターが存在し、これを用いることによって高速かつシンプルなコードで文字コード変換を行えます。
使っているのはソースコードのコメント中にもある様に stream_filter_append という関数です。
PHP: stream_filter_append – Manual
この関数ではストリームに流れるデータを加工するフィルターを追加できます。使えるフィルターは stream_get_filters で取得でき、stream_filter_register で自由に追加できます。
PHP: stream_get_filters – Manual
PHP: stream_filter_register – Manual
PHP: iconv – Manual
ソースコードで用いている ‘convert.iconv.CP932/UTF-8//TRANSLIT’ はおそらく PHP 内の iconv 関数を PHP 内部で適用しています(PHP内部の関数のハッシュテーブルっぽいものにフィルターの有無を聞いてる感じ)。iconv 関数は文字コード変換関数です。これを / の前半を第一引数、後半を第二引数として呼び出している感じです。iconv 関数は名前的におそらく Linux の iconv コマンド相当で動きます。