在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 的情況下進入下一輪讀取。