PHP でファイルダウンロード機能を実装する方法

はじめに

実務にて、管理画面で圧縮ファイルをダウンロードできる機能を実装する作業があり、それを解決した情報になります。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;

以上です。

おわりに

サンプルのソースコードを再利用する際は、要件定義やコーディング規約にお気を付けください。