PHP历史数据归档首选ZipArchive类打包目录并用zlib流式压缩单文件,需注意路径处理、编码兼容、内存管理、压缩级别设置、安全校验及时间戳保留。

PHP 实现历史数据归档压缩,首选 ZipArchive 类打包目录 + zlib 压缩单文件流,不依赖外部命令、可控性强、适合 Web 环境长期运行。
用 ZipArchive 递归压缩整个数据目录
归档历史数据(如日志、导出报表、用户上传附件)最常见需求是把整个目录结构打成 ZIP 包。直接用 RecursiveDirectoryIterator 遍历,配合 addFile() 添加时保留相对路径,是最稳妥的做法。
-
ZipArchive::CREATE | ZipArchive::OVERWRITE必须同时指定,否则旧包残留或打开失败 - 务必检查
$file->isDir(),跳过目录项,只添加文件;否则addFile()会静默失败 - 中文路径名需统一转为 UTF-8,并在
addFile()第二个参数中显式传入(PHP 7.4+ 默认支持,但低版本可能乱码) - 大目录建议在循环中调用
gc_collect_cycles(),防止内存持续增长
压缩级别与性能取舍:ZipArchive::setCompressionName()
默认 addFile() 不启用压缩(仅存储),实际体积没减少。要真正压缩,得对每个文件单独设置算法和级别:
-
$zip->setCompressionName($relativePath, ZipArchive::CM_DEFLATE, 9)是关键调用,必须在addFile()之后、close()之前执行 - 级别 1–9,9 是最高压缩比,但 CPU 时间翻倍;归档场景建议用 6,平衡速度与体积
- 如果目标文件已是压缩格式(如
.jpg、.png、.gz),设为ZipArchive::CM_STORE更省资源
避免 ZIP 炸弹和路径穿越:解压前必须校验
归档文件若来自用户上传或第三方导入,extractTo() 直接调用等于开放任意文件写入权限。真实归档系统必须做两层防护:
立即学习“PHP免费学习笔记(深入)”;
- 遍历条目时用
realpath($entry)和strpos($realpath, $targetDir) !== 0检查是否越界(防止../etc/passwd) - 拒绝含控制字符、空字节、超长路径(如 >255 字节)的文件名,
preg_match('/[\x00-\x08\x0E-\x1F]/', $name) - 限制总文件数(如
$zip->numFiles > 1000)和单文件大小($zip->statIndex($i)['size'] > 100 * 1024 * 1024)
小文件高频归档:改用 gzencode() 流式压缩更轻量
如果归档对象是单个文本类文件(如 CSV、JSON 日志片段),反复创建 ZIP 开销过大。此时用 gzencode() 直接生成 .gz 文件更合适:
-
gzencode($content, 6, ZLIB_ENCODING_GZIP)输出标准 RFC 1952 格式,可被gunzip或浏览器直接识别 - 注意不要对已压缩内容(如 JPEG 二进制)再套一层
gzencode(),反而增大体积 - 写入磁盘时用
file_put_contents($path . '.gz', $compressed),别漏掉.gz后缀,否则后续解压工具无法识别
归档逻辑里最容易被忽略的是时间戳一致性——ZipArchive 默认使用当前时间作为文件修改时间,导致解压后所有文件时间相同。若需保留原始 mtime,得用 stat() 提前读取并调用 setMtimeName(),这点在审计或增量同步场景中很关键。



















