はじめに
実務にて、管理画面で圧縮ファイルをダウンロードできる機能を実装する作業があり、それを解決した情報になります。Web 開発の初心者、初学者の方にもわかりやすいように、ソースコードを編集してサンプルを公開しています、ご参考になれば幸いです。
検証環境
サンプル
- GitHub / ソースコード
- download.php:ファイルダウンロード処理
- uploads:ファイルダウンロード先のフォルダ
- test.zip:テスト用ファイル
※ URL パラメータは download.php?fn=test.zip でファイルをダウンロードできます。
解説
サンプルのソースコードを基に、ファイルダウンロード機能を実装する方法を説明します。
ファイルダウンロード処理
1. ファイルが読み込み可能か is_readable で確認します。読み込めない場合は die でスクリプトを終了しています。
if (!is_readable($file_path)) die('File read error.');
2. ファイルからメディアタイプを finfo::file で取得します。
取得ができない場合は application/force-download を設定しています。また、未知のメディアタイプを設定しても、ブラウザの仕様によってファイルが強制ダウンロードされるので、チェックが必要な場合には注意しましょう。
$media_type = (new finfo())->file($file_path, FILEINFO_MIME_TYPE) ?? 'application/octet-stream';
3. header でファイルダウンロードに必要な HTTP ヘッダー の設定をします。
3-1. Content-Type にリソースのメディアタイプを設定します。
header('Content-Type: ' . $media_type);
3-2. Content-Type で指定したタイプを強制的に使用させる為に X-Content-Type-Options を nosniff に設定します。
header('X-Content-Type-Options: nosniff');
3-3. リソースの大きさを Content-Length で設定します。ダウンロードファイルの大きさを filesize で取得します。
header('Content-Length: ' . filesize($file_path));
3-4. Content-Disposition にダウンロードを示す為に attachment を設定します。さらに、ファイル名を示す為に filename に basename でパスの最後の部分を設定します。
header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
3-5. トランザクション完了後、ネットワークを閉じる為に Connection に close を設定して keep-alive を無効にします。
header('Connection: close');
4. readfile の注意によれば Out of memory エラーが発生する可能性があるので、出力前に ob_get_level の出力バッファリング機構のネストレベルがゼロになるまで ob_end_clean を実行して出力用バッファをクリアします。
while (ob_get_level()) ob_end_clean();
readfile($file_path);
5. これ以上の出力は必要無いので exit でスクリプトを終了させます。
exit;
以上です。
おわりに
サンプルのソースコードを再利用する際は、要件定義やコーディング規約にお気を付けください。