PHP 直接輸出 Excel 時會發送 HTTP Header 導致瀏覽器下載,無法同時作為附件發送郵件。解決關鍵在於捕獲輸出內容到變數/文件,而非直接輸出給瀏覽器。
<?php
require 'vendor/autoload.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
function generateExcelContent(array $title, array $data): string
{
// 1. 開啟輸出緩衝
ob_start();
// 2. 發送 Excel Header(此處僅為模擬,實際不輸出給瀏覽器)
// header("Content-type:application/vnd.ms-excel");
// header("Content-Disposition:attachment;filename=export.xls");
// 3. 輸出內容(實際寫入緩衝區)
if (!empty($title)) {
$title = array_map(fn($v) => iconv('UTF-8', 'GB2312//IGNORE', $v), $title);
echo implode("\t", $title) . "\n";
}
if (!empty($data)) {
foreach ($data as $row) {
$row = array_map(fn($v) => iconv('UTF-8', 'GB2312//IGNORE', $v), $row);
echo implode("\t", $row) . "\n";
}
}
// 4. 獲取緩衝內容並關閉緩衝
return ob_get_clean();
}
function sendExcelEmail(string $to, string $subject, string $body, string $excelContent, string $filename = 'export.xls'): bool
{
$mail = new PHPMailer(true);
try {
// SMTP 配置
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'sender@example.com';
$mail->Password = 'your_password';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->CharSet = 'UTF-8';
// 發件/收件
$mail->setFrom('sender@example.com', '系統通知');
$mail->addAddress($to);
$mail->Subject = $subject;
$mail->msgHTML($body);
// 核心:將字符串內容作為附件添加
$mail->addStringAttachment($excelContent, $filename, 'base64', 'application/vnd.ms-excel');
return $mail->send();
} catch (Exception $e) {
error_log("郵件發送失敗: {$mail->ErrorInfo}");
return false;
}
}
// 使用範例
$title = ['訂單號', '商品', '金額', '下單時間'];
$data = [
['202401001', '筆記本電腦', '5999.00', '2024-01-15 10:30'],
['202401002', '無線滑鼠', '199.00', '2024-01-15 11:00'],
];
$excelContent = generateExcelContent($title, $data);
$success = sendExcelEmail('user@example.com', '訂單導出報表', '請查收附件', $excelContent, 'orders_202401.xls');
echo $success ? '發送成功' : '發送失敗';
function generateExcelToFile(array $title, array $data, string $dir = '/tmp'): string
{
$filename = $dir . '/excel_' . date('Ymd_His') . '_' . uniqid() . '.xls';
$fp = fopen($filename, 'w');
// 寫入 BOM 防止 Excel 打開亂碼
fwrite($fp, "\xEF\xBB\xBF");
if ($title) {
fputcsv($fp, $title, "\t");
}
foreach ($data as $row) {
fputcsv($fp, $row, "\t");
}
fclose($fp);
return $filename;
}
// 發送時
$filePath = generateExcelToFile($title, $data);
$mail->addAttachment($filePath, 'orders.xls');
// 發送後清理
@unlink($filePath);
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
function generateRealXlsx(array $title, array $data): string
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// 標題行樣式
$sheet->fromArray($title, null, 'A1');
$sheet->getStyle('A1:' . $sheet->getHighestColumn() . '1')
->getFont()->setBold(true);
// 數據
$sheet->fromArray($data, null, 'A2');
// 自動欄寬
foreach (range('A', $sheet->getHighestColumn()) as $col) {
$sheet->getColumnDimension($col)->setAutoSize(true);
}
// 寫入臨時文件並返回路徑
$filename = sys_get_temp_dir() . '/export_' . time() . '.xlsx';
$writer = new Xlsx($spreadsheet);
$writer->save($filename);
return $filename;
}
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| ob_get_clean() + addStringAttachment | 無磁盤 IO、記憶體中完成、速度快 | 大數據量會佔用大量記憶體 | < 5000 行、簡單導出 |
| 臨時文件 + addAttachment | 記憶體穩定、支援超大數據 | 需磁盤 IO、需手動清理 | > 5000 行、長時間運行腳本 |
| PhpSpreadsheet .xlsx | 真正 Excel 格式、支援樣式/公式/多工作表 | 依賴較重、效能較低 | 專業報表、需樣式控制 |
iconv('UTF-8', 'GB2312//IGNORE') 或直接生成 UTF-8 的 .xlsx。fputcsv 流式寫入文件,避免 memory_limit 耗盡。addStringAttachment 第四參數務必設為 application/vnd.ms-excel 或 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet。掌握輸出緩衡與 PHPMailer 附件 API,即可靈活實現「導出即發送」的自動化報表流程。