在 PHP 中操作 ZIP 文件,一般流程如下:
$zip = zip_open("example.zip");
if (is_resource($zip)) {
while ($entry = zip_read($zip)) {
echo "Name: " . zip_entry_name($entry) . "\n";
if (zip_entry_open($zip, $entry, "r")) {
$contents = zip_entry_read($entry, 1024);
echo "Content: " . $contents . "\n";
zip_entry_close($entry);
}
}
zip_close($zip);
}
上述代码是 PHP 官方文档给出的典型用法。在很多场景下它运行良好,但只要稍加复杂,或者读取的内容超过某个大小,问题就来了。
读取不到内容:zip_entry_read() 返回空字符串。
部分文件读到一半就中断:只能读取到部分内容。
报错或者卡住:某些压缩包会导致 PHP 执行卡死。
读取顺序混乱:多个 entry 的内容交错在一起。
这些问题有一个共通点:只要改用其他库,如 ZipArchive,问题通常就会消失。这说明问题很可能不在 ZIP 文件本身,而在 zip_* 函数的用法或者实现机制上。
zip_read() 本质上是一个迭代器,它每次调用时会内部维护 ZIP 的读取指针。如果你在读取过程中(比如还没读取完一个条目)就调用下一个 zip_read(),会导致前一个 entry 的读取状态被破坏。
zip_entry_read() 并不保证一次读取完全部数据,特别是当你传入固定长度(如 1024)的时候,你需要循环读取直到返回空字符串为止,否则内容会丢失。
这也是本文的核心:zip_entry_read() 本质上依赖于 zip_read() 当前保持的 entry 状态,如果你在调用 zip_entry_read() 时再次调用了 zip_read()(即使在另一个逻辑分支中),前者的行为也会被打断。
不要在还未读取完当前 entry 内容时,进入下一次 zip_read(),否则读取指针将被提前移动,导致内容丢失或错乱。
while ($entry = zip_read($zip)) {
zip_entry_open($zip, $entry, "r");
$content = "";
while ($data = zip_entry_read($entry, 1024)) {
$content .= $data;
}
echo $content;
zip_entry_close($entry);
}
某些 ZIP 文件可能由非标准工具压缩,可能包含不规范的结构。建议使用 ZipArchive 类配合 isReadable() 方法先检查完整性。
$zip = new ZipArchive();
if ($zip->open('example.zip') === TRUE) {
// 校验逻辑
$zip->close();
}
虽然 zip_open() 等函数简洁易用,但它们早期就被标记为实验性(甚至不推荐使用),而 ZipArchive 类则更强大、稳定,能更好地应对复杂 ZIP 文件。
$zip = new ZipArchive();
if ($zip->open('example.zip') === TRUE) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$entryName = $zip->getNameIndex($i);
echo "File: " . $entryName . "\n";
$stream = $zip->getStream($entryName);
if ($stream) {
while (!feof($stream)) {
echo fread($stream, 1024);
}
fclose($stream);
}
}
$zip->close();
}
这个方式不仅效率更高,也不会出现 zip_entry_read() 的读取中断问题,更适合生产环境使用。
zip_read() 和 zip_entry_read() 之所以在一起使用时问题频发,根源在于它们对内部指针状态依赖非常强,一旦处理顺序、读取节奏稍有不慎,就容易出现逻辑错乱。建议对 ZIP 操作有较高稳定性要求的项目,优先使用 ZipArchive 代替这组底层函数。
如果你必须使用 zip_* 函数族,请始终保证读取顺序严谨,完整读取每个 entry 的内容,并避免在未关闭 entry 的情况下进入下一轮读取。