1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Laravel] Windowsで文字化けしないCSVのエクスポート機能を作ろうとしたら、意外と詰まった話

Last updated at Posted at 2024-03-28

はじめに

個人でシステム開発の仕事をしています。
管理画面を開発しているときに、Windowsのエクセルでも文字化けしないようなCSVインポート機能を実装しようとしたんですが、
SJIS変換してもBOMをつけてもなかなか解決できなかったので、その解決方法を備忘録として残しておこうと思います。

開発環境

  • php 8.2
  • laravel 10
  • docker

やりたいこと

header_1 header_2 header_2
body_a1 body_a2 body_a3
body_b1 body_b2 body_b3

上のようなCSVをエクスポートし、macでもwindowsでも文字化けせずにCSVを表示したい。

  • windowsではexcelで開くことを想定しています。
  • headerはソース内で定義し、bodyはMySQLから持ってきた値です。

試したこと

SJIS-winに変換(うまくいかなかった)

まずは、単純にUTF-8をSJISに変換しました。

mb_convert_encoding($text, 'SJIS-win', 'UTF-8');

しかし、エクスポートしたCSVを開くと、カタカナの濁音が ? となっていまいました。

具体的には「はばぱハバパ」のように濁音と半濁音のひらがなカタカナの文字列があったときに
「はばぱハハ?パ」と表記されてしました。

BOMの追加(うまくいかなかった)

BOM付きのUTF-8にすることで、ExcelがUTF-8で開いてくれると思い、
fopenした直後くらいに以下の一文を追加しましたがうまく行かず。

fwrite($handle, pack('C*', 0xEF, 0xBB, 0xBF));

0xEF, 0xBB, 0xBF はUTF-8のBOM表す3バイトのシーケンスで、CSVがUTF-8エンコーディングであることを示しています。

正規化 + SJIS-winに変換(うまくいった)

Unicodeで正規化して、正規化されたものをSJISに変換してうまくいきました。

use Normalizer;

Normalizer::normalize($field, Normalizer::FORM_C);
return mb_convert_encoding($normalizedField, 'SJIS-win', 'auto');

Normalizer::FORM_C で異なる方法の文字エンコードを統一した方法に正準合成します。
https://www.php.net/manual/ja/class.normalizer.php

データ取得先の MySQL が utf8mb4utf-8 の拡張表現なのが影響していました。
そのため、正規化してSJIS変換でうまくいきました。

以下にサンプルコードを記述します。

サンプルコード

  • 前提
    • TestDataというモデルからbodyのデータを取得することを想定しています。
    public function exportCsv(Request $request)
    {
        // データを取得
        $query = TestData::query();

        // CSVヘッダー
        $headers = ['ID', '名前', 'テストデータ1', 'テストデータ2', '作成日時', '更新日時'];
        $headers = array_map(function ($header) {
            return mb_convert_encoding($header, 'SJIS-win', 'UTF-8');
        }, $headers);
        $callback = function() use ($query, $headers) {
            $handle = fopen('php://output', 'w');
            fputcsv($handle, $headers);

            // データを一行ずつ書き込む
            foreach ($query->cursor() as $row) {
                $csvData = [
                    $row->id,
                    $row->user_name,
                    $row->test_data_1,
                    $row->test_data_2,
                    $row->created_at,
                    $row->updated_at,
                ];

                // 各行のデータをShift-JISに変換して出力
                $csvData = array_map(function ($field) {
                    $normalizedField = Normalizer::normalize($field, Normalizer::FORM_C);
                    return mb_convert_encoding($normalizedField, 'SJIS-win', 'auto');
                }, $csvData);

                fputcsv($handle, $csvData);
            }
            fclose($handle);
        };

        $fileName = 'test_data_' . date('Ymd') . '.csv';

        return new StreamedResponse($callback, 200, [
            'Content-Type' => 'text/csv',
            'Content-Disposition' => "attachment; filename=\"$fileName\"",
        ]);
    }

最後に

同じWindowsでも使っているExcelのバージョンなどによっても文字化けする/しないがあるので、文字コードの管理は難しいですね。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?