CSV(Comma-Separated Values)是最通用的表格交換格式。PHP 內建 fputcsv、fgetcsv 使得陣列與 CSV 雙向轉換極為簡單。本文提供「陣列寫入 CSV 下載」與「讀取 CSV 解析為陣列」兩個完整、可直接部署的範例,並處理中文編碼、大檔案記憶體優化等實務細節。
<?php
/**
* 將二維陣列輸出為 CSV 檔案下載
* @param array $data 二維陣列,每個子陣列為一列
* @param string $filename 不含副檔名的檔名
* @param string $encoding 輸出編碼,預設 big5(Excel 相容),可改 utf-8
*/
function outputCSV(array $data, $filename = 'export', $encoding = 'big5') {
// 設定下載標頭
header('Content-Type: text/csv; charset=' . $encoding);
header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
header('Pragma: no-cache');
header('Expires: 0');
$out = fopen('php://output', 'w');
// 可選:寫入 BOM,讓 Excel 直接雙擊開啟 UTF-8 不亂碼
if (strtolower($encoding) === 'utf-8') {
fwrite($out, "\xEF\xBB\xBF");
}
foreach ($data as $row) {
// 編碼轉換
if (strtolower($encoding) !== 'utf-8') {
$row = array_map(function($v) use ($encoding) {
return iconv('utf-8', $encoding . '//IGNORE', $v);
}, $row);
}
fputcsv($out, $row);
}
fclose($out);
exit; // 避免後續輸出汙染 CSV
}
// ---------- 使用範例 ----------
$data = [
['姓名', '性別', '年齡', 'Email', '電話'],
['王小明', '男', 28, 'wang@example.com', '0912-345678'],
['李小美', '女', 25, 'li@example.com', '0923-456789'],
['陳大同', '男', 32, 'chen@example.com', '0934-567890'],
];
outputCSV($data, 'member_list', 'big5'); // 或 'utf-8'
\xEF\xBB\xBF)。fopen('php://output', 'w') 直接寫入 HTTP 回應主體,不佔用暫存檔空間。exit 防止框架/模板輸出額外 HTML 破壞 CSV 格式。<?php
/**
* 讀取 CSV 檔案,回傳關聯式陣列(第一列為欄位名)
* @param string $filepath CSV 檔案路徑
* @param string $inputEncoding 檔案編碼,預設 big5
* @param int $maxLineLen 單行最大長度,0 為不限制(PHP 5.1+)
* @return array 結構:[ ['欄位1'=>值, '欄位2'=>值, ...], ... ]
*/
function readCSV($filepath, $inputEncoding = 'big5', $maxLineLen = 0) {
if (!file_exists($filepath) || !is_readable($filepath)) {
throw new InvalidArgumentException("檔案不存在或不可讀:$filepath");
}
$handle = fopen($filepath, 'r');
if (!$handle) {
throw new RuntimeException("無法開啟檔案:$filepath");
}
$header = null;
$data = [];
$lineNum = 0;
while (($row = fgetcsv($handle, $maxLineLen)) !== false) {
$lineNum++;
// 跳過空行
if (count($row) === 1 && $row[0] === '') continue;
// 編碼轉為 UTF-8 內部處理
if (strtolower($inputEncoding) !== 'utf-8') {
$row = array_map(function($v) use ($inputEncoding) {
return iconv($inputEncoding, 'utf-8//IGNORE', $v);
}, $row);
}
if ($header === null) {
$header = $row; // 第一列當標題
continue;
}
// 欄位數不一致時補齊或截斷
if (count($row) < count($header)) {
$row = array_pad($row, count($header), '');
} elseif (count($row) > count($header)) {
$row = array_slice($row, 0, count($header));
}
$data[] = array_combine($header, $row);
}
fclose($handle);
return $data;
}
// ---------- 使用範例 ----------
try {
$records = readCSV('/path/to/uploaded.csv', 'big5');
foreach ($records as $rec) {
// 直接以欄位名存取
echo "{$rec['姓名']} / {$rec['Email']} / {$rec['電話']}
";
}
} catch (Exception $e) {
echo '讀取失敗:' . $e->getMessage();
}
若 CSV 超過百 MB,一次載入陣列會爆記憶體。可用 Generator 逐行處理:
function readCSVChunked($filepath, $inputEncoding = 'big5') {
$handle = fopen($filepath, 'r');
$header = null;
while (($row = fgetcsv($handle)) !== false) {
if (strtolower($inputEncoding) !== 'utf-8') {
$row = array_map(fn($v) => iconv($inputEncoding, 'utf-8//IGNORE', $v), $row);
}
if ($header === null) {
$header = $row;
continue;
}
yield array_combine($header, $row);
}
fclose($handle);
}
// 使用:foreach (readCSVChunked('big.csv') as $row) { ... }
| 問題 | 原因 | 解決 |
|---|---|---|
| Excel 開啟 CSV 中文亂碼 | 編碼不符 | 輸出 Big5 或 UTF-8+BOM |
| 欄位含逗號/換行/雙引號導致欄位錯位 | 未正確逸出 | 用 fputcsv 自動處理,勿手動拼字串 |
| 長數字(身分證、卡號)變科學記號 | Excel 自動偵測數值 | 寫入前加單引號 ' 或用 HTML 表格法 style="vnd.ms-excel.numberformat:@" |
| fgetcsv 讀到一半停止 | 欄位含未閉合雙引號、換行 | 確認 CSV 產生端正確逸出;必要時自寫解析器 |
| 記憶體耗盡 | 大檔一次 file() 或 array_map | 改用 Generator 逐行處理 |
fputcsv + php://output + 編碼轉換 + exit。fgetcsv + iconv + 關聯式陣列回傳;大檔用 Generator。掌握這兩個函式,即可輕鬆處理 90% 的 CSV 匯入匯出需求,無需引入重型第三方套件。