PHPによるファイルアップロードフォームの作り方

# こんな人向け
PHPを勉強中の初心者の方には
「POSTやGET等のメソッドを使ってフォームのデータを送信したり取得したりする方法は分かったけど、じゃあ画像のアップロードやダウンロードはどうしたらいいわけ？」
と思っている方もいるはず。
今日はそんな迷える子羊のためにPHPフォーム上でのファイルアップロード・ダウンロードを行う方法をご紹介します。

**【2023年1月6日追記】**
**偉そうに「紹介します」とか言ってますが、**
**コメント頂いた通り、セキュリティ面の配慮を完全に失念していました。**
**修正予定ですので、そのままの使用はお控えください。**

**【2023年3月1日追記】**
**引っ越しシーズンだの何だのでしばらく更新できそうにないので手短に。
アップロードファイルを保存するときはファイル名を頑張って暗号化してブラウザ上にリスト表示する時は頑張って復号化してください。以上です。**


# 実装する機能一覧
・index.phpのフォームからファイル選択ボタンを押すとクライアントローカルのファイルを選択できる
・「アップロード」ボタンを押すと、サーバー上のフォルダへファイルをアップロード。result.phpへ遷移し、結果を表示。
・index.phpにはアップロード済みのファイルの一覧が表示される。
・表示済みのファイル名をクリックすると、ファイルがダウンロードされる。**



# 設計
構成を確認しておきましょう。
root
　∟ index.php
　∟ result.php
　∟ download.php
　∟ files
　　∟image.jpg

index.php : トップページ。ファイルのアップローダーと格納済みファイルの一覧を表示。
result.php : index.phpにて「アップロード」ボタンを押下した際のリダイレクト先。アップロードを行う。
download.php : index.phpの格納済みファイル一覧のファイル名押下時のリダイレクト先。ファイルのダウンロードを行う。
files : ファイルの格納先フォルダ
(te2jiさんのコメントにもある通り、こちらのディレクトリには何かしらのアクセス制限をかけることが推奨されます)

# 実装方法
**【2023年1月7日追記】**
**コメントでいただいた修正で知ったのですが、**
**substr($fpath, 8)とかいうバカみたいなことしなくても**
**basename($fpath)という便利な関数でファイルパスからファイル名を取得することが可能です。**
```index.php
<?php
$files = glob('./files/*.jpg'); //格納先フォルダ内のファイルの相対パスを配列で取得　ここではjpgのみ取得
?>


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ファイルアップローダー</title>
  </head>
  <body>
    <!-- 「enctype="multipart/form-data"」を指定することでPOSTデータにファイルを含めることができる -->
    <form method="post" action="result.php" enctype="multipart/form-data">
      <div>
        <input type="file" name="upload_file">
        <input type="submit" value="アップロード">
      </div>
    </form>
    <ul>
    <?php foreach ($files as $key => $value): ?>
      <!-- ファイル名だけを切り取り表示。download.phpへ渡すパラメータも設定。-->
<!--！！！コメントにてtadsanさんよりご指摘があったように、エスケープを失念していました。加えて修正案をご記載いただいたので、コメントをご確認ください！！！-->
      <!--<li><?php echo '<a href="./downlaod.php?file='.substr($value,8)'">'.htmlspecialchars(substr($value, 8)).'</a>'; ?></li>-->
    <?php endforeach; ?>
    </ul>
  </body>
</html>
```

```result.php
<?php

define("FILE_DIR", "./files/"); //ファイル格納先フォルダパス
//↓サーバー上のファイル一時保管先。「php.ini の upload_tmp_dir ディレクティブで 他の場所を指定しない限り、
//ファイルはサーバーにおけるデフォルトの テンポラリディレクトリに保存されます。」※PHP公式ドキュメントより抜粋
$tmpfile = $_FILES['upload_file']['tmp_name']; 
$filename = $_FILES['upload_file']['name'];    //アップロード元のファイル名
$result = "";

//ファイルのアップロード
if (is_uploaded_file($tmpfile)) {
  if (move_uploaded_file($tmpfile, FILE_DIR.$_FILES['upload_file']['name'])) {
    $result = "$filename" . "をアップロードしました。";
  } else {
    $result = "ファイルをアップロードできませんでした。";
  }
} else {
  $result = "ファイルが選択されていません";
}

?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>結果</title>
</head>
<body>
  <p><?php echo htmlspecialchars($result); ?></p>
  <button onclick="location.href='./index.php'">←戻る</button>
</body>
</html>
```

```download.php
<?php

//画像のパスとファイル名
$fpath = './files'.$_GET['filename'];
$fname = $_GET['filename'];
//画像のダウンロード
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($fpath));
header('Content-disposition: attachment; filename="'.$fname.'"');
readfile($fpath);

?>
```
php.iniもしくは.htaccessで最大アップロードサイズを指定することができます。
```.htaccess
php_value post_max_size       200M
php_value upload_max_filesize 200M
php_value memory_limit        256M
```