導入
Web開発の実務において、ユーザーがアップロードしたファイル群をバックアップしたり、特定のデータセットをまとめてダウンロードさせたりする機能は頻繁に求められます。PHPでZIPファイルを扱う際、標準の ZipArchive クラスは非常に強力ですが、単一ファイルの追加だけでなく「ディレクトリ構造を維持したまま再帰的に圧縮する」実装には、少し工夫が必要です。今回は、実務でも即戦力となるディレクトリ圧縮の仕組みを解説します。
基礎知識
ZipArchive クラスは、PHP 5.2以降で利用可能なZIPファイルを操作するための標準クラスです。
ZIP圧縮において重要なのは「パスの管理」です。サーバー上の絶対パスをそのままZIP内に含めてしまうと、解凍時に予期せぬ階層構造になってしまうことがあります。そのため、RecursiveIteratorIterator と RecursiveDirectoryIterator を組み合わせることで、ディレクトリ構造を効率的に走査し、相対パスを計算しながら追加する手法が一般的です。
実装/解決策
再帰的な圧縮を実現するには、以下の手順を踏みます。
1. RecursiveIteratorIterator でディレクトリを全探索する。
2. 走査中のパスから、圧縮対象のベースディレクトリを除去し、相対パスを算出する。
3. ファイルなら addFile()、ディレクトリなら addEmptyDir() を使用してZIPに追加する。
サンプルプログラム
以下のコードは、指定したディレクトリをZIPファイルとして保存する実用的な関数です。
/
function zipDirectory(string $sourceDir, string $outZipPath): bool {
$zip = new ZipArchive();
// ZIPファイルを新規作成(既に存在する場合は上書き)
if ($zip->open($outZipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
return false;
}
// ディレクトリのパスを正規化
$sourceDir = realpath($sourceDir);
$baseLen = strlen($sourceDir);
// 再帰的にディレクトリをイテレート
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($files as $file) {
$filePath = $file->getRealPath();
// ZIP内の相対パスを作成
$relativePath = substr($filePath, $baseLen + 1);
if ($file->isDir()) {
$zip->addEmptyDir($relativePath);
} else {
$zip->addFile($filePath, $relativePath);
}
}
return $zip->close();
}
// 使用例
zipDirectory(__DIR__ . ‘/data’, ‘./backup.zip’);
?>
応用・注意点
1. メモリ制限に注意: 大規模なディレクトリを圧縮する場合、PHPのメモリ制限(memory_limit)に抵触することがあります。その際は、プロセスを分けたり、実行時間を延長する設定を検討してください。
2. パスの重複と安全性: ユーザー入力をそのままパスとして利用すると、ディレクトリトラバーサル攻撃を受ける危険性があります。必ず realpath() やパスのバリデーションを行い、対象が意図したディレクトリ内にあるか確認してください。
3. 文字コード: Windows環境などで作成されたZIPファイルを扱う場合、ファイル名のエンコーディング(UTF-8とShift-JISなど)で文字化けが発生することがあります。必要に応じてファイル名を変換する処理を挟むのが定石です。
このように、ZipArchive を適切に抽象化して関数化しておくことで、プロジェクト全体でのコードの再利用性が高まり、堅牢なバックアップ・ダウンロード機能が構築可能になります。