PHP 完整表单实例

Wiring validation, re-rendering, escaping and redirect into a usable form handler.

Intermediate
Key PointsA production-ready form handles validation, re-rendering, escaping and double submission.

The goal

A contact form with name, email, website, comment and gender. Requirements: required and format checks, sticky fields on error, PRG to prevent resubmission.

The form page

PHP
<?php
// contact.php —— 表单与处理逻辑在同一文件
declare(strict_types=1);

$errors = [];
$old = ["name" => "", "email" => "", "website" => "", "comment" => "", "gender" => ""];

if (($_SERVER["REQUEST_METHOD"] ?? "") === "POST") {
    foreach ($old as $k => $_) {
        $old[$k] = trim((string)($_POST[$k] ?? ""));
    }

    if ($old["name"] === "") {
        $errors["name"] = "姓名必填";
    } elseif (mb_strlen($old["name"]) > 20) {
        $errors["name"] = "姓名最多 20 个字";
    }

    if ($old["email"] === "") {
        $errors["email"] = "邮箱必填";
    } elseif (filter_var($old["email"], FILTER_VALIDATE_EMAIL) === false) {
        $errors["email"] = "邮箱格式不正确";
    }

    if ($old["website"] !== "" && filter_var($old["website"], FILTER_VALIDATE_URL) === false) {
        $errors["website"] = "网址格式不正确";
    }

    if (!in_array($old["gender"], ["female", "male"], true)) {
        $errors["gender"] = "请选择性别";
    }

    if (!$errors) {
        // 成功:写入会话后重定向(PRG)
        $_SESSION["contact_ok"] = true;
        header("Location: thanks.php");
        exit;
    }
}

function e(string $s): string {
    return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
}
?>
<form method="post" novalidate>
    <p>
        <label>姓名 *</label>
        <input name="name" value="<?= e($old["name"]) ?>">
        <em><?= e($errors["name"] ?? "") ?></em>
    </p>
    <p>
        <label>邮箱 *</label>
        <input name="email" value="<?= e($old["email"]) ?>">
        <em><?= e($errors["email"] ?? "") ?></em>
    </p>
    <p>
        <label>网址</label>
        <input name="website" value="<?= e($old["website"]) ?>">
        <em><?= e($errors["website"] ?? "") ?></em>
    </p>
    <p>
        <label>评论</label>
        <textarea name="comment"><?= e($old["comment"]) ?></textarea>
    </p>
    <p>
        <label>性别 *</label>
        <label><input type="radio" name="gender" value="female"
            <?= $old["gender"] === "female" ? "checked" : "" ?>>女</label>
        <label><input type="radio" name="gender" value="male"
            <?= $old["gender"] === "male" ? "checked" : "" ?>>男</label>
        <em><?= e($errors["gender"] ?? "") ?></em>
    </p>
    <button>提交</button>
</form>

Submitting invalid data (empty name, bad@ email) renders errors alongside the retained values:

Output
<p>
        <label>姓名 *</label>
        <input name="name" value="">
        <em>姓名必填</em>
    </p>
    <p>
        <label>邮箱 *</label>
        <input name="email" value="bad@">
        <em>邮箱格式不正确</em>
    </p>

The success page

PHP
<?php
// thanks.php
session_start();

if (empty($_SESSION["contact_ok"])) {
    header("Location: contact.php");
    exit;
}

unset($_SESSION["contact_ok"]);
echo "提交成功,我们会尽快联系你。";
Output
提交成功,我们会尽快联系你。

Visiting thanks.php directly bounces back to the form because no success flag exists in the session — stricter than showing an empty success page.

The PRG pattern

Three steps of Post/Redirect/Get:

  1. Browser POSTs the form
  2. Server responds 302 Location: thanks.php
  3. Browser GETs it; the address bar now shows a GET page

Refreshing re-requests the GET page, so no resubmission. The cost: errors cannot simply be echoed — store them in the session or render in place, as done here.

Pre-launch checklist

  • **Everything passes e()** —— including value, textarea and error messages
  • **novalidate only disables browser hints**; server logic is unchanged
  • CSRF token —— write operations need one against cross-site forgery
  • **post_max_size / upload_max_filesize** —— tune ini for larger forms
  • Business-level dedupe —— unique constraints, idempotency keys
Forms are the largest attack surface in a PHP app. Missing any of these five invites XSS, CSRF, oversized payloads or duplicate orders.

Friends & Partners