Excelで文字が自動変換されないcsvをPHP(Laravel)で出力する技術

本投稿は TECOTEC Advent Calendar 2020 の12日目の記事です。

決済認証システム開発事業部の吉田です。
ECサイトの保守運用・機能改修の担当をしています。

本稿では私が担当した案件でのexcelで開くcsvをPHP(Laravel)で出力する方法を紹介します。

はじめに

管理画面から会員一覧情報や注文一覧情報などの一覧情報を出力等する機能を実装する場合がありますが、その際に用いられるのがcsv出力です。
データの連携や出力で利用されやすいフォーマットの一つだと思います。
いずれの場合でも、双方での解釈が合うフォーマットで実装をする必要があります。
今回は運営側が出力した情報をexcelで表示する想定で実装しています。

fputcsvを信用しない

php csv 出力と検索をすると上位でヒットするfputcsv関数ですが、とりあえずcsvを出力するだけであれば問題はありません。
しかし、同時にphp csv 自前 出力で検索をするとfputcsv関数が良き様に動いてくれるおかげで悩む人もいる様です。
今回の私も、fputcsvでは困難な処理に対しての対応をしました。

excelで解釈させるために

出力データサンプル

今回、サンプルデータとして以下を用意して出力しています。

  • ファイル名 => 文字列
  • ヘッダ行の内容 => 1次配列
  • 会員情報 => 2次配列

必要なデータを抽出し、ファイル名、ヘッダ行の情報、会員情報として持たせています。

//// 取得しているデータ
$filename = "会員情報一覧.csv";
$headers = [
    "id",
    "name",
    "mail_address",
    "phone_number",
    "address1",
    "address2",
];
$datalist=[ 
    [
        "id"           => "1000001234",
        "name"         => "梃子太郎",
        "mail_address" => "teco.tarou@tecotec.co.jp",
        "phone_number" => "0364470860",
        "address1"     => '東京都港区赤坂7丁目',
        "address2"     => '1-16',
    ],
    [
        "id"           => "0090002468",
        "name"         => "テコ二郎",
        "mail_address" => "teco.jirou@tecotec.co.jp",
        "phone_number" => "0364470860",
        "address1"     => '東京都港区赤坂7丁目',
        "address2"     => '1-16-7',
    ]
];
//////////////////

今回、私の対応したプロジェクトではLaravelを利用しているので、会員情報はCollectionでもよいかもしれません。

出力の実装

今回のメイントピックである 文字が自動変換 される候補は以下のものです。

  • id =>n桁固定で頭0埋め、桁の多いID、などのID体系だと0が抜かれたり指数表示になったりします
  • phone_number => 0から始まるので頭の0が抜かれます
  • address2 =>何丁目、何番地、何号をサンプルの様に数字とハイフンで入力された場合、日付型になってしまう場合があります

これらの対策として、 $searchArrayにあるヘッダの内容にイコールとダブルクォーテーションを加える事による文字の固定化を行っています。
この文字の固定化を柔軟に行うためにfputcsvだと入力された文字列を自動解釈してダブルクォーテーションをつけたりつけなかったりをするので不適でした。
また、ダブルクォーテーションをつけるだけでも上記の自動変換がされてしまうため、文字の固定を行うために苦肉の策で付けたのがイコールとダブルクォーテーションの実装でした。

サンプルコード

LaravelのControllerからこの関数を呼んでreturnさせる事でcsvの出力を実装しています。

<?php

class CsvOutput
{
    public static function output(string $filename, array $headers, array $datalist)
    {
        // ファイルポインタのopen
        $stream = fopen('php://temp', 'r+b');

        // Excelの仕様で数値になってしまうものを文字列として表示するためのリスト
        $searchArray = [
            'id'          => true,
            'phone_number'=> true,
            'address2'    => true,
        ];

        foreach ($datalist as $row) {
            $out = "";
            $cnt = 1;
            
            foreach($row as $index => $col){
                // 要素をダブルクォーテーションで囲む
                if(isSet($searchArray[$index])){
                    $out .= '="' . $col . '"';
                } else {
                    $out .= '"' .$col. '"';
                }

                // 要素が最後かどうかで区切り要素か改行文字を挿入
                if($cnt < count($row)){
                    $out .= ",";
                }else{
                    $out .="\n";
                }
                $cnt++;
            }
            fwrite($stream, $out);
        }

        rewind($stream);
        $csv = str_replace(PHP_EOL, "\r\n", stream_get_contents($stream));
        
        // ファイルポインタのclose
        fclose($stream);

        // csvをexcelで開くためにSJIS-winでコンバート
        $csv = mb_convert_encoding($csv, 'SJIS-win', 'UTF-8');
        $headers = array(
            'Content-Type'            => 'text/csv',
            'Content-Disposition' => "attachment; filename=$filename",
        );
        return \Response::make($csv, 200, $headers);
    }
}

データ連携あるある

csvについては規格が曖昧な箇所が多く、実装や解釈が各ソフトウェア依存なので、プロジェクト毎に仕様を定めておく必要があります。
csvに限らず、BOM問題・文字コード問題・改行コード問題などなど、連携するシステムに応じてデータの連携で頭を悩ませる事が多いと思います。
本稿ではexcelで開く前提のcsv出力を紹介しましたが、データ連携を実施する場合は仕様を擦り合わせて実装をしてください。

おわりに

テコテックは一緒に働ける人材を募集しています。
私の所属する決済認証システム開発事業部ではECシステムやチケット決済システムの開発を主に行っています。
興味がありましたら、下記リンクよりご応募ください。

明日は投資戦略システム事業部の古谷さんの記事になります。お楽しみに。 www.tecotec.co.jp