Fatal Error: Unexpected BLOG

主に自分用の備忘録として

【PHP】ZipArchiveで作成したZIPファイルが解凍できない

いやー、ハマったハマった。

別処理でサーバ上に作成されるいくつかのCSVファイルを、ZIP圧縮してダウンロードさせるフォームを作りまして。 ダウンロードまでは問題なく出来たのですが、肝心のZIPファイルが解凍できない。
Lhaplusで解凍しようとすると、「エラーがあります」の文言のみで沈黙・・・。
ハマった。

以下のサイトを参考にさせて頂きました。

うまくいかないコード

    $zip = new ZipArchive();
    $res = $zip->open('path/to/zipfile.zip', ZipArchive::CREATE);
    if ($res) {
        $zip->addFile('absolute/path/to/file.ext');
    }

ZIPファイルはこんな感じで作成される。で、

    $name = 'absolute/path/to/zipfile.zip';
    $base = basename($name);
    $size = filesize($name);
    header('Content-Type: application/octet-stream');
    header("Content-Length: {$size}");
    header("Content-Disposition: attachment; filename={$base}");
    readfile($name);

↑こんな感じでダウンロードさせる。

で、ダウンロード後に解凍しようとするとエラーが出て解凍できない。

改修 - 第一段

まずダウンロード時のzlibに関する実行時設定が怪しいとのこと。

    if (ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off');

データ送出時にページ圧縮を行うかの設定らしいが、上記のコードをダウンロードの直前に挿入しても解決せず。

ZIPファイルをFTPクライアントでダウンロードすると普通に解凍できたので、やはりダウンロード周りで何かあるかと思っていたけど、解凍してできたフォルダを見たらこうなってた↓

    \absolute\path\to\file.ext

どうやらZipArchive::addFile()の第二引数(ZIPファイル内でのファイル名)を省略したのがいけなかったよう。

公式マニュアルの"User Contributed Notes"にも言及があり、サーバ上でのアブソルートなツリー構造をZIP内に引き継がない場合は、第二引数は指定した方が良いよとのこと。

ので、addFile()の部分を以下のように変更。

    $zip->addFile('absolute/path/to/file.ext');
    ↓
    $zip->addFile('absolute/path/to/file.ext', 'file.ext');

第二引数のパスはZIPアーカイブ内部での相対パスでいいみたい。

でもまだだめ。解凍しようとするとエラー。

改修 - 第二段

お次は、文字コード関連。
HTTP出力時に文字エンコードを変更するmb_http_output()を使用して、文字コードを「変換させない」という処理を明示的に挟む。こちらもheader出力の直前。

    mb_http_output("pass");

第一引数は本来文字コードを指定する引数ですが、"pass"を指定すると「変換しない」を指定できる。
※公式の"User Contributed Notes"では「php will not touch the encoding」と表現されています。

一縷の希望と共にダブルクリックするも、まだエラー。この時点で結構泣きそう。

改修 - 第三段

で、関連する記事を検索していたら、同じようにPHPで作成・ダウンロードしたZIPファイルがエラーを起こして解凍できないという人で、「ZIPファイルをバイナリエディタで開いたらPHPがエラーを吐いていた」というのを発見。

早速試してみると案の定、見覚えのあるPHPエラーがそこに。

蓋を開けてみれば何のことは無い、ZIP作成に使う自前クラスのコンストラクタに渡すべき引数を渡してなった・・・。

おしまい。

教訓

PHPが吐くエラーが解決への近道。