最实用导出方案是 phpspreadsheet + fputcsv 组合,能覆盖95%场景;需重点解决中文显示、大表内存、并发稳定三关,避免框架封装层带来的冗余和兼容问题。

直接用 PhpSpreadsheet + fputcsv 组合最实用,不堆功能、不造轮子,能覆盖 95% 的导出场景。选库不是看文档多炫,而是看它能不能扛住中文、大表、并发这三关。
为什么不用框架自带的 Excel 扩展
ThinkPHP/Laravel 自带的 Excel 封装层(如 maatwebsite/excel)本质是 PhpSpreadsheet 的壳,但加了太多中间层:自动类型推断、集合转换、队列钩子……这些在简单导出里全是负担。一旦出问题,你得同时查框架文档、扩展文档、PhpSpreadsheet 源码三层。更关键的是,它们默认启用单元格缓存,5000 行就可能触发 Allowed memory size exhausted。
- 遇到
Class 'PHPExcel' not found或Call to undefined method ...::save(),八成是混用了旧版示例或 autoload 失败 -
maatwebsite/excel的fromArray()会把所有数值转成字符串,日期变文本,数字失去 Excel 原生格式能力 - 导出接口若依赖框架的请求生命周期(比如自动注入 Request 对象),在 CLI 或队列中运行时容易报错
CSV 导出必须绕开的三个坑
很多人以为 fputcsv 是“最简单”的方案,结果上线后用户反馈 Excel 打开全是乱码或列错位——问题不在函数本身,而在上下文控制。
- 响应头漏
charset=utf-8:浏览器按 ISO-8859-1 解析,中文全变方块;必须写成header('Content-Type: text/csv; charset=utf-8') - 没写 BOM 头:Windows Excel 默认不识别 UTF-8,需在
fopen('php://output', 'w')后立刻fprintf($fp, "\xEF\xBB\xBF") - 字段含换行符或双引号:
fputcsv默认用"包裹字段,但若原始数据里已有",必须提前用str_replace('"', '""', $val)转义,否则 CSV 结构崩坏
Excel 导出时字体和日期怎么不丢
中文显示为 #VALUE! 或方块,90% 是因为只设了单元格字体,没动全局默认样式;日期导出后变成一串数字,是因为没告诉 PhpSpreadsheet 这是时间类型。
- 全局字体必须设在
$spreadsheet->getDefaultStyle()上:$spreadsheet->getDefaultStyle()->getFont()->setName('SimSun')(Linux 需确认系统已安装该字体) - 日期字段别用
$date->format('Y-m-d')写入,改用$sheet->setCellValueExplicit('A1', $timestamp, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC)+$sheet->getStyle('A1')->getNumberFormat()->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_YYYYMMDD2) - 数值列(如金额)也建议用
setCellValueExplicit(..., TYPE_NUMERIC),避免被当成字符串参与 Excel 公式计算时出错
大数据量导出不崩内存的关键动作
超过 1 万行就别想靠 fromArray() 一把梭;重点不是“怎么快”,而是“怎么不卡死”。核心是切断“全量加载 → 全量写入”链路。
- 数据库查询必须用游标分页或
while ($row = $stmt->fetch())迭代,禁用fetchAll() - 每写入 1000 行,调一次
gc_collect_cycles(),强制 PHP 回收引用计数归零的对象 - 禁用公式计算:
$spreadsheet->getActiveSheet()->setAutoFilter('A1:Z10000')改为显式关闭($spreadsheet->getActiveSheet()->setAutoFilter(null)) - 真到 10 万行以上,直接放弃 Excel,返回
Content-Type: text/csv并在前端提示“超大数据请下载 CSV 格式”
最易被忽略的是:导出前的数据查询必须完成,不能边查边写进 Spreadsheet —— PhpSpreadsheet 写入过程本身就会触发大量对象创建,此时再叠加数据库 fetch,内存增长是非线性的。宁可多一次全量查询,也不要省那点连接开销。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











