📁

ファイル添付機能を、確認画面付きのフォームに実装する方法

2023/04/07に公開

はじめに

こんにちは!
ポートでエンジニアをしている、@shoya.iikawaです。

今回は、つい先日にファイル添付機能&確認画面が付いたフォームを実装したので、その方法を書いて行こうかな、と思います。

というのも、ファイル添付単体であれば、<input type="file">を使いながら比較的簡単に実装出来ますし
確認画面単体であれば、<input type="hidden" />を使って「入力画面->確認画面->送信画面」と入力値を受け渡せば比較的簡単に実装出来ます。
しかし、確認画面&ファイル添付となると、添付されたファイルそのものは<input type="hidden" />で受け渡せ無いので、ちょこっと工夫がいるんですよね🧐

という事で、以下がどなたかの参考になれば嬉しいです!😀

前提

データが送られてきた事を確認するisset($_POST)や、フォームにとって何より大事なバリデーションあたりは全部省いて、極力ifを減らして要点に絞ったコードサンプルとなっているので、ご理解ください🙏
とは言え、ファイルアップロードの方法を求めてこの記事を開いて下さる方もいると思うので、ファイルアップロード関係のバリデーションだけは最後のまとめに簡単に書いておこうと思います。

まずは確認画面付きフォームを用意

手始めに、まずは確認画面が付いたシンプルなフォームを用意します。

index.php

フォームのTOP画面です。
お名前と問い合わせ内容の入力項目を用意しています。

ここで入力された内容は、次のconfirm.phpへと受け渡されます。

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-confirm-form/src/index.php

confirm.php

確認画面です。
index.phpから送信された情報を、エンドユーザー向けにはテキストで出力しつつ、<input type="hidden" />を利用して、次の送信画面に受け渡します。

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-confirm-form/src/confirm.php

send.php

送信処理&送信完了画面です。
confirm.phpから送信された情報を、PHPMailerというライブラリを使ってメールで送信しつつ、完了画面を表示しています。

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-confirm-form/src/send.php

添付ファイル機能を実装

さて、ここから本題です!
先述の確認画面付きフォームに、添付ファイル機能を付け足していきます。

要点は以下の通りです。

  • index.phpから送信されたファイルは、PHPによって一時的にサーバー内に保管される
  • そのファイルをsend.phpで扱う為、confirm.phpの段階で、プログラム側で決めた一時ディレクトリに移動させる
  • この時、一時ディレクトリに保管するファイル名はユニークなもので無ければならないので、元々アップロードされたファイル名とは別名に変換する
  • confirm.phpからsend.phpには、元々アップロードされたファイル名と、一時ファイル名の2つを受け渡す
  • send.phpでは、受け渡された一時ファイル名を使って、メールにファイルを添付する。
  • この際、メールを受信したユーザーには元々アップロードされたファイル名でダウンロード出来るように添付する
  • サーバー容量節約のため、送信が完了したらファイルは消去する
  • 確認画面でファイルを保管し、送信後に消す処理の為、確認画面で離脱するとファイルが残存する。
    その為、一定時間毎にファイルを消去する

そして、上記要点を満たすのが以下となります。

index.php

ここでの変更点は少なく、11行目でファイルをアップロード出来るようにenctype="multipart/form-data"を付け加え、21〜24行目で添付ファイルの入力項目を用意したのみです。
確認画面の有無に関係無く、ファイルアップロード機能があるフォームを作る時の基本ですね。

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-attachment-file/src/index.php

confirm.php

ここでは、FileOperatorクラス内のupload_file()メソッドを通して、先述の要点のうち

  • index.phpから送信されたファイルは、PHPによって一時的にサーバー内に保管される
  • そのファイルをsend.phpで扱う為、confirm.phpの段階で、プログラム側で決めた一時ディレクトリに移動させる
  • この時、一時ディレクトリに保管するファイル名はユニークなもので無ければならないので、元々アップロードされたファイル名とは別名に変換する

の3つの処理を行っています。
(1〜5行目)

そして、upload_file()から「元々のファイル名」と「一時保存のファイル名」がreturnされているので、それを<input type="hidden" />に埋め込み

  • confirm.phpからsend.phpには、元々アップロードされたファイル名と、一時ファイル名の2つを受け渡す

の準備をしています。
(33・34行目)

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-attachment-file/src/confirm.php

file-operator.php(confirm.phpで呼ばれる箇所)

ファイル操作を行う責務を担うプログラムです。
手始めに、インスタンスが生成されるタイミングで、ファイルの一時保存を行うディレクトリの存在確認と、存在しない場合は作成を行います。
また、今回は作成したフォルダ内のファイルに対して、外部からのアクセスは不要な為、.htaccessを利用して閲覧制限を設けました。

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-attachment-file/src/file-operator.php#L2-L18

次に、先述のconfirm.phpで呼ばれる、upload_file()です。

まず、ユニークなファイル名を実現するために、ファイル名を

  • 現在時刻をマイクロタイム(100万分の1秒)で取得
  • mt_rand()でランダムな数字を取得
  • その2つを連結し、ファイル名とする

という方法で手法で決定しています。
(42〜44行目)

万全を期すなら、「ファイル名を生成->同一ファイル名の存在確認->存在する場合はファイル名を改めて生成->ファイル名の存在確認...」の再帰処理を入れてもいいかも知れませんね。
(100万分の1秒単位で同時アクセスがあり、更にランダムに決められた数字が偶然一致し、ファイルの拡張子も一緒、なんて事は無いと思って、僕は再帰処理は入れませんが)

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-attachment-file/src/file-operator.php#L27-L54

send.php
最後に送信処理です。
ここでは先述の要点の内

  • send.phpでは、受け渡された一時ファイル名を使って、メールにファイルを添付する。
  • この際、メールを受信したユーザーには元々アップロードされたファイル名でダウンロード出来るように添付する
  • サーバー容量節約のため、送信が完了したらファイルは消去する
  • 確認画面でファイルを保管し、送信後に消す処理の為、確認画面で離脱するとファイルが残存する。

を実装しています。

まずファイルの添付に関しては、PHPMaileraddAtachment()を使うと、「第一引数に添付したいファイルのパス」「第二引数に添付した時のファイル名」を指定するだけで、簡単に添付が行えます。
(22行目)

そして、24行目で送信が完了した後、27行目でFileOperatorクラスのdel_file()メソッドを利用して、今回の添付ファイルの削除を行います。
さらに、過去アップロードされたものが残ってしまっている可能性を考慮して、同クラスのgarbage_collection()で一定時間経過したファイルの削除を行っています。

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-attachment-file/src/send.php

file-operator.php(send.phpで呼ばれる箇所)

get_file_path()del_file()は、それぞれconfirm.phpからsend.phpに渡された一時ファイル名を引数として渡す事で、保存されたファイルのフルパスの取得を行ったり、削除を行うものです。

つまり、送信処理のあとにdel_file()を呼び出せば、その送信に利用したファイルは消去出来ます。

加えて、garbage_collection();では、残存している一時ファイルを全て取得し、ファイルの作成日時と現在日時を照らし合わせ、一定時間が経過した一時ファイルを削除しています。
https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-attachment-file/src/file-operator.php#L56-L82

まとめ

ここまでお読みいただきありがとうございました!
最後に、ファイルアップロード部分だけバリデーション加えたものを記載して今回は〆とさせていただきます。

https://github.com/port-iikawa/file-attachment-for-form-with-confirm/blob/add-upload-validation/src/file-operator.php

Discussion