CSV 是資料匯出最通用的格式,但 PHP 直接輸出常遇到:前導零被 Excel 自動去除、中文檔名亂碼、記憶體溢出、特殊字元破壞結構等問題。本文提供一個生產級導出函式,解決上述痛點。
/**
* 導出 CSV 檔案
*
* @param array $data 二維陣列,每筆為一列資料
* @param array $titleArr 標題列陣列
* @param string $fileName 檔名(不含副檔名),預設為時間戳
* @return void 直接輸出下載
*/
function exportCsv(array $data, array $titleArr, string $fileName = '') {
// 避免大資料導出超時
ini_set('max_execution_time', '3600');
ini_set('memory_limit', '512M');
$csvData = '';
// 1. 組裝標題列
$csvData .= implode(',', array_map(function($t) {
return '"' . str_replace('"', '""', $t) . '"';
}, $titleArr)) . "\r\n";
// 2. 組裝資料列(分塊處理,避免記憶體暴增)
foreach ($data as $row) {
$csvData .= implode(',', array_map(function($cell) {
// 雙引號轉義、前導零保護(加 \t)、換行符處理
$cell = str_replace('"', '""', (string) $cell);
// 前導零、長數字、科學記號保護:加前置 \t 強制 Excel 視為文字
if (preg_match('/^0\d+$/', $cell) || strlen($cell) > 11 || preg_match('/^[\d.]+$/', $cell)) {
$cell = "\t" . $cell;
}
return '"' . $cell . '"';
}, $row)) . "\r\n";
}
// 3. 編碼轉換:UTF-8 → CP936 (GBK) 相容 Excel 開啟不亂碼
$csvData = mb_convert_encoding($csvData, 'CP936', 'UTF-8');
// 4. 檔名處理:中文 URL 編碼,IE 相容空格處理
$fileName = $fileName ?: date('Y-m-d-H-i-s');
if (stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'MSIE') !== false || stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'Trident') !== false) {
$fileName = urlencode($fileName);
$fileName = str_replace('+', '%20', $fileName);
}
$fileName .= '.csv';
// 5. 輸出標頭
header('Content-Type: application/csv; charset=gbk');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Expires: 0');
header('Pragma: public');
echo $csvData;
exit;
}
| 問題 | 解決方案 |
|---|---|
| 前導零消失 (如 00123 → 123) | 儲存格值前加 \t (Tab),Excel 會視為純文字 |
| 長數字/科學記號 (身分證、銀行卡) | 同前導零處理,或在值前加單引號 '(但 CSV 標準不支援) |
| 雙引號、換行破壞欄位 | 雙引號轉義為 "",整欄包雙引號,換行保留於欄內 |
| 中文檔名下載亂碼 | IE/Edge 需 urlencode,現代瀏覽器支援 RFC 5987 (filename*=UTF-8''...) |
| Excel 開啟中文亂碼 | 輸出編碼轉為 CP936 (GBK),或加 BOM (EF BB BF) 維持 UTF-8 |
| 大資料記憶體溢出 | 分塊輸出、關閉輸出緩衝、直接 echo 每行、或使用 Generator 串流 |
function exportCsvStream(Generator $rows, array $titleArr, string $fileName = '') {
ini_set('max_execution_time', '0');
header('Content-Type: application/csv; charset=gbk');
header('Content-Disposition: attachment; filename="' . ($fileName ?: date('YmdHis')) . '.csv"');
header('X-Accel-Buffering: no'); // 關閉 Nginx 緩衝,即時串流
$out = fopen('php://output', 'w');
// 寫入 BOM,Excel 直接開啟 UTF-8 不亂碼
fwrite($out, "\xEF\xBB\xBF");
fputcsv($out, $titleArr);
foreach ($rows as $row) {
fputcsv($out, $row);
}
fclose($out);
exit;
}
// 使用範例:資料庫游標逐行讀取
$stmt = $pdo->query('SELECT * FROM huge_table');
exportCsvStream(
(function() use ($stmt) { while ($row = $stmt->fetch()) yield $row; })(),
['id', 'name', 'email'],
'large_export'
);
Generator 或資料庫游標,避免一次載入所有資料到記憶體。filename 與 filename* 兩種標頭,完整相容新舊瀏覽器。output_buffering、Nginx proxy_buffering off),實現真正串流下載。掌握 CSV 導出的編碼、轉義、串流技巧,能輕松應對從小型報表到千萬級資料匯出的各種場景。