[PHP] 複数ファイルをZipにしてダウンロートする2つの方法

PHP

複数のファイルをまとめて Zip に圧縮しダウンロードする。
1 つのフォルダに複数のファイルがあって、フォルダごと Zip にしてダウンロードする方法と、複数のフォルダにまたがった複数のファイルを Zip にしてダウンロードする方法。

方法 1 : フォルダごと Zip にしてダウンロード

例えば、 ClassA というフォルダに 3 つのファイルがあってこの 3 つのファイルを一括ダウンロードする場合、 フォルダ ClassA を 丸ごと Zip にする。

フォルダ構成
|-firstGrade
    |-ClassA
        |-サンジ.txt
        |-ゾロ.txt
        |-ルフィ.txt

PHP コード

// 丸ごと圧縮するフォルダ
$folder = "ClassA";
// Zip ファイル名
$fileName = $folder.".zip";
// ファイルディレクトリ
$dir = __DIR__."/firstGrade";
// Zip ファイルパス
$zipPath = $dir. '/' .$fileName;
$command =  "cd ". $dir .";".
	    "zip -r '". $fileName . "' ./".$folder."/";
exec($command);
mb_http_output( "pass" ) ;
header("Content-Type: application/zip");
header("Content-Transfer-Encoding: Binary");
header("Content-Length: ".filesize($zipPath));
header('Content-Disposition: attachment; filename*=UTF-8\'\'' . $fileName);
ob_end_clean();
readfile($zipPath);
// Zipファイル削除
unlink( $zipPath );
exit;

これを実行すると、サーバー内の firstGrade フォルダの中に ClassA.zip が作成され、それをダウンロードする。最後に unlink で作成された Zip ファイルは削除される。

コマンドの説明
cd : ディレクトリの移動
zip : ファイルを圧縮 , zip ZIPファイル名 対象フォルダ ( -r オプションで再帰的にしないとでディレクトリだけが格納される)
exec : Linux コマンドを実行

ダウンロード部分の説明
mb_http_output( "pass" ); のとこ
出力バッファの文字エンコーディングを変換する関数 mb_output_handler() を無効にする。
mb_output_handler() が呼び出されるとダウンロードファイルが壊れることがある。

header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($fileName));
IE・Edge での日本語のファイル名文字化け対策

ob_end_clean();
readfile($zipPath);

出力バッファにゴミが残っているとダウンロードしたファイルが壊れることがあるので、 ob_end_clean() を使って、出力バッファを破棄してから、ファイルの中身を出力させる。


Content-Typeapplication/force-download は MIME タイプの(正式な)値ではない。
ブラウザは認識できない Content-Type が指定されるとダウンロード処理になる(必ずではない)という動きを利用しているだけで、別に「 abc 」とかでもいい。

SSL 環境になっていて、かつ IE8 以下の場合に、うまくいかないときは以下をつける。
header('Cache-Control: public');
header('Pragma: public');

方法 2 : ZipArchive クラスを使ってダウンロード

チェックボックスとかで選択されたファイルを圧縮してダウンロードしたい場合、またはフォルダを跨いでいてフォルダごと Zip にできないときは、 ZipArchive クラスを使う。

ZipArchive は、 PHP に Zip の拡張機能が入っていないと使えない。
ターミナルで、 $ php -m コマンドを実行し「 zip 」がなければインストールする。

フォルダ構成
|-firstGrade
    |-ClassA
        |-サンジ.txt
        |-ゾロ.txt
        |-ルフィ.txt
    |-ClassB
        |-ウソップ.txt
        |-ナミ.txt
    |-ClassC
        |-チョッパー.txt
        |-ロビン.txt

チェックボックスでファイルを選択する画面
form とかボタンは書いていない
<div class="box">
<?php
  $dir = __DIR__."/firstGrade";
  if (file_exists($dir)) {
    $iterator = new RecursiveDirectoryIterator($dir);
    $iterator = new RecursiveIteratorIterator($iterator);
    $i=1;
    foreach ($iterator as $key=>$fileinfo) {
      if ($fileinfo->isFile()) {?>
        <label><input type="checkbox" name="check[]" value="<?=$key?>"/>
        <?=str_replace($dir."/","",$key)?></label><br>
<?php   $i++;
      }
    }
  } ?>
</div>
filedownload checkbox
filedownload checkbox
PHP
$chk = filter_input(INPUT_POST, 'check', FILTER_DEFAULT,FILTER_REQUIRE_ARRAY);
if(!empty($chk)) {
  // Zip ファイル名
  $fileName = "firstGrade.zip";
  // ファイルディレクトリ
  $dir = __DIR__."/firstGrade";
  // Zip ファイルパス
  $zipPath = $dir."/".$fileName;
  // インスタンス作成
  $zip = new ZipArchive();
  // Zip ファイルをオープン
  $res = $zip->open($zipPath, ZipArchive::CREATE);
  // Zip ファイルのオープンに成功した場合
  if ($res === true) {
    foreach( $chk as $value ){
      $newname = str_replace($dir."/","",$value);
      // 圧縮するファイルを追加
      $zip->addFile($value, $newname);
    }
    // Zip ファイルをクローズ
    $zip->close();
    mb_http_output( "pass" );
    header("Content-Type: application/zip");
    header("Content-Transfer-Encoding: Binary");
    header("Content-Length: ".filesize($zipPath));
    header('Content-Disposition: attachment; filename*=UTF-8\'\'' . $fileName);
    ob_end_clean();
    readfile($zipPath);
    // zipを削除
    unlink($zipPath);
  }
}

これを実行すると、 firstGrade.zip の中は、選択した「フォルダ/ファイル」という構成になる。
ClassA/ルフィ.txt と ClassA/ゾロ.txt を選択してダウンロードすると、 firstGrade フォルダ – ClassA フォルダ - ルフィ.txt と ゾロ.txt が格納される。

ZipArchive の説明
$zip->open($zipPath, ZipArchive::CREATE) のとこ
open(パスを含めた Zip ファイル名 , オプション) : Zip ファイルをオープンする。
オプション ZipArchive::CREATE は、第一引数に指定した Zip ファイルを新規作成する。
ZipArchive::OVERWRITE は Zip ファイルを上書きする。 ( PHP5.6 以上だと、第一引数に指定した Zip ファイルが存在しない場合、エラーとなる。)

オプションは繋げて書くことができる。
$zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)


$zip->addFile($value, $newname) のとこ
addFile(追加するファイルのパス+ファイル名 , Zip の中でのファイル名) : Zip にファイルを追加する。
第二引数は省略可。ただし省略すると、第一引数の「追加するファイルのパス」にある階層がそのまま Zip の中にも作られる。

このサンプルの場合、チェックボックスで取得するファイル名には、 __DIR__ も入っているため、 addFile で第二引数を省略すると、ルートディレクトリから反映され階層が深くなる。

Zip にしたとき、 firstGrade 以下のディレクトリとファイルが欲しいので、 str_replace() で不要部分を取り除いている。 ClassA とかのディレクトリも不要で、ファイルだけでいいなら、第二引数にはファイル名のみ指定する。

Posted by Agopeanuts