ZipArchive是PHP操作ZIP首选,但需手动遍历目录压缩、用addFromString()写入动态内容、extractTo()前必须校验路径防遍历、中文名需UTF-8处理或编码转换,且须确认zip扩展已启用。

PHP 要压缩或解压 ZIP 文件,ZipArchive 类是首选,但直接调用 addFile() 压目录、用 extractTo() 解压上传包,稍不注意就会出空包、路径穿越、中文乱码或权限失败——这些不是配置问题,而是 API 使用惯性导致的逻辑漏洞。
压缩整个目录时 addFile() 不递归,必须手动遍历
addFile() 只接受真实文件路径,传入目录会静默失败或报 ZipArchive::addFile(): File not found。要压目录,得自己走一遍子项:
- 用
RecursiveDirectoryIterator+RecursiveIteratorIterator扫描所有子文件 - 对每个
SplFileInfo实例,先用isFile()过滤掉.、..、符号链接等非文件项 - 计算相对路径:用
substr($file->getPathname(), strlen($srcDir) + 1),否则所有文件都挤在 ZIP 根目录下 - 显式跳过系统隐藏文件(如
.git、.env),避免意外泄露
动态内容别写磁盘再 addFile(),优先用 addFromString()
想把 json_encode($data)、模板渲染结果或 CSV 字符串直接打进 ZIP?别先 file_put_contents() 写临时文件再 addFile() —— 多一次 IO、多一个权限点、多一处清理逻辑。
-
addFromString('config.json', json_encode($data, JSON_UNESCAPED_UNICODE))是唯一干净方式 - 内部文件名不能以
/开头,否则部分解压工具(如 macOS 归档实用工具)会忽略该条目 - 若内容是二进制(如 GD 生成的 PNG),确保传入的是原始字节流,不是 base64 编码后的字符串
extractTo() 默认不做路径校验,用户上传 ZIP 可能覆写系统文件
extractTo() 会原样还原 ZIP 内部路径,遇到 ../../etc/passwd 这类恶意路径就直接写到系统关键位置——这是真实发生过的高危漏洞。
- 必须在解压前做路径净化:对每个
getNameIndex($i)结果,用realpath()+strpos()校验是否落在目标目录内 - 更稳妥的做法是用
getFromIndex()读取内容,再手动file_put_contents()到白名单路径下 - 不要依赖
mkdir(..., true)自动建深层目录——它可能创建不该存在的父级(如/tmp/evil/../etc)
中文文件名乱码和扩展缺失是两个独立问题
中文名显示为 .txt,常被误以为是编码问题,其实根源可能是:
- ZIP 包本身没声明 UTF-8(旧版
ZipArchive默认用 CP437),需升级 PHP ≥ 8.0 或打补丁 - 更常见的是扩展根本没启用:运行
extension_loaded('zip'),返回false就得去装php-zip包(Ubuntu)或重编 PHP(macOS) - 如果必须兼容低版本 PHP,只能用
iconv('UTF-8', 'GBK', $filename)转换后再传给addFile(),但解压端也得对应转回
真正难处理的从来不是“怎么调 API”,而是目录遍历时漏掉符号链接、解压时没拦住 ../、或者以为 addFile() 能自动处理相对路径——这些细节一旦漏掉,线上就出故障。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











