PHPによる画像のアップロード（確認画面経由）

# はじめに
　私は某プログラミングスクールを経て、今年10月から未経験エンジニアとしてWEBコンサルティング会社に入社しました。これから時間がある時にアウトプットの場としてQiitaに記事を投稿していくので、気が付いたこと等アドバイスいただけると幸いです。
　本記事では、未経験として入社した会社で情報のやり取りを理解するために行った新人研修の内容のうち、フォームによる画像の送信＆保存の内容について少しつまずいたことをまとめています。
　この記事が少しでもプログラミング初学者の方々の一助となれば幸いです。

# 事前準備
①同じディレクトリ直下にPHPファイルとimgフォルダを作る
②imgフォルダ内にtmpフォルダとmainフォルダを作る
③サーバー上でtmpとmainの権限を第三者の読み書き可能に属性変更しておく

# 実装内容

・一つのPHPファイルのみで実装
・ページは入力画面→確認画面→送信完了画面の３ページ構成
・それぞれの画面ではform action=""で同ファイルに値をPOSTする
・確認画面に遷移する際に仮のディレクトリに画像ファイルを格納
・送信完了画面に遷移する際にメインのディレクトリにファイルを移す

< 補足 >
　本来であればMVCの概念に沿って、ファイル毎で役割を分担させるべきではあるが、HTMLも書き込めるというPHPの特性を生かし、今回は力試しということで一つのPHPファイルで実装を進めていくこととします。
　本記事内では省略していますが、if文やswitch文を使い都度表示するviewや動かすコントローラ部分を分けて実装する必要があります。

## 画像の選択

```php
// ①トップのフォーム画面でinput type="file"画像ファイルの選択箇所を作る
<input type="file" name="picture" accept="image/*">
```

①トップページのview部分の実装です。 form タグの action="" で同じファイルにPOSTするように実装します。ファイルを分けて実装する際は action 属性にPOST先のファイルのパスを指定してください。今回私は name 属性を "picture" としましたが、ここは任意の値で大丈夫です。

## コントローラー部分での確認画面の処理

```php
// ①現在時刻を取得
$now = date("YmdHis");

// ②変数errsを空の配列として事前に作る
$errs = array();

// ③$_FILESデータを変数pictureに入れる
foreach($_FILES['picture'] as $key => $var){
    $picture[$key] = $var;
}

// ④画像のバリデーション
$finfo = finfo_open(FILEINFO_MIME_TYPE);
list($mime,$ext) = explode("/",finfo_file($finfo, $picture["tmp_name"]));
if($mime!="image"){
    $errs['picture'] = "画像を選択してください";
    unset($picture);
}

// ⑤$_FILES内のファイルデータを名前を変えて仮のディレクトリ（tmp）に移す
move_uploaded_file($picture['tmp_name'], "img/tmp/".$now."_picture.".$ext);
// ⑥アウトプット用の変数に画像データを格納
$output_file['name'] = $picture['name'];
// ⑦アウトプット用の変数のtmp_nameの値を仮のディレクトリ内のファイル名に設定する
$output_file['tmp_name'] = {imgまでのパス}."/img/tmp/".$now."_picture.".$ext;
// ⑧アウトプット用の変数のurlに確認画面で表示するための画像urlを格納する
$output_file['url'] = "http://".$_SERVER['SERVER_NAME']."/".{imgまでのパス}."/img/tmp/".$now."_picture.".$ext;
finfo_close($finfo);
```

①トップのフォーム画面からPOSTされた値を受け取るコントローラーにあたる部分です。後半でPOSTデータのファイル名を変更する際に使用する変数 $now をあらかじめ定義しています。

②ここでバリデーションエラーが生じた際に、フォーム画面にリダイレクトしエラー文を表示させたかったので、あらかじめ変数 $errs を配列の形で定義しています。

③グローバル変数 FILES の中には input type="file" に格納されたデータが入れられています。この中身をコントローラー部分で扱うために、foreach を使い変数 $picture に連想配列の形で定義しています。

④POSTされてきたファイルが画像データかどうか判断するバリデーションを設定しています。ここではファイルの拡張子を調べることで画像データどうかの判定をしています。画像データではないと判断された場合は、エラー文が変数 $errs に格納され、変数 $picture が空になるようにしています。

⑤move_uploaded_file関数はグローバル変数 FILES でPOSTされてきたファイルを他のディレクトリに移動させることのできる関数です。これを用いてあらかじめ作っておいたtmpディレクトリに画像データを移すと同時に、現在時刻を交えたファイル名に変更しています。

⑥⑦⑧移動させた仮のディレクトリの情報をもとに、アウトプット用の変数 $output_file を定義しています。

## 確認画面

```php
<!-- ①画像の表示 -->
<p>写真：<img src="<?php echo $output_file['url']; ?>" width="300px"></p>
<!-- ②POSTするデータをinput type="hidden"で格納する -->
<?php foreach($output_file as $key => $var): ?>
  <input type="hidden" name="picture[<?php echo $key; ?>]" value="<?php echo $var; ?>">
<?php endforeach; ?>d
```

①仮のディレクトリに保存した画像情報をもとに確認画面に画像を表示させています。そのままのサイズだと画像が大きすぎたので、 width 属性を使ってサイズを調整しています。

②コントローラーで定義した変数 $output_file の内容をそのまま form タグ内の input type="hidden" に格納しPOSTする。このとき foreach を使って連想配列の形にしています。

## コントローラー部分での送信画面の処理

```php
// ①保存する画像の設定
foreach($_POST['picture'] as $key => $var){
    $picture[$key] = $var;
}

// ②tmp内のファイルをmainディレクトリに移す
if(rename($picture['tmp_name'], {imgまでのパス}."/img/main/".basename($picture['tmp_name']))){
    // ③後で確認するようにurlを変数に格納
    $output_file['url'] = "http://".{imgまでのパス}."/img/main/".basename($picture['tmp_name']);
    $result = "記録が完了しました。";
}else{
    $result = "記録に失敗しました。";
    // ④保存に失敗した時は仮のディレクトリ内のファイルを削除
    unlink($picture['tmp_name']);
}
```

①先ほどと同様、POSTデータを foreach を使い変数 $picture に連想配列の形で定義しています。

②仮のディレクトリ（tmp）内に格納されていた画像ファイルをmainディレクトリに移動させる作業をしています。これが成功したときと失敗したときで挙動を変えるように条件分岐しています。

③保存した画像を送信完了画面から確認できるように変数 $output_file['url'] を定義しています。

④もし保存に失敗した場合は、仮のディレクトリ（tmp）内の画像ファイルが削除されるようにしています。

## 送信完了画面

```php
<!-- ①結果の文字列の出力 -->
<h1><?php echo $result; ?></h1>

<!-- ②保存した画像へのリンクを作成 -->
<p><a href="<?php echo $picture['url']; ?>">画像の確認</a></p>
```

# おわりに
　今回は初歩的な研修であったために簡単なバリデーションしかかけていないため、ご自身で開発される際には画像保存のバリデーションを調べていただく必要があるかと思います。
　また、今回の実装は私が調べた範囲での記述方法になりますので、もっと簡単な実装方法もあると思います。参考程度に目を通していただければ幸いです。
　最後まで読んでいただきありがとうございました。