萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 實現圖片批量打包下載:純 PHP Zip 類 + 串流輸出無臨時檔殘留

無需系統 `zip` 指令、無需 `ZipArchive` 擴充,純 PHP 實現的 `zipfile` 類可將多張圖片打包為 ZIP 並直接串流輸出下載,避免磁碟 I/O 佔用與臨時檔清理麻煩。本文提供完整可用類別與實戰範例。

核心類:zipfile (純 PHP 實現 ZIP 格式)

<?php
class zipfile {
    private $datasec = [];      // 檔案資料區
    private $ctrl_dir = [];     // 中央目錄區
    private $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
    private $old_offset = 0;

    // Unix 時間轉 DOS 時間格式
    private function unix2_dostime($unixtime = 0) {
        $t = ($unixtime == 0) ? getdate() : getdate($unixtime);
        if ($t['year'] < 1980) { $t = array('year'=>1980,'mon'=>1,'mday'=>1,'hours'=>0,'minutes'=>0,'seconds'=>0); }
        return (($t['year']-1980)<<25) | ($t['mon']<<21) | ($t['mday']<<16) | ($t['hours']<<11) | ($t['minutes']<<5) | ($t['seconds']>>1);
    }

    // 新增檔案內容
    public function add_file($data, $name, $time = 0) {
        $name = str_replace('\\', '/', $name);
        $dtime = dechex($this->unix2_dostime($time));
        $hexdtime = "\x{$dtime[6]}{$dtime[7]}\x{$dtime[4]}{$dtime[5]}\x{$dtime[2]}{$dtime[3]}\x{$dtime[0]}{$dtime[1]}";
        eval('$hexdtime = "' . $hexdtime . '";'); // 產生二進位時間字串

        $fr  = "\x50\x4b\x03\x04";          // Local file header signature
        $fr .= "\x14\x00";                  // Version needed
        $fr .= "\x00\x00";                  // General purpose bit flag
        $fr .= "\x08\x00";                  // Compression method (deflate)
        $fr .= $hexdtime;                   // Mod time/date

        $unc_len = strlen($data);
        $crc = crc32($data);
        $zdata = substr(substr(gzcompress($data), 0, -4), 2); // 移除 zlib header(2bytes) + Adler32(4bytes)
        $c_len = strlen($zdata);

        $fr .= pack('V', $crc);
        $fr .= pack('V', $c_len);
        $fr .= pack('V', $unc_len);
        $fr .= pack('v', strlen($name));
        $fr .= pack('v', 0);                // Extra field length
        $fr .= $name;
        $fr .= $zdata;

        // Data descriptor (延續 local header)
        $fr .= pack('V', $crc);
        $fr .= pack('V', $c_len);
        $fr .= pack('V', $unc_len);

        $this->datasec[] = $fr;

        // Central directory record
        $cdrec  = "\x50\x4b\x01\x02";
        $cdrec .= "\x00\x00";               // Version made by
        $cdrec .= "\x14\x00";               // Version needed
        $cdrec .= "\x00\x00";               // General purpose
        $cdrec .= "\x08\x00";               // Compression
        $cdrec .= $hexdtime;
        $cdrec .= pack('V', $crc);
        $cdrec .= pack('V', $c_len);
        $cdrec .= pack('V', $unc_len);
        $cdrec .= pack('v', strlen($name));
        $cdrec .= pack('v', 0);             // Extra field
        $cdrec .= pack('v', 0);             // File comment
        $cdrec .= pack('v', 0);             // Disk number
        $cdrec .= pack('v', 0);             // Internal attr
        $cdrec .= pack('V', 32);            // External attr (archive)
        $cdrec .= pack('V', $this->old_offset);
        $this->old_offset += strlen($fr);
        $cdrec .= $name;

        $this->ctrl_dir[] = $cdrec;
    }

    // 遞迴新增目錄 (可選)
    public function add_path($path, $strip = 0) {
        $strip = $strip ?: strlen($path) + 1;
        $dh = opendir($path);
        while (($file = readdir($dh)) !== false) {
            if ($file == '.' || $file == '..') continue;
            $full = $path . '/' . $file;
            if (is_dir($full)) $this->add_path($full, $strip);
            else $this->add_file(file_get_contents($full), substr($full, $strip));
        }
        closedir($dh);
    }

    // 組裝完整 ZIP 二進位字串
    public function file() {
        $data = implode('', $this->datasec);
        $ctrldir = implode('', $this->ctrl_dir);
        return $data . $ctrldir . $this->eof_ctrl_dir
            . pack('v', count($this->ctrl_dir))   // Total entries (this disk)
            . pack('v', count($this->ctrl_dir))   // Total entries (total)
            . pack('V', strlen($ctrldir))         // Central dir size
            . pack('V', strlen($data))            // Local headers size
            . "\x00\x00";                         // Comment length
    }

    // 直接輸出下載 (無臨時檔)
    public function download($filename = 'download.zip') {
        $zipData = $this->file();
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Content-Length: ' . strlen($zipData));
        header('Content-Transfer-Encoding: binary');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        echo $zipData;
        exit;
    }
}

實戰使用範例

<?php
require_once 'zipfile.class.php';

$zip = new zipfile();

// 要打包的圖片陣列:['src' => 伺服器絕對路徑/URL, 'name' => 壓縮包內顯示名稱(可含子目錄)]
$images = [
    ['src' => '/var/www/images/photo1.jpg', 'name' => '相簿/photo1.jpg'],
    ['src' => '/var/www/images/photo2.jpg', 'name' => '相簿/photo2.jpg'],
    // 遠端圖片需先 file_get_contents 或 curl 取得二進位
    ['src' => 'https://cdn.example.com/banner.png', 'name' => 'banner.png'],
];

foreach ($images as $img) {
    $content = @file_get_contents($img['src']);
    if ($content !== false) {
        $zip->add_file($content, $img['name']);
    } else {
        error_log("圖片讀取失敗: {$img['src']}");
    }
}

// 直接串流輸出下載,不寫入磁碟
$zip->download('我的相簿_' . date('Ymd_His') . '.zip');

關鍵優勢

  • 零依賴:不需 `zip`、`ZipArchive`、`zlib` (除 `gzcompress` 外) 擴充,純 PHP 可跑。
  • 記憶體友善** (相對)**:雖將整包載入記憶體,但避免了磁碟臨時檔 I/O 與清理風險;超大打包 (>100MB) 建議改用 `ZipArchive` 流式寫入磁碟或分卷。
  • 檔名編碼** (注意)**:ZIP 標準早期不支援 UTF-8 檔名,中文檔名在 Windows 內建解壓縮可能亂碼。現代解決:
    1. 檔名用 `iconv('UTF-8', 'CP950', $name)` (Big5/CP950) 相容 Windows。
    2. 或啟用 ZIP Unicode 標誌 (Bit 11 of General Purpose Flag) — 需修改類別 Header 邏輯。
  • 大檔案分塊輸出** (進階)**:將 `file()` 改為 Generator 逐段 `yield`,配合 `ob_flush()`/`flush()` 實現真正串流,不佔用峰值記憶體。

替代方案對比

方案依賴記憶體中文檔名適用場景
純 PHP zipfile 類無全量載入需手動轉碼中小打包、無擴充權限環境
ZipArchive 擴充php-zip流式寫入磁碟原生支援 UTF-8 (PHP 7.2+)生產環境標準選擇
系統指令 `zip -r`OS 安裝 zip極低原生支援超大打包、備份腳本
Box/SpoutComposer串流支援現代專案、大數據匯出

根據專案環境與打包規模選擇適當方案;純 PHP `zipfile` 類在「無擴充權限、中小檔案、快速原型」場景極具價值。

copyright © 萬盛學電腦網 all rights reserved