14421
こんにちは。  
PHPでの質問になります。

ディレクトリをまるごとZipで圧縮してダウンロードさせる仕組みを作りたいです。

リンクをクリックすることでZipで圧縮されたフォルダをダウンロードさせいたいのですが、  
いくつかのサイトを参考にさせてもらったにも関わらず理解することが出来ませんでした。

現在は、中身がファイルのみであれば、Zipで圧縮してダウンロードさせることは可能です。

ディレクトリは以下のような構成のものを圧縮したいです。

test.zip  
|-images  
|-image1.jpg  
|-image2.jpg  
|-file1.php  
|-file2.php

複数ファイルの入ったフォルダと、ファイルが複数枚といった構成です。

ディレクトリを階層ごと圧縮するには再帰的な処理が必要とのことなのですが、  
いまいち理解ができていません。

プログラムの流れ的には、

1.圧縮するディレクトリのパスを指定  
2.保存する場所のパスを指定  
3.ディクトリ内の一覧を取得  
4.ファイルであればそのままストリームへ追加、フォルダであればフォルダを作成

といった感じでは無いかと想像はしています。

ですがどうやってコードを書けばよいのか、  
またそもそも考え方が正しいのかどうかも分かりません。

いくつかのサイトを参考に拝見させていただきましたが、理解することができませんでした。

参考までに、自分の使っている  
中身がファイルのみでのZipで圧縮するプログラムを載せておきます。

下記のコードでは、  
・ファイル名を直接コードに書かないといけない  
・フォルダを指定することが出来ない

といった問題点があります。

ご回答の方よろしくお願いします。

```

    <?php
    //Zipクラスロード
    $zip = new ZipArchive();
    
    //Zipファイル名指定
    $zipFileName = 'test.zip';
    
    //Zipファイル一時保存ディレクトリ取得
    $zipTmpDir = '/phpdocs/filelesson';
    
    //Zipファイルオープン
    $result = $zip->open($zipTmpDir.$zipFileName, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
    
    if ($result !== true) {
        return false;
    }
    
    //処理制限時間を外す
    set_time_limit(0);
    
    //パス
    $fpath_array = array('images/asn.jpg','images/asn2.jpg','images/shirika.jpg');
    
    //Zip追加処理
    foreach ($fpath_array as $filepath) {
        //filename取得
        $filename = basename($filepath);
    
        //取得ファイルをZipに追加
        $zip->addFromString($filename,file_get_contents($filepath));
    }
    
    $zip->close();
    
    // ストリームに出力
    header('Content-Type: application/zip; name="' . $zipFileName . '"');
    header('Content-Disposition: attachment; filename="' . $zipFileName . '"');
    header('Content-Length: '.filesize($zipTmpDir.$zipFileName));
    echo file_get_contents($zipTmpDir.$zipFileName);
    
    // 一時ファイルを削除しておく
    unlink($zipTmpDir.$zipFileName);
    exit();
    
```
CC BY-SA 3.0
2015-08-14T06:42:45.293
14416
2015-08-14T10:40:47.417
2015-08-14T07:24:03.110
10917
10917
question
php
複数ファイルや、フォルダも含むディレクトリをまるごとZipで圧縮して、ダウンロードさせたい
あなたが知らなければいけない関数やメソッドは、

●scandir()  
●is_dir() または is_file()  
●ZipArchive::addEmptyDir()  
●ZipArchive::addFile()

です。

それらを再帰的に処理すれば、そのままのディレクトリ構造を保てますよ。

※ユーザー関数が2つに別れてしまったので、クラスにしてあります。

```

    <?php
    namespace My;
    
    /**
     * ZipArchiveを拡張して、ディレクトリまるまる扱う機能を追加
     */
    class MyZipArchive extends \ZipArchive
    {
        /**
         * ディレクトリをまるごとzipファイルにします。
         *
         * @param string $dir              ディレクトリパス
         * @param string $inner_path       zipファイル中のディレクトリパス
         * @param bool   $create_empty_dir 空ディレクトリもディレクトリを作成するか
         * @return bool  処理の成否
         */
        public function addDir($dir, $inner_path, $create_empty_dir=false){
            $items = \array_diff(\scandir($dir), ['.','..']);
            $item_count = \count($items);
    
            if($create_empty_dir || $item_count > 0){
                $this->addEmptyDir($inner_path);
            }
    
            // 追加するものがないならここで終了する
            if($item_count === 0) return true;       
    
    
            foreach($items as $_item){ // forで行うなら$itemsは一旦array_values()を通したほうがいい
                $_path = $dir . DIRECTORY_SEPARATOR . $_item;
                $_item_inner_path = $inner_path . DIRECTORY_SEPARATOR . $_item;
    
                // ディレクトリの場合は再帰的に処理する
                if(\is_dir($_path)){
                    $_r = \call_user_func( // "$this->addDir"より保守的に好ましい
                        [$this, __FUNCTION__], $_path, $_item_inner_path);
                    if(!$_r) return false;
                }
                // ファイルの場合でかつ処理に失敗したときはこちら
                else if(!$this->addFile($_path, $_item_inner_path)
                      && !$this->on_recursive_error($dir, $inner_path, $create_empty_dir)){
                    return false;
                }
                // ファイルの追加成功時は何も他には行わない
            }
    
            return true;
        }
    
        /**
         * 再帰的処理のときにエラーが生じたらどうするかを定義しています。
         *
         * @param string $parent_dir              ディレクトリパス
         * @param string $parent_inner_path       zipファイル中のディレクトリパス
         * @param bool   $create_empty_dir        空ディレクトリもディレクトリを作成するか
         * @return bool Falseならその場で中断
         */
        private function on_recursive_error($parent_dir, $parent_inner_path, $create_empty_dir){
            // 自由に定義してください
            return false;        
        }
    }
    
    
    /*
     * 下記は、質問本文のコードをちょっと改造してのテスト
     */
    
    //Zipクラスロード
    $zip = new MyZipArchive();
    
    //Zipファイル名指定
    $zipFileName = 'test.zip';
    
    //Zipファイル一時保存ディレクトリ取得
    $zipTmpDir = '/phpdocs/filelesson';
    
    // Zipファイルのパス
    $zip_file = $zipTmpDir . DIRECTORY_SEPARATOR . $zipFileName;
    
    //Zipファイルオープン
    $result = $zip->open($zip_file, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE);
    
    if ($result !== true) {
        return false;
    }
    
    //処理制限時間を外す
    set_time_limit(0);
    
    // ※今回の肝
    $zip->addDir('/phpdocs/filelesson/images', 'images', true);
    
    // ※下記は今回処理しない
    while(false){
        //パス
        $fpath_array = array('images/asn.jpg','images/asn2.jpg','images/shirika.jpg');
    
        //Zip追加処理
        foreach ($fpath_array as $filepath) {
            //filename取得
            $filename = basename($filepath);
    
            //取得ファイルをZipに追加
            $zip->addFromString($filename,file_get_contents($filepath));
        }
    } // 処理しない範囲ここまで
    
    $zip->close();
    
    
    // ストリームに出力
    header('Content-Type: application/zip; name="' . $zipFileName . '"');
    header('Content-Disposition: attachment; filename="' . $zipFileName . '"');
    header('Content-Length: '.filesize($zip_file));
    echo file_get_contents($zip_file);
    
    // 一時ファイルを削除しておく
    unlink($zip_file);
    exit();
    
```
CC BY-SA 3.0
2015-08-14T10:40:47.417
14421
2015-08-14T10:40:47.417
10919
14416
answer