Excel 作為商業資料交換通用格式,PHP 專案常需匯入 .xls/.xlsx 檔案。本文以經典 Spreadsheet_Excel_Reader 庫為例,詳細講解從安裝配置到資料讀取、編碼處理、錯誤處理的完整流程,並提供生產級程式碼範例。
vendor/Excel/)iconv 或 mbstring(編碼轉換必需)<?php
require_once __DIR__ . '/vendor/Excel/reader.php';
// 1. 實例化讀取器
$data = new Spreadsheet_Excel_Reader();
// 2. 設定輸出編碼(關鍵:防止中文亂碼)
$data->setOutputEncoding('UTF-8'); // 現代專案建議 UTF-8
// 3. 可選:指定編碼轉換引擎(預設 iconv,可改 mbstring)
// $data->setUTFEncoder('mb');
// 4. 可選:設定行/列索引起始值(預設 1,改 0 符合陣列習慣)
// $data->setRowColOffset(0);
// 5. 可選:設定數值格式化
// $data->setDefaultFormat('%.2f'); // 預設兩位小數
// $data->setColumnFormat(4, '%.3f'); // 第 4 欄三位小數
// 6. 讀取 Excel 檔案
$filePath = __DIR__ . '/uploads/data.xls';
if (!$data->read($filePath)) {
die('檔案讀取失敗:' . $filePath);
}
// 7. 取得資料維度
$rowCount = $data->sheets[0]['numRows'];
$colCount = $data->sheets[0]['numCols'];
echo "共 {$rowCount} 列,{$colCount} 欄\n";
// 8. 遍歷資料(索引從 1 開始,除非 setRowColOffset(0))
for ($i = 1; $i <= $rowCount; $i++) {
$rowData = [];
for ($j = 1; $j <= $colCount; $j++) {
$cell = $data->sheets[0]['cells'][$i][$j] ?? '';
$cellInfo = $data->sheets[0]['cellsInfo'][$i][$j] ?? [];
// 進階:根據型別處理
switch ($cellInfo['type'] ?? 'unknown') {
case 'date':
// Excel 日期為序數,需轉換
$cell = date('Y-m-d', ($cell - 25569) * 86400);
break;
case 'number':
// 可套用格式化
break;
}
$rowData[] = $cell;
}
// 處理單行資料(如寫入資料庫)
processRow($rowData);
}
function processRow(array $row) {
// 實際業務邏輯:驗證、轉換、入庫
// 範例:列印
echo implode(', ', $row) . "\n";
}
?>
讀取成功後,$data->sheets[0] 包含:
| 鍵名 | 說明 |
|---|---|
| numRows | 總行數 |
| numCols | 總欄數 |
| cells[i][j] | 第 i 行第 j 欄的格式化後值 |
| cellsInfo[i][j]['type'] | 型別:date/number/unknown |
| cellsInfo[i][j]['raw'] | 原始未格式化值 |
| cellsInfo[i][j]['colspan'] | 橫向合併欄數 |
| cellsInfo[i][j]['rowspan'] | 縱向合併列數 |
setOutputEncoding('UTF-8') 配合 iconv/mbstring 自動轉碼setUTFEncoder('mb') 強制使用 mbstringExcel 日期儲存為「1900/1/1 起的天數序數」。轉換公式:
$unixTimestamp = ($excelSerial - 25569) * 86400;
$dateStr = date('Y-m-d', $unixTimestamp);
注意:Excel 錯誤地將 1900 視為閏年,1900/3/1 後日期需減 1 天修正。
Spreadsheet_Excel_Reader 會將整個檔案載入記憶體。大檔案(>10MB)建議改用:
ReadFilter)、記憶體優化本庫僅支援 .xls (BIFF 格式)。.xlsx 請使用 PhpSpreadsheet 或 Box/Spout。
class ExcelImporter {
private $reader;
public function __construct(string $encoding = 'UTF-8') {
$this->reader = new Spreadsheet_Excel_Reader();
$this->reader->setOutputEncoding($encoding);
}
public function import(string $filePath, callable $rowHandler, array $options = []): int {
if (!$this->reader->read($filePath)) {
throw new \RuntimeException("無法讀取檔案: $filePath");
}
$sheet = $this->reader->sheets[0] ?? null;
if (!$sheet) throw new \RuntimeException('檔案無工作表');
$count = 0;
$startRow = $options['start_row'] ?? 1; // 跳過標題列
for ($i = $startRow; $i <= $sheet['numRows']; $i++) {
$row = [];
for ($j = 1; $j <= $sheet['numCols']; $j++) {
$row[] = $sheet['cells'][$i][$j] ?? null;
}
// 過濾空行
if (array_filter($row)) {
$rowHandler($row, $i);
$count++;
}
}
return $count;
}
}
// 使用範例
$importer = new ExcelImporter();
$imported = $importer->import('uploads/order.xls', function($row, $rowNum) {
// $row[0] = 訂單號, $row[1] = 客戶, $row[2] = 金額...
Order::create([
'order_no' => $row[0],
'customer' => $row[1],
'amount' => (float)$row[2],
]);
}, ['start_row' => 2]);
echo "成功匯入 {$imported} 筆訂單。";
| 庫 | 支援格式 | 記憶體效率 | 功能豐富度 | 學習曲線 |
|---|---|---|---|---|
| PhpSpreadsheet | .xls, .xlsx, .ods, .csv | 中(支援串流) | 極高(樣式、圖表、公式) | 中 |
| Box/Spout | .xlsx, .ods, .csv | 極高(純串流) | 中(核心讀寫) | 低 |
| OpenSpout | .xlsx, .ods, .csv | 極高 | 中 | 低 |
| FastExcel | .xlsx | 高 | 低(Laravel 整合佳) | 極低 |
Spreadsheet_Excel_Reader 是處理舊版 .xls 檔案的經典選擇,但不支援 .xlsx、記憶體效率低、維護狀態低。新專案強烈建議直接採用 PhpSpreadsheet(功能全)或 Box/Spout(效能極致)。無論選用何庫,核心流程皆為:實例化 → 設定編碼 → 讀取 → 遍歷轉換 → 業務入庫,並務必處理編碼、日期、合併儲存格、大檔案記憶體四大痛點。