見出し画像

PHPのお問い合わせフォームに、必須項目とバリデーションを追加する

こんにちは、UPLIFT代表しかたこうきです。前回は、PHPのシンプルなお問い合わせフォームに、CSRF対策を追加しました。

また、途中からこの記事を読まれる方は、ぜひこのマガジンの1記事目からも読んでみてくださいね。

さて、今回はフォームを作るにあたって、絶対に避けては通れない「必須項目」について設定していきたいと思います。

また、バリデーションも同時に設定します。バリデーションは、入力されたデータが期待通りかどうか、異常なデータが入力されてないか検査する機能のことです。

必須項目は、お問い合わせフォームには必ず求められる機能なので、しっかり実装しましょう。

必須項目であることを定義する

まず、どの項目が必須項目であるのかを定義してやる必要があります。通常、HTMLから指定します。

今回は「お名前」と「メールアドレス」の項目が必須項目にしないといけな、という設定で行きます。

index.phpを、以下のように書き換えてください。

<!-- これまでのソースコード -->
<div>
    <label for="name">お名前:</label>
    <input type="text" id="name" name="name" />
</div>
<div>
    <label for="email">メールアドレス:</label>
    <input type="email" id="email" name="email" />
</div>

<!-- 今回書き換えたソースコード -->
<div>
    <label for="name">お名前:</label>
    <input type="text" id="name" name="name" required />
</div>
<div>
    <label for="email">メールアドレス:</label>
    <input type="email" id="email" name="email" required />
</div>

一見、どこが変わったのか分かりづらいかもしれませんね。input type="text" name="name"とinput type="email" name="email"のそれぞれに、requiredという属性がついているのが変更ポイントです。

HTML上の変更は、ひとまず以上ですので、この状態で一旦どのような挙動をするか確認します。

「お名前」と「メールアドレス」を必須項目に変更した上で、あえてこの2項目は何も入力せずに、送信ボタンを押してみます。

このように、エラーが表示される

必須項目を入力せずに送信ボタンを押すと、このような吹き出し形のエラーが表示されます。これは、HTMLが用意している標準のエラー画面です。

この通り、送信ボタンを押しても次の画面にはいかないのですが、実は裏技的な方法でこのエラーを突破する方法もあります(これはまた別の機会に紹介します)

ですので、突破されてしまった場合、必須項目が空欄のままメール送信されてしまいます。したがって、この後はPHP側でデータのチェック、バリデーションを行います。(これを、バックエンドサイドバリデーションと言います。)

PHP側でバリデーションする

では、contact.phpを以下のように変更しましょう。

<?php
// ↓変更前のソースコード

session_start();
// ワンタイムトークンの一致を確認
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
  // トークンが一致しなかった場合
  die('お問い合わせの送信に失敗しました');
}

mb_language("Japanese");
//↑マルチバイトの言語設定を日本語にします

mb_internal_encoding("UTF-8");
//↑マルチバイトの文字エンコーディングをUTF-8にします

// 以降続きます
// ---------

// ↓変更後のソースコード

session_start();
// ワンタイムトークンの一致を確認
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
  // トークンが一致しなかった場合
  die('お問い合わせの送信に失敗しました');
}

// nameまたはemailのどちらかが入力されていなければ、index.phpへリダイレクト
if(empty($_POST['name']) || empty($_POST['email'])) {
  header('Location: /index.php');
}

mb_language("Japanese");
//↑マルチバイトの言語設定を日本語にします

mb_internal_encoding("UTF-8");
//↑マルチバイトの文字エンコーディングをUTF-8にします

// 以降続きます
// ---------

前回、ワンタイムトークンとマルチバイト言語設定を記述した箇所の間に、新しい条件分岐の文を追加しました。

empty()というのは、その中身が空っぽだったら、という意味です。つまり、nameやemailのどちらかが空っぽだった場合、header('Location : /index.php');へリダイレクトする、という意味になっています。

正常にHTMLのエラーが動作していれば、PHPへPOSTされることがないのでこの命令文は不要なのですが、先述の通り、裏技的にHTMLのエラーを回避する方法があるので、PHP側でも、万一入力欄が空っぽだった場合に備えての対応策を追加しました。これがバリデーションです。

エラーメッセージ(フラッシュメッセージ)を追加する

ただ、このままだと、必須項目に入力がなく送信ボタンを押してそれが通ってしまった場合、何の前触れもなく突然入力画面にリダイレクトとなるので、画面に何も変化が起きません。これでは、ユーザーは送信ボタンがうまく動作しているのかどうか分かりづらい状況になります。

もちろん、正しく送信はできていないのですが、「正しく送信ができなかった」ということを明確に伝える必要があります。

そこで、入力ページへリダイレクトされた場合は、エラーメッセージが表示されるようにします。

contact.phpとindex.phpをそれぞれ、以下のように書き換えます

contact.php

<?php

session_start();
// ワンタイムトークンの一致を確認
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
  // トークンが一致しなかった場合
  die('お問い合わせの送信に失敗しました');
}

// ↓追記ここから

// 必須項目の確認
if(empty($_POST['name'])) {
  $_SESSION['flash']['name'] = 'お名前は必須項目です';
}

if(empty($_POST['name'])) {
  $_SESSION['flash']['email'] = 'メールアドレスは必須項目です';
}

// ↑追記ここまで

// nameまたはemailのどちらかが入力されていなければ、index.phpへリダイレクト
if(empty($_POST['name']) || empty($_POST['email'])) {
  header('Location: /index.php');
}

mb_language("Japanese");
//↑マルチバイトの言語設定を日本語にします

mb_internal_encoding("UTF-8");
//↑マルチバイトの文字エンコーディングをUTF-8にします

まず、追記内容はエラーメッセージの文言設定となります。それぞれ必須項目でない項目が空欄であった場合、$_SESSIONのflash、nameとflash、emailという配列に、それぞれのエラーメッセージを設定しています。

index.php

<?php
// セッションの利用を開始
session_start();

// ↓追記ここから

// セッションのflashメッセージをクリア
$flash = isset($_SESSION['flash']) ? $_SESSION['flash'] : [];
unset($_SESSION['flash']);

// ↑追記ここまで

// ワンタイムトークン生成
$toke_byte = openssl_random_pseudo_bytes(16);
$csrf_token = bin2hex($toke_byte);

// トークンをセッションに保存
$_SESSION['csrf_token'] = $csrf_token;
?>
<!-- 中略 -->
<div>
    <label for="name">お名前:</label>
    <input type="text" id="name" name="name" required />
    <!-- ↓追記 -->
    <?php echo isset($flash['name']) ? $flash['name'] : null ?>
</div>
<div>
    <label for="email">メールアドレス:</label>
    <input type="email" id="email" name="email" required />
    <!-- ↓追記 -->
    <?php echo isset($flash['email']) ? $flash['email'] : null ?>
</div>

次に、index.phpです。リダイレクトで戻ってきた時に、$_SESSIONにflashが存在すれば、新たに$flashという変数に格納し、そうでなければ$flashは空の配列、そして$_SESSION['flash']をunset関数で破棄しています。

これにより、$flash変数にメッセージが入っている場合は、後半のHTMLの中で、$flash['name']か$flash['email']のどちらか、もしくはその両方が表示されます。

最初にindex.phpにアクセスしたときは、エラーはまだ発生してないので$flashには何も格納されません。したがって、エラーメッセージも表示されません。

また、エラーで戻ってきてエラーメッセージが表示されても、リロードすれば$flashの内容は破棄されるため、フラッシュメッセージは最初のリダイレクトの時のみ表示されます。

入力した内容はリダクレクト後にも表示させたい

ただ、ここまでのコードの内容でもまだ問題があります。

既に試された方もいると思いますが、このままではエラーメッセージは表示されても、入力していた項目の内容は一旦クリアされてしまいます。

そのため、入力されていた内容は前の画面に戻っても残すという機能の実装が必要です。

さらに、以下のように書き換えます。

contact.php

<?php

session_start();
// ワンタイムトークンの一致を確認
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
  // トークンが一致しなかった場合
  die('お問い合わせの送信に失敗しました');
}

// 必須項目の確認
if(empty($_POST['name'])) {
  $_SESSION['flash']['name'] = 'お名前は必須項目です';
}

$_SESSION['original']['name'] = $_POST['name']; // nameに入力があった場合、一旦セッションに保存

if(empty($_POST['email'])) {
  $_SESSION['flash']['email'] = 'メールアドレスは必須項目です';
}

$_SESSION['original']['email'] = $_POST['email']; // emailに入力があった場合、一旦セッションに保存
$_SESSION['original']['message'] = $_POST['message']; // messageに入力があった場合、一旦セッションに保存

// nameまたはemailのどちらかが入力されていなければ、index.phpへリダイレクト
if(empty($_POST['name']) || empty($_POST['email'])) {
  header('Location: /index.php');
  exit;
}

mb_language("Japanese");
//↑マルチバイトの言語設定を日本語にします

mb_internal_encoding("UTF-8");
//↑マルチバイトの文字エンコーディングをUTF-8にします

フラッシュメッセージと同じ要領で、POSTに入ってきた来た各データをセッションに移します。

index.php

<?php
// セッションの利用を開始
session_start();

// セッションのflashメッセージをクリア
$flash = isset($_SESSION['flash']) ? $_SESSION['flash'] : [];
unset($_SESSION['flash']);

// 過去のPOSTデータをクリア
$original = isset($_SESSION['original']) ? $_SESSION['original'] : [];
unset($_SESSION['original']);

// ワンタイムトークン生成
$toke_byte = openssl_random_pseudo_bytes(16);
$csrf_token = bin2hex($toke_byte);

// トークンをセッションに保存
$_SESSION['csrf_token'] = $csrf_token;

?>

<!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>
    <form action="contact.php" method="POST">
        <div>
            <label for="name">お名前:</label>
            <input type="text" id="name" name="name" value="<?php echo isset($original['name']) ? $original['name'] : null;?>" required />
            <?php echo isset($flash['name']) ? $flash['name'] : null ?>
        </div>
        <div>
            <label for="email">メールアドレス:</label>
            <input type="text" id="email" name="email" value="<?php echo isset($original['email']) ? $original['email'] : null;?>" required />
            <?php echo isset($flash['email']) ? $flash['email'] : null ?>
        </div>
        <div>
            <label for="message">お問い合わせ本文</label>
            <textarea id="message" name="message"><?php echo isset($original['message']) ? $original['message'] : null;?></textarea>
        </div>
        <input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>" />
        <button type="submit">送信</button>
    </form>
</body>

</html>

こちらも、同じくフラッシュメッセージと同じく処理します。$originalに入った配列は、input value=""の中で展開するのと、$original['message']は、textareaの中で展開するようにします。

では、最後にブラウザでチェックしてみよう

実は、デベロッパーツールを開き、form要素の「novalidate」という属性をつけると、HTMLのエラー機能(バリデーション)は無効化できます。

最も、こういった開発の時にしか使う意味のない機能なので、本番公開時にはnovalidateをつけないようにしましょう。

novalidetで、HTMLのバリデーションを無視して送信が可能

このように、メールアドレスに入力がない時には、メールアドレスの横にエラーが。名前の入力がないときは名前の横にエラーが表示されます。

CSSの調整をしていないので素っ気ないですが、後から赤文字などに調整したり、位置を調整すると良い感じになると思います。

ソースコードはこちら

ここまでのソースコードはGitHubにまとめてあります。今回の内容は、0.3.0に含まれています。ぜひ、内容をチェックしてみてください!

最後に

いかがでしたか。セッションにPOSTデータを入れて戻すあたり、ちょっと手間がかかりましたね。

しかし、必須項目が入力されてないまま送信されてしまっては大変なことになります。必須項目はバリデーションの基本中の基本なので、しっかり覚えておきましょう。

とはいえ、バリデーションのやり方や、全画面への戻り方はこのやり方だけではありません。次のステップでは、もう少しスマートなやり方も紹介しますので、楽しみにしていてください。


頂いたサポートはクリエイター活動の主に機材費・出張費に充てます! より良い作品アウトプットのためにご協力よろしくお願いします!