PHP標準のZipArchiveにてディレクトリを丸っとZIP圧縮・展開する方法



ディレクトリの圧縮は、Linuxコマンドが使える環境ではコマンド一発で圧縮出来ますが、
Windowsサーバーや、開発環境をWindowsXAMPPをのせて構築している場合などでは、
Linuxコマンドが使えないため、1コマンドで圧縮というわけには行きません。
そこで、今回はWindowsでも動作させるために、
PHP5.2から導入された標準クラス「ZipArchive」を使ってディレクトリを丸っと圧縮したいと思います。
汎用的に使い回せる様にメソッド化していますので、コピペで使用出来ますよ!。

  1. 一筋縄では行かないPHPでのディレクトリ圧縮
  2. ZipArchiveのストリームを開く
  3. ディレクトリのZIP圧縮
  4. ZIP圧縮ファイルを展開する



一筋縄では行かないPHPでのディレクトリ圧縮




「ZipArchive」にてディレクトリを丸っと圧縮することは、実は一工夫必要です。
なぜなら、「ZipArchive」は圧縮用のストリームを作成し、その中にディレクトリやファイルを
追加し、ストリームを閉じることによって圧縮が完了する形をとります。
ですので、ディレクトリ内のファイルを解析して、ストリームへ再起的に追加していく仕組みが必要です。
ストリーム内での新しいディレクトリの作成等も自分で行わなければならないので、
毎回対象に合わせて作っていては大変です。

そこで、指定ディレクトリ内の一覧を配列として返却してくれるメソッドを
まずは下記のように用意しました。

public static function get_inner_path_of_directory( $dir_path )
{
 $file_array = array();
 if( is_dir( $dir_path ) ){
  if( $dh = opendir( $dir_path ) ){
   while( ( $file = readdir( $dh ) ) !== false ){
    if( $file == "." || $file == ".." ){
     continue;
    }
    $file_array[] = $file;
   }
   closedir( $dh );
  }
 }
 sort( $file_array );
 return $file_array;
}

引数として、指定ディレクトリまでのパスを受け取り、ディレクトリ内を探りながら配列に入れています。
最後に、順序を整える為にソート処理を実行しています。
このメソッドで取得した情報を元に、zipストリームへ内容を追加して行きます。
ディレクトリ内は、更にディレクトリが存在する可能性があり、再起的にチェックする必要がありますので、
この様にメソッド化し、どの階層のディレクトリでも呼び出せる様にしています。





ZipArchiveのストリームを開く




それでは、ZipArchiveのストリームを実際に開き、ファイルを追加して行きましょう。
ZipArchiveのストリームを開くには、ZipArchiveのインスタンスメソッド「open()」にて開き、
追加が終了したら『close()』にてストリームを保存し、閉じます。
「open()」メソッドは、第一引数にzipを作成する場所までのパスを指定します。
ストリームを開いた時点でzip圧縮ファイルが指定パスへ作られ、そこにファイルを追加して行くイメージです。
ですのでストリームを開く際には、新規で開くのか?既存のzipファイルを開くのか?などを指定することが出来ます。

そういった開き方を指定するには、「open()」メソッドに対して、
予め用意されている定数を第二引数として指定します。
主な定数は下記の通りです。

ZipArchive::CREATE 既にzipが存在していれば存在しているものを開き、無ければ新規で作成します。
ZipArchive::OVERWRITE 常に新規でストリームを作成し、既にzipが存在している場合は上書きします。
ZipArchive::EXCL 既にzipが存在している場合はエラーとします。
ZipArchive::CHECKCONS 一貫性チェックを行い、失敗時にはエラーとします。


開かれたストリームに対して、ファイルを追加するには
「addFile()」メソッドに対して、第一引数に追加したい対象ファイルのパスを指定します。
第二引数には、任意でストリーム内でのパスを指定します。

ストリーム内にて、新しいディレクトリを作成したい場合は「addEmptyDir()」メソッドにて
引数に作成するディレクトリのパスを指定します。

これらのメソッドを使用し、既に存在するディレクトリを丸っとzipとして圧縮してみましょう。





ディレクトリのZIP圧縮




まずはディレクトリを圧縮する為のエントリーポイントとなるメソッドを作成します。
今回は、ZIPとして圧縮させたいディレクトリまでのパスと、作成したZIPの配置場所までのパスを
引数としてとり、内部的に対象ディレクトリの解析、ストリーム内でのディレクトリ作成、
ファイルの追加を行います。

下記のエントリーポイントでは、ストリームを上書き指定で開き、別個に用意したファイル追加用の
メソッドを呼び出し、ストリームを閉じています。
zip化処理中に何か問題が発生した場合には、例外が送出されますので、
try-catchにて括っています。

public static function all_zip( $dir_path, $new_dir )
{
 $zip = new ZipArchive();
 if( $zip->open( $new_dir, ZipArchive::OVERWRITE ) === true ){
  self::add_zip( $zip, $dir_path, "" );
  $zip->close();
 }
 else{
  throw new Exception('It does not make a zip file');
 }
}


次に、別個に外だししたファイル追加処理、
上記では「self::add_zip( $zip, $dir_path, "" );」の部分にあたるメソッドは下記の通りです。


private static function add_zip( $zip, $dir_path, $new_dir )
{
 if( ! is_dir( $new_dir ) ){
  $zip->addEmptyDir( $new_dir );
 }

 foreach( self::get_inner_path_of_directory( $dir_path ) as $file ){
  if( is_dir( $dir_path . "/" . $file ) ){
   self::add_zip( $zip, $dir_path . "/" . $file, $new_dir . "/" . $file );
  }
  else{
   $zip->addFile( $dir_path . "/" . $file, $new_dir . "/" . $file );
  }
 }
}


第一引数には開かれたストリームのインスタンスを受け取り、
第二引数にはZIP対象となるディレクトリのパス
第三引数には、新しく追加するディレクトリのパスを指定します。

処理の内容としては、まず始めに新しく追加するディレクトリが存在していなければディレクトリを作成します。
そして、本記事の冒頭で作成したディレクトリ内を配列として取得する為のメソッドを活用し、
ディレクトリ内の一覧を順に取り出していきます。
取り出した内容がディレクトリであれば、再度自分自身を再起的に呼び出し、
新しいディレクトリまでのパスを第三引数に指定します。
ファイルの場合は、そのままストリームに対して「addFile()」メソッドにて追加します。

まず、ディレクトリの作成が必要かを判断し、その中にある内容をチェック、
ディレクトリで有れば再度ディレクトリを作成して中身をチェックする作業を再起的に繰り返し、
ファイルであれば、現在指しているパス上にファイルを追加して行く。
この作業を指定ディレクトリ内を掘り進めながら再起的に行っています。

これで、エントリーポイントとなる「all_zip( $dir_path, $new_dir )」メソッドを起動するだけで、
目的のディレクトリがZIP圧縮され、指定場所へ配置される様になります。

最後に、今回作成したメソッドを纏めて下記に記しますので、
コピペにて使用して頂ければ幸いです。(クラスに含まれるメソッドとしての使用を想定しています。)


//--------------------------------------------------------------------------
// ディレクトリZIP圧縮
//--------------------------------------------------------------------------
public static function all_zip( $dir_path, $new_dir )
{
 $zip = new ZipArchive();
 if( $zip->open( $new_dir, ZipArchive::OVERWRITE ) === true ){
  self::add_zip( $zip, $dir_path, "" );
  $zip->close();
 }
 else{
  throw new Exception('It does not make a zip file');
 }
}

//--------------------------------------------------------------------------
// 再起的にディレクトリかファイルを判断し、ストリームに追加する
//--------------------------------------------------------------------------
private static function add_zip( $zip, $dir_path, $new_dir )
{
 if( ! is_dir( $new_dir ) ){
  $zip->addEmptyDir( $new_dir );
 }

 foreach( self::get_inner_path_of_directory( $dir_path ) as $file ){
  if( is_dir( $dir_path . "/" . $file ) ){
   self::add_zip( $zip, $dir_path . "/" . $file, $new_dir . "/" . $file );
  }
  else{
   $zip->addFile( $dir_path . "/" . $file, $new_dir . "/" . $file );
  }
 }
}

//--------------------------------------------------------------------------
// ディレクトリ内の一覧を取得する
//--------------------------------------------------------------------------
public static function get_inner_path_of_directory( $dir_path )
{
 $file_array = array();
 if( is_dir( $dir_path ) ){
  if( $dh = opendir( $dir_path ) ){
   while( ( $file = readdir( $dh ) ) !== false ){
    if( $file == "." || $file == ".." ){
     continue;
    }
    $file_array[] = $file;
   }
   closedir( $dh );
  }
 }
 sort( $file_array );
 return $file_array;
}





ZIP圧縮ファイルを展開する




既に存在するzipファイルを展開する時も、ZipArchiveを使用すれば簡単に行うことが出来ます。
まずは圧縮の時と同じ様にストリームを開きます。
その際に、「open()」メソッドにはzipファイルまでのパスを指定します。
そして、開かれたストリームに対して「extractTo()」メソッドにて展開します。
引数には展開先のパスを指定します。
最後に「close()」メソッドにてストリームを閉じることを忘れない様にしましょう。

zipファイルを展開する為の例として、下記のようなメソッドを用意しました。
引数としてzipファイルまでのパスと、展開先のパスを指定します。
このままコピペでも使用出来るかと思いますので、
そのままご使用頂ければ幸いです。(クラスに含まれるメソッドとしての使用を想定しています。)

public static function unzip( $zip_path, $dir_path )
{
 $zip = new ZipArchive();
 if( $zip->open($zip_path) === true ){
  $zip->extractTo($dir_path);
  $zip->close();
 }
 else{
  throw new Exception('It does not open a zip file');
 }
}

4 Responses to PHP標準のZipArchiveにてディレクトリを丸っとZIP圧縮・展開する方法

  1. 助かりました。ありがとうございました。

    返信削除
  2. 恐れ入ります、function add_zip の以下の行は、
    >self::add_zip( $zip, $dir_path . "/" . $file, $new_dir . "/" . $file );

    正しくは↓のようです。
    >self::add_zip( $zip, $dir_path . "/" . $file, $new_dir . $file );

    返信削除
  3. 間違えました。
    正しくは↓のようです。
    >self::add_zip( $zip, $dir_path . "/" . $file, $file );

    返信削除
  4. ディレクトリルートのファイルが出力されないバグがあります。正しくは以下

    if ($new_dir === '') {
    $zip->addFile($dir_path . '/' . $file, $file);
    } else {
    $zip->addFile($dir_path . '/' . $file, $new_dir . '/' . $file);
    }

    返信削除

人気の投稿

Category

Algorithm (2) Android (8) ASP/aspx (1) Blogger (2) C/C++ (1) Chrome (5) CSS (9) Firefox (4) Fortran (1) Google (9) GoogleMap (2) HTML (12) IE (3) Information (4) iOS (2) iPhone/iPad/iPod (2) Java (6) JavaScript (16) jQuery (9) JSP (1) LifeRecipe (5) Linux (2) Macintosh (2) MapKit (4) Marketing (7) MySQL (3) NAMAZU (2) Objective-C (7) Other (7) Perl (1) PHP (9) Python (1) RSS/Atom (2) Ruby (1) Safari (2) SEO (11) Smarty (2) SQL (2) Tex (1) Three.js (1) Twitter (1) TwitterLog (313) UIKit (5) Unix (1) VBA/VBS (1) Windows (5) WordPress (3) Writing (5) XAMPP (1) XML (1) Yahoo (2) ZendFramework2 (14)