PHP
AdventCalendar
CSV
adventcalendar2017
PHPDay 19

【PHP】csvを自前で作った話 | ダブルコーテーション問題でfputcsvは使わず実装

やりたいこと

phpでcsvファイルを作成するにあたり、
全てのフィールドをダブルコーテーションで囲うプログラムを作りたい

fputcsv()を使ってcsvを作る

ダブルコーテーションで「くくられる」or「くくられない」

sample
    $header = [
        'MakeCsv',
        'SampleReport',
        'Php',
        'Date',
        'Csv'
    ];
    $data = [
        "hoge",
        "ho ge",//半角スペース1つ
        "ho  ge",//半角スペース2つ
        "ho ge",//全角スペース1つ(全角スペースは$enclosureでくくられない)
        "hoge"
    ];
    $delimiter = ',';//引数として設定しなくてもデフォでカンマ区切り
    $enclosure = '"';//引数として設定しなくても必要があればダブルコーテーションでくくってくれる?
    $escape ; //今回未使用だが、エスケープしたい文字があれば指定する
    $csvFile = fopen('test.txt', 'w');
    fputcsv($csvFile,$header, $delimiter, $enclosure);
    fputcsv($csvFile, $data, $delimiter, $enclosure);
    fclose($csvFile);

出力結果

test.txt
    MakeCsv,SampleReport,Php,Date,Csv
    hoge,"ho ge","ho  ge",ho ge,hoge

このように、fputcsvというphpの関数を使用してみたが何かと不都合が多かったです。
http://php.net/manual/ja/function.fputcsv.php

delimiter

delimiterの設定は上手くいきました。
そもそも引数として取らなくても、デフォルトで「,」(カンマ)区切りとなっています。

enclosure

enclosureの設定が上手くいきませんでした。
一文字で区切るためのオプションですが、
フィールドに半角スペースが入っていると、デブルコーテーションでくくられますが、
全角スペースが入っていても、くくられない問題があります。

書き方の問題かと思い、エスケープ「/"」などを試してみましたが、どうやらそうゆう問題ではなさそうで、
enclosureの「"」は、エスケープ済みであるらしく、バックスラッシュで区切る必要はないみたいです。
しかし、だからといってダブルコーテーションでくくられるわけではありませんでした。

php5.4以降に出来たオプション escape_char と組み合わせても、うまくいかなかったです。
http://d.hatena.ne.jp/miau/20141214/1418574582

fputcsvを使って「""」(ダブルコーテーション)を必ず囲うという組み合わせは、諦める方向で判断しました(´Д` )

結論!! 文字列として、自前でcsvにする

配列を文字列にし、必要な文字を追加していきました。
「"」「対象の値」「"」「,」「/n」の順で、文字列として追加
※cakePHP3を使用しているので、new Folderなどは適時置き換えて下さい。

sample
    private function _createCSV($data, $header, $fileName)
    {
        $folderPath = TMP . 'csv-sheets/';
        $dir = new Folder();
        $dir->create($folderPath);

        $stream = fopen($folderPath . $fileName, 'w');
        fwrite($stream, $header);
        foreach ($data as $row) {
            $out = '';
            $row_tmp = '"';
            $row_tmp .= implode('","', $row);
            $row_tmp .= '"' . "\n";
            $out .= $row_tmp;
            fwrite($stream, $out);
        }
        fclose($stream);
    }

これで、すべての値がダブルコーテーションでくくられることが出来ました。

※関数fwriteは、配列を引数としてとれないので、ヘッダーに関してはフィールドの値が決まっているため、直で文字列で格納しています。

$header = '"MakeCsv","SampleReport",Php,"Date","Csv"' . "\n";

ダブルコーテーションにこだわる必要はあったのか

今回はcsvファイルを作成する上で、ダブルコーテーションを必ず付与するという仕様のためダブルコーテーションにこだわりました。

しかしそもそも、csvファイルとしては、すべてにダブルコーテーションをつける必要はなかったのではないかと疑問を抱きました。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11145768576

スペースが間に入っていたりすると、その区切りとして、ダブルコーテーションが必要で、
fputcsvは、間にスペースがある値にダブルコーテーションを囲っていて、それ以外は囲ってないのは、それで問題がないから囲ってないのかもしれない。

懸念点を一つあげるとしたら、windowsのエクセルがshift-jisなため、shift-jisに変更しておかないとダメな可能性があります

RFC 規約を見てみる

RFCによると、ダブルコーテションをつけるつけないは、どっちでも良いみたいです。

ただし、フィールドにダブルコーテーションがある場合は、そのフィールドをダブルコーテーションでくくらないといけないなどの例外はある模様でした。
http://www.kasai.fm/wiki/rfc4180jp

ダブルコーテーションでくくらないといけない理由が少しでもあると救われます^_^;