ZipArchive是PHP处理ZIP的事实标准,但需手动递归遍历目录、用addFromString写入动态内容、extractTo前必须校验路径防遍历、中文名需UTF-8处理或编码转换,且须确认zip扩展已启用。

ZipArchive 是 PHP 处理 ZIP 的事实标准,但直接调用 addFile() 压目录、用 extractTo() 解上传包,大概率出空包、路径穿越或中文乱码——这些不是环境问题,是 API 使用方式错了。
压缩整个目录必须手动递归遍历
addFile() 不支持目录,传入目录路径会静默失败或报 ZipArchive::addFile(): File not found。
要真正压一个文件夹,得自己走子项:
- 用
RecursiveDirectoryIterator+RecursiveIteratorIterator扫描所有条目 - 对每个
SplFileInfo实例,先调isFile()过滤掉.、..、符号链接和目录 - 计算相对路径:用
substr($file->getPathname(), strlen($srcDir) + 1),否则所有文件都堆在 ZIP 根目录 - 显式跳过
.git、.env、.DS_Store等敏感或系统文件
addGlob() 可简化单层匹配(如 addGlob('*.php')),但不递归,也不能过滤隐藏文件。
动态内容别写磁盘再 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() 默认不校验路径,必须手动防护
extractTo() 原样还原 ZIP 内部路径,遇到 ../../etc/passwd 这类恶意路径就直接覆写系统关键位置——这是真实发生过的高危漏洞。
- 解压前必须对每个条目做路径净化:用
getNameIndex($i)获取名字,再realpath()+strpos()判断是否落在目标目录内 - 更稳妥的做法是不用
extractTo(),改用getFromIndex()读取内容,再file_put_contents()到白名单路径下 - 别依赖
mkdir($dir, 0755, true)自动建深层目录——它可能创建不该存在的父级(如/tmp/evil/../etc)
中文文件名乱码 ≠ 编码没设好,先确认 zip 扩展是否真启用
显示为?.txt 或全是方块,常被当成 UTF-8 问题,其实更可能是:
-
extension_loaded('zip')返回false:Ubuntu 需装php-zip包,macOS 需确认php_zip.dll已加载且extension_dir正确 - PHP < 8.0 时,ZIP 默认用 CP437 编码存文件名,不声明 UTF-8;PHP 8.0+ 默认开启 UTF-8 标志,但旧 ZIP 包仍可能乱码
- 即使扩展启用、PHP 版本够新,若 ZIP 包本身由 Windows 工具用 GBK 打包,
ZipArchive也无能为力——这时得靠iconv()或mb_convert_encoding()在getNameIndex()后手动转码
最易被忽略的一点:空 ZIP 归档在 close() 时会被自动删除(libzip 行为),所以调试时看到文件“消失”,未必是写失败,而是里面没加任何有效条目。



















