PHP Tips | 文字列処理:実務向け便利系 - CSV 用エスケープ

PHP PHP
スポンサーリンク

なぜ「CSV 用エスケープ」が必要になるのか

業務で CSV を扱うとき、よくあるのが「Excel で開ける CSV を出したい」「システム間連携用に CSV を吐きたい」というパターンです。ここで雑に文字列をカンマでつないでしまうと、すぐに壊れます。

値の中にカンマが入っている。
値の中に改行が入っている。
値の中にダブルクォートが入っている。

こういうとき、正しく「エスケープ」しておかないと、CSV として解釈できなくなります。CSV 用エスケープは、「1 セル分の文字列を、CSV 仕様に沿った安全な形に変換する」処理だとイメージしてください。


CSV の基本ルールをざっくり押さえる

CSV の細かい仕様はいろいろありますが、実務で最低限覚えておきたいルールは次のとおりです。

カンマや改行を含むフィールドは、ダブルクォート " で囲む。
フィールド内にダブルクォート " がある場合は、"" と 2 つ重ねて書く。

例えば、こういう値があったとします。

こんにちは, 世界
彼は「天才」です

これをそのまま CSV に書くと、カンマやダブルクォートのせいで区切りが壊れます。正しくはこうなります。

"こんにちは, 世界"
"彼は""天才""です"

この「囲む」「二重にする」を自動でやってくれるのが、CSV 用エスケープです。


PHP 標準の fputcsv を知っておく

実は「自前でエスケープしなくていい」場面が多い

PHP には、CSV を正しく出力してくれる fputcsv() という関数があります。これを使えば、1 行分の配列を渡すだけで、カンマ区切り+必要なエスケープを全部やってくれます。

$row = [
    'id'      => 1,
    'name'    => '山田, 太郎',
    'comment' => '彼は「天才」です',
];

$fp = fopen('php://output', 'w');
fputcsv($fp, $row);  // カンマ区切りの 1 行を出力
fclose($fp);
PHP

このコードが出力するのは、例えばこんな感じです。

1,"山田, 太郎","彼は""天才""です"

カンマを含む name がダブルクォートで囲まれ、
comment 内の「」が "" に変換されているのが分かります。

もし「ファイルに書きたい」「ダウンロードさせたい」などの用途なら、まずは fputcsv() を使うのが一番簡単で安全です。


それでも「1 セルだけエスケープしたい」ことがある

自前のエスケープ関数を作る

ときどき、「配列を一気に CSV にするのではなく、1 セルずつ文字列を組み立てたい」という場面があります。そのときに使えるのが「1 フィールド用のエスケープ関数」です。

CSV のルールに従うと、やることはこうです。

値の中にカンマ・改行・ダブルクォートが含まれていたら、ダブルクォートで囲む。
中にあるダブルクォートは "" に置き換える。

これをコードにすると、こうなります。

/**
 * 1 フィールド分の文字列を CSV 用にエスケープする
 */
function csv_escape_field(string $value): string
{
    $needsQuote = strpbrk($value, ",\"\r\n") !== false;

    if ($needsQuote) {
        // ダブルクォートを "" に置き換える
        $escaped = str_replace('"', '""', $value);
        // 全体をダブルクォートで囲む
        return '"' . $escaped . '"';
    }

    // 特殊文字がなければそのまま
    return $value;
}
PHP

使い方の例です。

echo csv_escape_field('山田太郎');          // 山田太郎
echo csv_escape_field('山田, 太郎');        // "山田, 太郎"
echo csv_escape_field('彼は「天才」です');  // 彼は「天才」です(ダブルクォートなし)
echo csv_escape_field('彼は"天才"です');    // "彼は""天才""です"
PHP

ここでのポイントは、「カンマ・改行・ダブルクォートが含まれているかどうかを判定してから、必要なときだけ囲む」というところです。毎回必ずダブルクォートで囲んでも CSV としては問題ありませんが、出力が少し読みにくくなるので、実務ではこのように条件付きで囲むことが多いです。


行全体を組み立てるユーティリティに発展させる

配列 → 1 行の CSV 文字列

先ほどの csv_escape_field() を使えば、配列から 1 行分の CSV を自前で組み立てることもできます。

/**
 * 配列から 1 行分の CSV 文字列を生成する
 */
function array_to_csv_line(array $fields, string $delimiter = ','): string
{
    $escaped = [];

    foreach ($fields as $value) {
        $escaped[] = csv_escape_field((string)$value);
    }

    return implode($delimiter, $escaped) . "\r\n";
}
PHP

使い方の例です。

$row = [1, '山田, 太郎', '彼は"天才"です'];

echo array_to_csv_line($row);
// 出力: 1,"山田, 太郎","彼は""天才""です"
PHP

ファイルに書き込む場合は、これを fwrite() で順番に書いていけば、fputcsv() とほぼ同じことができます。


Excel 向け CSV で気をつけたいこと(軽く触れておく)

細かくやり始めるとキリがないのですが、実務で Excel 向け CSV を出すときに、よく出てくる話題が 2 つあります。

文字コードを Shift_JIS(CP932)にするか UTF-8 にするか。
先頭が =+ などの値を「数式」と誤認識されないようにするか。

これらは「CSV 用エスケープ」というより、「Excel 向けの追加対策」に近いので、ここでは深掘りしません。ただ、「Excel で開く前提なら、文字コードと先頭文字にも気を配る必要がある」と頭の片隅に置いておくと、後でハマりにくくなります。


まとめ:今日からの「CSV 用エスケープ」ユーティリティ

CSV 用エスケープの本質は、「1 セル分の文字列を、カンマや改行、ダブルクォートを含んでいても安全に CSV として扱える形にする」ことです。そのためにやるべきことは、次の 2 点に集約されます。

必要ならダブルクォートで囲む。
中のダブルクォートは "" に置き換える。

PHP では、まず fputcsv() を使うのが一番簡単で安全です。それでも「1 フィールドだけエスケープしたい」場面のために、次のようなユーティリティを持っておくと便利です。

function csv_escape_field(string $value): string
{
    $needsQuote = strpbrk($value, ",\"\r\n") !== false;

    if ($needsQuote) {
        $escaped = str_replace('"', '""', $value);
        return '"' . $escaped . '"';
    }

    return $value;
}

function array_to_csv_line(array $fields, string $delimiter = ','): string
{
    $escaped = [];

    foreach ($fields as $value) {
        $escaped[] = csv_escape_field((string)$value);
    }

    return implode($delimiter, $escaped) . "\r\n";
}
PHP

もし、あなたのコードが「implode(',', $row) でそのまま CSV を作っている」ような状態なら、そこがこのユーティリティに差し替えるべきポイントです。たった数行のエスケープを挟むだけで、「Excel で開いても壊れない CSV」に一気に近づきます。

PHPPHP
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました