最稳妥方式是使用ZipArchive::CREATE标志打开压缩包,配合realpath()规范路径、addFile()逐个添加并指定归档内路径,遍历用RecursiveIteratorIterator+RecursiveDirectoryIterator确保递归完整。

用 ZipArchive 批量添加文件最稳妥
PHP 自带的 ZipArchive 类是批量压缩文件的首选,不依赖外部命令,跨平台稳定。关键不是“新建 zip”,而是“逐个添加路径”,否则容易漏文件或路径错乱。
常见错误:直接把目录名传给 addGlob() 却没设 RecursiveDirectoryIterator,结果只压了空目录;或者用 exec('zip') 在 Windows 上根本跑不通。
- 务必先调用
open()检查返回值,ZIPARCHIVE::CREATE是安全模式,不会覆盖已有 zip - 遍历文件时用
realpath()规范路径,避免相对路径导致压缩包里出现../或空层级 - 添加文件用
addFile($absPath, $localName),第二个参数控制压缩包内路径——比如想把/var/log/app.log压成logs/app.log,就传'logs/app.log'
addGlob() 看似方便但限制多
addGlob() 能一行加多个文件,但只支持简单通配符(如 *.php),不递归子目录,也不处理排除逻辑。它内部用的是 glob(),而 glob() 在某些 PHP 版本或 open_basedir 限制下会静默失败。
实操建议:仅用于同级少量文件,例如打包当前目录所有配置文件:$zip->addGlob('*.ini', 0, ['flags' => GLOB_BRACE])。如果要包含 src/ 和 tests/ 下所有 .php,不如手写 RecursiveIteratorIterator 更可控。
立即学习“PHP免费学习笔记(深入)”;
-
addGlob()的第二个参数是 flag,不是选项数组;想忽略大小写得用GLOB_CASE,不是case_insensitive => true - 它不会自动跳过目录,如果通配符匹配到文件夹,
addGlob()会报 warning 并跳过——但你可能根本没开error_reporting,就发现压缩包里少了东西
大文件或大量小文件要注意内存和超时
ZipArchive 写入是流式,但整个压缩过程仍在内存中构建索引,1000 个以上小文件或单个 >50MB 的文件容易触发 memory_limit 或 max_execution_time。别指望 set_time_limit(0) 就万事大吉——CLI 和 Web SAPI 行为不同,Nginx 还有 fastcgi_read_timeout。
- 用
clearstatcache()避免反复 stat 同一文件拖慢遍历速度 - 对超大文件,改用
addFromString($name, file_get_contents($path))前先检查filesize(),超过阈值就分块读取再拼接(但通常不如换用exec('zip -r')配合escapeshellarg()) - Web 场景下,优先生成 zip 到临时目录,再用
readfile()输出 +unlink()清理,别让 ZipArchive 对象一直 hold 着句柄
Windows 下中文文件名乱码怎么办
ZipArchive 默认用 CP437 编码存文件名,Windows 系统解压时显示为乱码。这不是 PHP bug,是 ZIP 格式历史遗留问题。PHP 8.0+ 加了 setArchiveComment() 等接口,但文件名编码仍需手动处理。
- 最简方案:用
iconv('UTF-8', 'GB2312//IGNORE', $filename)转换文件名(仅限简体中文 Windows 用户) - 通用方案:升级到 PHP 8.1+,启用
ZipArchive::FL_ENC_UTF8标志,调用addFile($path, $filename)前确保$filename是 UTF-8 字符串,并确认解压软件支持 ZIP64 + UTF-8 文件名标志(7-Zip、Bandizip 可以,原生 Windows 解压器不行) - 绕过方案:统一用英文命名临时文件,压缩完再重命名 zip 包本身——文件内容不乱码,只是包内路径看着不直观
真正麻烦的是混合环境:PHP 在 Linux 生成的 UTF-8 文件名 zip,发给 Windows 用户双击解压就乱码,而他们又不用专业解压工具。这时候与其死磕编码,不如提前把文件名转拼音或时间戳。



















