Web フォーム のシステム設計

UI や項目の設計ではなく、システムとして動作や URL の設計です。 SEO 的なものも考慮しません。 セキュリティに関しても触れないです。

よくあるお問い合わせフォームのようなものを MVC で構築する場合を考えます。

要件

  • お問い合わせがあったときに、お問い合わせ内容をメールで受け取る

基本設計

画面は 入力画面 確認画面 完了画面 の 3 つ

フォームを設置するということは受付や質問の手間を省きたいという意図があるはずなので、確認画面を挟みます。理由は下記です。

  • 誤送信させない
    • 誤送信メールを見る時間をなくす
  • フォームにある質問に確実に回答してもらう
    • 折返しの質問をなくす

各画面でできる操作は下記の通り。

  • 入力画面
    • フォームを送信して確認画面を表示する
  • 確認画面
    • 入力画面へ戻る
    • 入力を確定しメールを送信する

フォームは POST で送信する

GET は入力内容が URL にクエリパラメータとして乗るので、メールアドレスや個人情報などのセンシティブな入力項目がある場合は使用するべきではないです。 POST にすることでファイルの添付も可能になります。

バリデーションはサーバサイドで行う

フロントサイドのバリデーションはユーザによる操作が可能です。 言い換えればバリデーションを通さずにデータを送信できます。 サーバサイドでバリデーションを行うことで確実に不正な入力を弾けます。

入力画面・確認画面の URL は同じ、完了画面は別の URL とする

例えばこんな感じ。

画面 URL
入力画面 https://example.com/contact/
確認画面 https://example.com/contact/
完了画面 https://example.com/contact/complete/

すべてのリクエストは 1 つの Action で受け付けて、操作に応じて処理と View を分けます。

入力画面・確認画面の URL を分けてしまうと、無駄に考慮すべきことと必要な処理が増えてしまいます。 パッと思いつくのは下記くらいですが多分もっとあります。

  • フォームの送信先はどちらか
    • 入力画面の Action
      • バリデーションが通れば確認画面へリダイレクト?
      • バリデーションが通らなければ入力画面をそのまま表示
    • 確認画面の Action
      • バリデーションが通れば確認画面をそのまま表示
      • バリデーションが通らなければ入力画面へリダイレクト?
    • どちらにせよリダイレクトが必要で、そのときのデータはどう渡すのか?
  • 確認画面へ GET リクエストされたら入力画面へリダイレクトが必要

詳細設計

入力画面

初期表示

画面にはフォームと初期値が必要になります。 Model を初期化し、View に渡します。

+ index() {
+   // Model の初期化処理
+   // View に Model を渡す
+   // 入力画面の View を表示
+ }
フォームを送信して確認画面を表示する

POST リクエストで確認画面へ遷移する操作であれば、バリデーションを行います。 バリデーションが通れば確認画面の View を表示し、通らなければ入力画面でバリデーションエラーを表示します。

  index() {
    // Model の初期化処理
+   // POST されたデータを Model に Set
    // View に Model を渡す
+   if (POSTであるか) {
+     switch (ボタン) {
+       case 確認画面へ:
+         if (バリデーション) {
+           確認画面の View を表示
+         } 
+         break;
+     }
+   }
    // 入力画面の View を表示
  }

表示する画面が入力画面でも、確認画面でも同じ Model を View に渡します。

バリデーションエラーがあった場合

バリデーションエラーがあった場合は入力画面に戻します。 入力項目にエラーがある旨の文言と、エラーのある項目付近に具体的なエラー内容を表示します。

確認画面

確認画面の View には入力画面と同じ項目・値を <input type="hidden"> として出力します。 確認画面の操作ではこれを必ず POST します。 ファイルを添付可能な場合はアップロードされたファイルにキーを振ってそのキーを hidden に持たせるといった工夫が必要です。

入力を確定するときは再度バリデーションを行い、メールの送信処理が正常に完了すれば、完了画面へリダイレクトします。 リダイレクトで完了画面へ遷移することで、完了画面の F5 更新でデータが再送されることを防げます。

入力画面へ戻るときはデータを入力画面へ渡せさえすればよいです。

  index() {
    // Model の初期化処理
    // POST されたデータを Model に Set
    // View に Model を渡す
    if (POSTであるか) {
      switch (ボタン) {
        case 確認画面へ:
          if (バリデーション) {
            確認画面の View を表示
          } 
          break;
+       case 確定:
+         if (バリデーション && メール送信処理) {
+           完了画面へリダイレクト
+         } 
+         break;
      }
    }
    // 入力画面の View を表示
  }