ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PHPWord解压即用:不装Composer也能在服务器生成Word文档

PHPWord解压即用:不装Composer也能在服务器生成Word文档 简介面向需要在服务器端生成、编辑或读取文档的 PHP 开发者这份代码包集合了 PHPWord 类库的完整源码与可直接运行的示例工程无需通过 Composer 安装依赖解压后即可在 PHP 运行环境中直接调用特别适合希望减少部署步骤、快速验证文档处理功能的开发场景。压缩包整体约一百四十九兆字节内部以 PHP 脚本文件、示例程序、配置文件、许可证文本和说明文档为主同时包含自述文件、更新日志、依赖声明、代码风格设定等工程化内容目录层次清晰方便按模块学习与二次扩展。目前已有超过一千人浏览学习覆盖合同文书生成、报表导出、批量文档处理等典型应用场景。除了现成的样例还附带了项目贡献指南与许可证信息能够帮助使用者理解开源协议、代码规范及扩展方式而完整保留的配置文件和依赖锁定记录也为需要利用 Composer 做进一步集成的项目提供了衔接便利。整体开箱即用适合需要快速集成或系统学习 PHP 文档处理方案的开发人员。 很多做PHP开发的朋友遇到“帮我把这些数据生成Word”的需求第一反应就是去搜怎么在服务器上装Office或者直接上Composer拉一个库下来。但这两条路在一些老项目、虚拟主机环境里都走不通没有命令行权限连不上外网甚至PHP扩展都不全。我最早做这个功能时也折腾过好一阵后来找到一套“解压即用”的PHPWord方案——把完整代码和依赖一起打包放到任何一台有PHP环境的机器上就能跑不装Composer、不装Office、不做复杂配置。这篇文章就把这套方案完整拆开从环境准备、核心Sample代码到线上常见的坑一次性讲清楚。如果你需要在服务器上批量生成合同、周报、成绩单、订单明细这类Word文档或者你在维护一个没法执行Composer命令的旧项目这篇内容基本能让你少走两三天弯路。1. 为什么你需要一个“解压即用”的PHPWord包1.1 传统集成方式的门槛比想象中高PHPWord是PHP环境下生成Word文档的事实标准库核心价值在于它输出的是标准OOXML格式的docx文件不需要服务器安装任何Office组件。功能覆盖段落、表格、图片、页眉页脚、模板替换、样式控制等基本你能想到的Word操作它都有对应API。但问题是官方文档推荐的标准安装方式是Composercomposer require phpoffice/phpword这一行命令看起来简单实际在真实项目里会撞上几堵墙目标服务器没有SSH登录权限只给了FTP文件管理。服务器在内网或隔离区Composer源根本访问不了。某些虚拟主机禁用了exec、shell_exec等函数Composer安装依赖时走了PHP进程也会失败。运维不允许在服务器上执行任何写操作之外的命令。我接手过一个老项目服务器还是PHP 5.6跑着一个十年前的内容管理系统根本不敢动环境。当时的需求是每天凌晨生成一批PDF和Word报表发给客户。在这种场景下Composer那条路就是死路必须换思路。1.2 “无需安装”的实现原理Vendor目录整包搬运其实PHP的依赖和编译型语言的依赖不一样PHPWord本质就是一堆PHP类文件没有任何编译产物。Composer做的事情只是把依赖从仓库拉下来再加一个自动加载文件。既然本质是文件搬运那完全可以在本地或任意一台能联网的机器上把依赖下好然后连同代码一起打包上传到目标服务器。这就是标题里“无需安装可直接运行”的真正含义。具体做法分三步在自己的电脑或一台有Composer的机器上执行cd /tmp composer require phpoffice/phpword:0.18.3把生成的vendor目录完整打包连同业务PHP文件一起上传服务器。业务代码里统一写require_once __DIR__ . /vendor/autoload.php;直接跑。只要PHP环境本身没问题这套代码放哪都能跑跟Composer有没有、外网通不通完全无关。1.3 运行环境检查清单网上很多教程默认“环境没问题”但实际部署时环境检查才是第一个拦路虎。我整理了一份清单部署前对着看一遍检查项要求说明PHP版本0.18.x要求PHP 5.3.31.x要求PHP 7.1老服务器建议用0.18.3兼容性最好php-zip扩展必须开启docx本质是zip包没有ZipArchive直接白屏报错php-xml扩展必须开启包含SimpleXML、XMLWriterPHPWord底层依赖它们内存限制建议128M以上大文档256M几千行表格的文档很吃内存执行时间建议不限制或至少300秒批量生成时默认30秒必挂Office软件不需要PHPWord生成的是标准docx任何Word/WPS都能打开其中php-zip扩展是最容易遗漏的。有些虚拟主机后台默认没开需要在php.ini里加上extensionzip.soLinux或extensionphp_zip.dllWindows或者直接在虚拟主机管理面板里勾选。检查方法很简单跑一个php -m | grep zip就能看到。2. 完整Sample代码从“你好Word”到带样式的周报2.1 目录结构规划先给一个可以直接抄的目录结构/wordgen ├── vendor/ # PHPWord依赖整包拷入 │ ├── autoload.php │ └── ... ├── sample.php # 完整示例代码 ├── chart.png # 示例图片可选 ├── template.docx # 模板示例可选 └── output/ # 生成结果目录需可写权限vendor目录比较大几千个文件很正常打包成zip上传时不要解压到一半就中断建议上传后先跑一次示例确认完整性。2.2 完整可运行代码下面这段代码是我实际用过的一个“项目周报生成器”精简版覆盖了PHPWord里最常碰到的功能点。代码里每一段都加了注释直接复制保存为sample.php就能跑?php /** * PHPWord 完整示例生成一份项目周报.docx * 无需安装只要 vendor 目录与本文件放在一起即可运行 */ require_once __DIR__ . /vendor/autoload.php; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\SimpleType\Jc; set_time_limit(0); ini_set(memory_limit, 256M); // 1. 创建文档对象设置默认字体 // 默认字体会影响到后面所有没有单独指定字体的文本块 $phpWord new PhpWord(); $phpWord-setDefaultFontName(微软雅黑); $phpWord-setDefaultFontSize(10.5); // 2. 添加一个横向A4页面页边距都显式指定 // orientation 和 pageSizeW/pageSizeH 必须配套否则Word里方向不生效 $section $phpWord-addSection([ orientation landscape, pageSizeW Converter::cmToTwip(29.7), pageSizeH Converter::cmToTwip(21), marginTop Converter::cmToTwip(1.5), marginBottom Converter::cmToTwip(1.5), marginLeft Converter::cmToTwip(1.8), marginRight Converter::cmToTwip(1.8), ]); // 3. 页眉页脚 $header $section-addHeader(); $header-addText(项目周报 - 内部资料, [size 9, color 999999]); $footer $section-addFooter(); $footer-addText(第 {PAGE} 页 / 共 {NUMPAGES} 页, [size 9, color 999999]); // 4. 预定义标题样式一级标题居中二级标题左对齐 // addTitleStyle 必须先于 addTitle 调用否则标题样式不会生效 $phpWord-addTitleStyle(1, [size 22, bold true, color 2E74B5], [alignment Jc::CENTER, spaceAfter 240]); $phpWord-addTitleStyle(2, [size 16, bold true, color 2E74B5], [spaceBefore 240, spaceAfter 120]); $section-addTitle(2024年第8周项目进度周报, 1); $section-addText( 汇报人张三 日期2024-02-23 部门研发部, [size 10, color 666666], [alignment Jc::CENTER, spaceAfter 120] ); $section-addTitle(一、本周完成, 2); $section-addText( 本周完成了支付模块的重构核心接口响应时间从 350ms 降低到 120ms同时修复了 3 个线上问题。具体工作项如下, [size 11] ); // 5. 列表Word原生列表格式 $bullets [ 订单列表接口增加分页与Redis缓存, 支付回调增加幂等校验避免重复入账, 商家后台新增对账报表导出功能, ]; foreach ($bullets as $item) { $section-addListItem($item, 0, [size 11]); } // 6. 表格带表头背景色、固定列宽 $table $section-addTable([ borderSize 6, borderColor 999999, cellMargin 80, ]); // 表头 $table-addRow(400); $headers [模块, 负责人, 进度, 备注]; $widths [3000, 2000, 1500, 2500]; // 单位是twip1厘米≈567twip foreach ($headers as $i $h) { $cell $table-addCell($widths[$i]); $cell-getStyle()-setShading([fill 2E74B5]); $cell-addText($h, [bold true, color FFFFFF, size 11], [alignment Jc::CENTER]); } // 数据行 $rows [ [支付模块, 张三, 100%, 已上线], [退款模块, 李四, 80%, 联调中], [对账报表, 王五, 60%, 预计下周提测], ]; foreach ($rows as $row) { $table-addRow(350); foreach ($row as $i $val) { $table-addCell($widths[$i])-addText($val, [size 10.5], [alignment $i 2 ? Jc::CENTER : Jc::LEFT]); } } // 合并单元格示例跨4列 $table-addRow(350); $mergeCell $table-addCell(array_sum($widths)); $mergeCell-getStyle()-setGridSpan(4); $mergeCell-addText(整体进度健康注意退款模块的联调风险。, [size 10, italic true]); // 7. 插入图片判断文件是否存在避免报错 $imagePath __DIR__ . /chart.png; if (file_exists($imagePath)) { $section-addText(附本周接口性能趋势图, [bold true, size 11], [spaceBefore 240]); $section-addImage($imagePath, [ width 420, height 220, alignment Jc::CENTER, ]); } else { $section-addText(chart.png 不存在跳过插图示例, [size 10, color 999999]); } // 8. 分页后再写一段 $section-addPageBreak(); $section-addTitle(二、下周计划, 2); $section-addText(继续推进退款模块联调完成对账报表的导出优化。, [size 11]); // 9. 保存为 docx $outputDir __DIR__ . /output; if (!is_dir($outputDir)) { mkdir($outputDir, 0755, true); } $writer IOFactory::createWriter($phpWord, Word2007); $writer-save($outputDir . /项目周报.docx); echo 生成完成 . $outputDir . /项目周报.docx . PHP_EOL;2.3 核心API背后的几个关键点这段代码运行起来不难但有几个细节我头一次用的时候踩过专门讲一下第一addTitleStyle必须先于addTitle调用。PHPWord的样式系统是“先定义后引用”如果你先写了addTitle再用addTitleStyle去定义前面的标题不会应用这个样式。这个和CSS的“后定义覆盖先定义”完全不同新手很容易在这里懵。第二页面横向设置必须同时给orientation、pageSizeW、pageSizeH三个参数。只写orientation landscape生成的文档在Word里经常出现“方向变了但纸张宽高没换”的情况打印时溢出。我上面用Converter::cmToTwip来做单位换算是为了避免手写一长串twip数字容易错。第三表格宽度单位是twip不是像素也不是厘米。一个A4纵向页面减去左右页边距后可用宽度约9026twip。横向页面约15940twip。表格各列宽度之和尽量不要超过这个可用宽度否则Word打开时列宽会被强制压缩样式和你预期差很多。.addCell()传的宽度就是控制列宽的关键而addRow(400)里的400是行高的最小值单位同样是twip想让行高随内容撑开填-1或者不填这个参数都行。第四页脚里的{PAGE}和{NUMPAGES}是PHPWord预置的占位符。你不需要手动去插入域代码按照这个写法保存后Word会自动把占位符转成页码域。3. 在实际环境里跑起来常见坑与排查方法代码写好了但在不同服务器上跑总会有几个“为什么我这台就不行”的问题。我把线上遇见的几次典型情况整理了一下基本都是这个方案里最容易出问题的点。3.1 报错“Class ZipArchive not found”这个错误是最常见的原因很直接PHP的zip扩展没启。docx文件的本质是用zip算法打包的XML文件集合PHPWord写入文件时靠的就是ZipArchive类。处理方式看你的运行环境如果是自己管理的服务器改php.ini找到extensionzip去掉注释或者直接加一行extensionzip.so重启php-fpm或Apache。如果是虚拟主机去管理面板找“PHP扩展设置”把zip勾上。如果面板里没有这个选项给服务商提工单让客服开一下。大部分虚拟主机都支持只是藏在设置里不太好找。判断是否开启最快的方式是在项目根目录放一个探针文件?php phpinfo();然后浏览器打开搜“zip”看到ZipArchive support enabled就没有问题。确认后记得把探针文件删掉避免暴露服务器信息。3.2 生成成功但打开是乱码docx本身是UTF-8编码所以乱码问题十有八九出在数据源上。最常见的是老系统数据库用的GBK编码取出来传入PHPWord之前没有转码。我处理过一个历史订单导出功能数据从MySQL一个utf8_general_ci的库来没问题但另一张历史表是latin1导出来全是“锟斤拷”。后来统一在写入前加了一道转换$text mb_convert_encoding($text, UTF-8, auto);如果你知道数据源确切编码第二个参数传GBK、GB2312或BIG5比auto更稳因为auto在遇到短文本时可能猜错。还有另一种“伪乱码”你设置了name 微软雅黑但打开文档的电脑上没有安装这个字体Word会自己挑一个替代字体显示效果和你本地看不一样。这个不是代码问题是字体缺失问题。如果要求所有打开的人都看到统一效果可以考虑把字体嵌进文档但PHPWord对这个支持一般实际项目里更常见的做法是统一用“宋体”或“微软雅黑”这类Windows系统普遍预置的字体。3.3 页面设置横向不生效很多人以为在addSection里写了orientation landscape就是横向了结果生成出来还是纵向。原因就是我前面说的PHPWord对于页面宽高不会因为方向自动交换你必须在提供orientation的同时把pageSizeW和pageSizeH一起传进去并且这两个值应该是横向后的实际宽高。也就是说pageSizeW要比pageSizeH大比如A4横向下是29.7cm x 21cm反过来就是纵向。3.4 Fatal error: Allowed memory size exhausted生成几千行表格、插入大量图片的文档时内存很容易爆。PHP默认内存限制通常是128M看起来不小但PHPWord在内存里维护整个文档对象树再加上字符串拼接和zip压缩大文档吃个200M很轻松。我处理这个问题有三个手段按优先级来在脚本开头加ini_set(memory_limit, 256M);覆盖系统默认值。加set_time_limit(0);防止长时间生成被php执行时间策略掐断。改代码结构不要一次性把所有数据加载到数组里再写入文档改成数据库查询一条写一条边读边写内存峰值会明显下降。3.5 保存时报“failed to open stream: Permission denied”这个不是PHPWord的问题是输出目录没写权限。很多虚拟主机网站根目录之外是不能写的或者output目录权限是755只有文件所有者能写。把输出目录权限设为755或777并且确认当前PHP进程用户有这个目录的写权限。如果不确定直接用一个最简单的测试file_put_contents(__DIR__ . /output/test.txt, ok);能生成test.txt说明权限没问题再把PHPWord的保存路径指过去即可。4. 把Sample改造成你自己的导出功能4.1 模板替换合同、通知书类场景的利器如果你的需求是“套用固定模板、替换几个变量”每次去用代码重新排版会很蠢。PHPWord自带TemplateProcessor类专门处理这种场景。先用Word做好一个template.docx在需要替换的地方写上${name}这样的占位符然后在代码里require_once __DIR__ . /vendor/autoload.php; use PhpOffice\PhpWord\TemplateProcessor; $templateProcessor new TemplateProcessor(__DIR__ . /template.docx); $templateProcessor-setValue(name, 张三); $templateProcessor-setValue(order_no, SO20240223001); $templateProcessor-setValue(amount, 1280.00); $templateProcessor-saveAs(__DIR__ . /output/合同_张三.docx);这套方案我在“批量生成合同”的需求里用过模板里可以有表格、有图片、有页眉页脚只要占位符写对替换完全不用改样式。有一个坑模板中的占位符如果在Word里被分成了多个run比如你打了几个字又改过几次字体setValue会匹配不上。解决方法是占位符写好后不要再去局部调它的格式要么单独全选占位符设置一套统一格式要么用setValue的第二个参数传一个包含样式信息的数组。TemplateProcessor还支持cloneRow和setValue配合做成循环块来填充动态行表格适合发票明细、工资条这类行数不确定的模板。4.2 批量生成一条Foreach搞定但注意三个细节批量生成应用最多的场景是工资条、录取通知书、成绩单。把模板替换放进循环就可以$list [ [name 张三, salary 6500, id 001], [name 李四, salary 7200, id 002], ]; foreach ($list as $row) { $templateProcessor new TemplateProcessor(__DIR__ . /template.docx); $templateProcessor-setValue(name, $row[name]); $templateProcessor-setValue(salary, $row[salary]); $templateProcessor-saveAs(__DIR__ . /output/ . $row[id] . _ . $row[name] . .docx); }这个场景需要注意三件事文件名尽量用唯一标识工号、订单号而不要只用姓名很多人会重名循环里每次都要重新new TemplateProcessor避免上一个文件的替换残留到下一个内存释放问题循环体末尾可以unset($templateProcessor);。4.3 让用户直接下载而非保存到服务器路径很多后台导出功能用户点一下按钮希望浏览器直接下载docx而不是先去服务器找文件。这时把保存路径改成php://output配合HTTP头就行$fileName 导出_ . date(YmdHis) . .docx; header(Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document); header(Content-Disposition: attachment; filename . $fileName . ); $writer IOFactory::createWriter($phpWord, Word2007); $writer-save(php://output); exit;注意一点save(php://output)之前脚本里不能有任何输出包括echo、空格、BOM头否则下载下来的docx文件会损坏打不开。用编辑器写PHP文件时也要确保文件是UTF-8无BOM格式。4.4 性能和安全的几个务实建议把PHPWord放到正式项目里有几个容易被忽视的点数据量大的时候内存是最大的敌人。用模板方式做工资条几千人的数据不要一次性从数据库查出来放内存里用yield生成器或者分批查询每一批处理完就写文件漏掉指定内存上限的执行风险。用户上传的模板不能直接信任。TemplateProcessor加载的docx如果内容是非法的XML会直接抛异常。如果允许用户上传模板一定要校验文件扩展名、MIME类型并且在上传后尝试加载一次加载失败就拒绝。不要让用户输入直接拼文件名。下载接口的filename参数如果可被用户控制要做好过滤避免写入路径穿越字符如../否则可能被利用覆盖服务器文件。生成结果目录定期清理。如果选择保存到服务器路径时间久了会积累大量docx文件占用磁盘空间写个定时任务把超过7天的文件删掉是运维层面最基础的省心事。这套“解压即用”的方案后来我给三个项目组部署过全部都是整个目录拷过去就跑通没有一次因为环境问题卡壳。唯一一次例外是对方服务器PHP版本停在5.2连命名空间都不支持那就真没办法了只能建议先升级PHP环境。所以拿到项目先看一眼php -v比什么都重要。如果你也是要在受限环境里批量出Word照着上面的sample把目录搭起来生成好的docx先在自己电脑上用Word和WPS各打开一次确认样式没问题再交出去基本就稳了。本文还有配套的精品资源点击获取
返回列表