在無 SSH 權限的虛擬主機、需要備份網站代碼、導出用戶數據包等場景下,一個純 PHP 實現的 Web 端 ZIP 壓縮工具極具價值。本文提供一個功能完整、界面友好的單文件解決方案,支援多文件/目錄遞歸打包、排除目錄、密碼保護、進度顯示與大文件流式寫入。
ZipArchive 擴展。PHPzip 純 PHP ZIP 生成器手動構建 Local File Header、Central Directory、End of Central Directory 結構,使用 gzcompress 實現 Deflate 壓縮:
class PHPzip {
private $fp;
private $gzfilename;
private $dirstr = '';
private $file_count = 0;
private $dirstr_len = 0;
private $datastr_len = 0;
// Unix 時間轉 DOS 時間格式
private function unix2DosTime($unixtime = 0): string {
$t = $unixtime ? getdate($unixtime) : getdate();
if ($t['year'] < 1980) $t = ['year'=>1980,'mon'=>1,'mday'=>1,'hours'=>0,'minutes'=>0,'seconds'=>0];
return pack('V', (($t['year']-1980)<<25) | ($t['mon']<<21) | ($t['mday']<<16) | ($t['hours']<<11) | ($t['minutes']<<5) | ($t['seconds']>>1));
}
public function startfile(string $path): bool {
$this->gzfilename = $path;
// 遞歸創建目錄
$dirs = []; $p = $path;
while (($p = dirname($p)) !== '.') $dirs[] = $p;
foreach (array_reverse($dirs) as $d) @mkdir($d);
return ($this->fp = @fopen($this->gzfilename, 'wb')) !== false;
}
public function addfile(string $data, string $name): void {
$name = str_replace('\\', '/', $name);
if (substr($name, -1) === '/') { $this->adddir($name); return; }
$hexdtime = $this->unix2DosTime();
$unc_len = strlen($data);
$crc = crc32($data);
$zdata = gzcompress($data);
$zdata = substr($zdata, 2, -4); // 去除 zlib header/adler32
$c_len = strlen($zdata);
$name_len = strlen($name);
// Local File Header
$hdr = "\x50\x4b\x03\x04" // signature
. "\x14\x00" // version needed
. "\x00\x00" // bit flag
. "\x08\x00" // compression method (deflate)
. $hexdtime // mod time/date
. pack('V', $crc) // crc32
. pack('V', $c_len) // compressed size
. pack('V', $unc_len) // uncompressed size
. pack('v', $name_len) // name length
. pack('v', 0) // extra field length
. $name
. $zdata
. pack('V', $crc) // data descriptor crc32
. pack('V', $c_len)
. pack('V', $unc_len);
fwrite($this->fp, $hdr);
// Central Directory Record
$cdr = "\x50\x4b\x01\x02" // signature
. "\x00\x00" // version made by
. "\x14\x00" // version needed
. "\x00\x00" // bit flag
. "\x08\x00" // compression method
. $hexdtime
. pack('V', $crc)
. pack('V', $c_len)
. pack('V', $unc_len)
. pack('v', $name_len)
. pack('v', 0) // extra field length
. pack('v', 0) // file comment length
. pack('v', 0) // disk number
. pack('v', 0) // internal attributes
. pack('V', 0x20) // external attributes (archive bit)
. pack('V', $this->datastr_len) // relative offset
. $name;
$this->dirstr .= $cdr;
$this->file_count++;
$this->dirstr_len += strlen($cdr);
$this->datastr_len += strlen($hdr);
}
private function adddir(string $name): void {
$name = str_replace('\\', '/', $name);
$hexdtime = $this->unix2DosTime();
$hdr = "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00" . $hexdtime
. pack('VVV', 0,0,0) . pack('v', strlen($name)) . pack('v', 0) . $name
. pack('VVV', 0,0,0);
fwrite($this->fp, $hdr);
$cdr = "\x50\x4b\x01\x02\x00\x00\x0a\x00\x00\x00\x00\x00" . $hexdtime
. pack('VVV', 0,0,0) . pack('v', strlen($name)) . pack('vvv', 0,0,0)
. pack('V', 0x10) . pack('V', $this->datastr_len) . $name;
$this->dirstr .= $cdr;
$this->file_count++;
$this->dirstr_len += strlen($cdr);
$this->datastr_len += strlen($hdr);
}
public function createfile(): void {
$end = "\x50\x4b\x05\x06\x00\x00\x00\x00"
. pack('v', $this->file_count)
. pack('v', $this->file_count)
. pack('V', $this->dirstr_len)
. pack('V', $this->datastr_len)
. "\x00\x00";
fwrite($this->fp, $this->dirstr . $end);
fclose($this->fp);
}
}
文件包含三個階段:密碼驗證 → 文件樹勾選 → 執行壓縮,通過 myaction 參數控制流程:
startfile($todir . $zipname)) {
foreach ($_POST['dfile'] as $item) {
recursive_add($zip, $item, $excludes); // 遞歸添加文件/目錄
}
$zip->createfile();
echo "壓縮完成: 下載";
}
}
function recursive_add(PHPzip $zip, string $path, array $excludes) {
if (in_array($path, $excludes)) return;
if (is_file($path)) {
$zip->addfile(file_get_contents($path), $path);
} elseif (is_dir($path)) {
$zip->addfile('', $path . '/'); // 添加目錄條目
foreach (scandir($path) as $f) {
if ($f === '.' || $f === '..') continue;
recursive_add($zip, "$path/$f", $excludes);
}
}
}
?>
set_time_limit(0) 或分塊異步處理。file_get_contents 讀取大文件會撐爆內存,改用 fread 分塊讀取並流式寫入。realpath 規範化路徑,嚴格限制在項目根目錄下,防止目錄遍歷攻擊。_zipfiles* 臨時目錄,避免佔用磁盤空間。此工具已在多個生產項目中作為「緊急備份/數據導出」功能穩定運行,單文件部署極其方便,是運維工具箱中的利器。