
PHP用ZipArchive打包时,空文件夹默认不被包含
这是最常被误以为“bug”的行为:调用addDirectory()或循环addFile()后解压发现空目录消失了。根本原因不是PHP漏了,而是ZIP规范本身不强制存储空目录——它只存文件和路径前缀,而ZipArchive默认不主动写入仅含/结尾的目录条目。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 如果目标是保留完整目录结构(比如部署脚本依赖空
logs/或cache/),必须手动添加空目录条目 - 用
addEmptyDir('path/to/dir/')显式创建,注意末尾斜杠不能省——addEmptyDir('logs')和addEmptyDir('logs/')在部分ZIP工具里解析结果不同 - 别依赖
addGlob()或addPattern()自动补空目录,它们只处理匹配到的文件,对纯空文件夹完全无感
为什么addEmptyDir()有时不生效
常见现象:调用addEmptyDir('uploads/')后解压仍看不到该文件夹,或者出现在错误层级。问题多出在路径拼接和ZIP内部路径规则上。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
-
addEmptyDir()传入的路径是ZIP包内的逻辑路径,不是磁盘绝对路径;若当前工作目录是/var/www,而你想打包/var/www/app/uploads/下的空目录,应传'app/uploads/'而非'/var/www/app/uploads/' - 确保路径以
/结尾,否则某些解压工具(如Windows资源管理器)可能忽略它 - 调用顺序有影响:
addEmptyDir()必须在close()前执行,且最好在所有addFile()之后统一补空目录,避免因路径覆盖导致意外截断
递归打包带空目录的目录树,安全写法
直接scandir() + 递归判断是否为目录再调用addEmptyDir()容易漏掉嵌套空目录,也容易把符号链接当真实目录处理。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
RecursiveIteratorIterator配合RecursiveDirectoryIterator遍历,能自然跳过.和..,且isDir()返回true时再判断是否为空(count(new FilesystemIterator($path)) === 0) - 构造ZIP内路径时,用
str_replace($base_path . '/', '', $real_path) . '/'确保相对路径干净,末尾补/ - 避免在循环中反复
open()/close(),全部操作应在单个ZipArchive实例中完成
$zip = new ZipArchive();
$zip->open('out.zip', ZipArchive::CREATE);
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iter as $file) {
$relPath = str_replace($src . '/', '', $file->getPathname()) . '/';
if ($file->isDir() && !is_file($file->getPathname())) {
$zip->addEmptyDir($relPath);
} elseif ($file->isFile()) {
$zip->addFile($file->getPathname(), $relPath);
}
}
$zip->close();
兼容性陷阱:老版本PHP和某些解压工具对空目录支持差
PHP 7.4之前addEmptyDir()存在路径截断bug;而Windows自带解压工具、部分Android ZIP应用,对不含任何文件的纯目录条目识别率低。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 生产环境若需强兼容,宁可往每个空目录里塞一个
.keep空文件,用file_put_contents($dir . '/.keep', '')再addFile(),比死磕addEmptyDir()更稳妥 - 测试环节必须用目标用户实际使用的解压工具验证,比如Mac的归档实用工具、Windows资源管理器、
unzip -l命令行输出,三者对同一ZIP包的目录显示可能不同 - 不要假设
ZipArchive::EMULATION能绕过这个问题——它只影响压缩算法,不改变目录元数据写入逻辑
空目录这件事,表面是API调用问题,实际是ZIP格式、PHP实现、终端解压工具三方妥协的结果。最容易被忽略的是:你以为加了addEmptyDir()就万事大吉,但没验证最终ZIP包里是否真有那个目录条目——用unzip -l archive.zip | grep 'trailing-slash-dir/'看一眼,比猜强得多。











