PHP 表单和用户输入 处理

PHP 中的 $_GET$_POST$_REQUEST 变量用于检索表单中的用户输入信息。表单是Web应用程序与用户交互的重要方式。

PHP 表单处理基础

当处理 HTML 表单时,PHP 能自动将表单元素的值转换为 PHP 脚本可用的变量。

超全局变量 描述 使用场景
$_GET 通过 URL 参数传递的数据 获取数据、搜索、分页等
$_POST 通过 HTTP POST 方法传递的数据 提交表单、上传文件、敏感数据
$_REQUEST 包含 $_GET$_POST$_COOKIE 的数据 不推荐使用,安全性较低

基础表单实例

HTML 表单 (index.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
    <style>
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
        }
        input[type="text"],
        input[type="email"],
        input[type="password"] {
            width: 300px;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .error {
            color: red;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <h1>用户注册表单</h1>
    <form action="process_form.php" method="post">
        <div class="form-group">
            <label for="fname">名字:</label>
            <input type="text" id="fname" name="fname" required>
        </div>

        <div class="form-group">
            <label for="lname">姓氏:</label>
            <input type="text" id="lname" name="lname" required>
        </div>

        <div class="form-group">
            <label for="email">邮箱:</label>
            <input type="email" id="email" name="email" required>
        </div>

        <div class="form-group">
            <label for="age">年龄:</label>
            <input type="text" id="age" name="age">
        </div>

        <div class="form-group">
            <label>性别:</label>
            <input type="radio" id="male" name="gender" value="male">
            <label for="male">男</label>
            <input type="radio" id="female" name="gender" value="female">
            <label for="female">女</label>
        </div>

        <div class="form-group">
            <label>兴趣爱好:</label>
            <input type="checkbox" id="sports" name="hobbies[]" value="sports">
            <label for="sports">运动</label>
            <input type="checkbox" id="reading" name="hobbies[]" value="reading">
            <label for="reading">阅读</label>
            <input type="checkbox" id="music" name="hobbies[]" value="music">
            <label for="music">音乐</label>
        </div>

        <div class="form-group">
            <label for="country">国家:</label>
            <select id="country" name="country">
                <option value="">请选择</option>
                <option value="china">中国</option>
                <option value="usa">美国</option>
                <option value="uk">英国</option>
            </select>
        </div>

        <div class="form-group">
            <label for="bio">个人简介:</label>
            <textarea id="bio" name="bio" rows="4" cols="50"></textarea>
        </div>

        <input type="submit" value="提交">
        <input type="reset" value="重置">
    </form>
</body>
</html>

PHP 处理脚本 (process_form.php):

<?php
// 检查表单是否已提交
if ($_SERVER["REQUEST_METHOD"] == "POST") {

    // 获取表单数据
    $firstName = $_POST['fname'] ?? '';
    $lastName = $_POST['lname'] ?? '';
    $email = $_POST['email'] ?? '';
    $age = $_POST['age'] ?? '';
    $gender = $_POST['gender'] ?? '';
    $hobbies = $_POST['hobbies'] ?? [];
    $country = $_POST['country'] ?? '';
    $bio = $_POST['bio'] ?? '';

    // 显示结果
    echo "<!DOCTYPE html>";
    echo "<html lang='zh-CN'>";
    echo "<head><title>表单处理结果</title></head>";
    echo "<body>";
    echo "<h1>表单提交成功!</h1>";
    echo "<h2>您的信息:</h2>";
    echo "<p><strong>姓名:</strong> " . htmlspecialchars($firstName) . " " . htmlspecialchars($lastName) . "</p>";
    echo "<p><strong>邮箱:</strong> " . htmlspecialchars($email) . "</p>";

    if (!empty($age)) {
        echo "<p><strong>年龄:</strong> " . htmlspecialchars($age) . "</p>";
    }

    if (!empty($gender)) {
        echo "<p><strong>性别:</strong> " . htmlspecialchars($gender) . "</p>";
    }

    if (!empty($hobbies)) {
        echo "<p><strong>兴趣爱好:</strong> " . implode(', ', array_map('htmlspecialchars', $hobbies)) . "</p>";
    }

    if (!empty($country)) {
        echo "<p><strong>国家:</strong> " . htmlspecialchars($country) . "</p>";
    }

    if (!empty($bio)) {
        echo "<p><strong>个人简介:</strong> <br>" . nl2br(htmlspecialchars($bio)) . "</p>";
    }

    echo "<a href='index.html'>返回表单</a>";
    echo "</body></html>";

} else {
    // 如果不是POST请求,重定向到表单页
    header("Location: index.html");
    exit;
}
?>

GET 与 POST 方法比较

特性 GET 方法 POST 方法
数据位置 URL 参数 HTTP 请求体
数据可见性 可见(在URL中) 不可见
数据长度限制 有限制(约2048字符) 无限制
安全性 较低 较高
缓存 可缓存 不可缓存
使用场景 获取数据、搜索、分页 提交表单、上传文件、敏感操作

GET 方法示例:

<!-- 搜索表单 -->
<form action="search.php" method="get">
    <input type="text" name="keyword" placeholder="输入搜索关键词">
    <input type="submit" value="搜索">
</form>

<!-- 生成的URL:search.php?keyword=用户输入 -->
<?php
// search.php
if (isset($_GET['keyword'])) {
    $keyword = $_GET['keyword'];
    echo "搜索关键词: " . htmlspecialchars($keyword);
    // 执行搜索逻辑...
}
?>

表单验证

表单验证是确保数据安全和完整性的关键步骤,应该在客户端和服务器端都进行验证。

完整的表单验证示例:

<?php
// 初始化变量
$errors = [];
$firstName = $lastName = $email = $age = $gender = $bio = '';
$hobbies = [];
$country = '';

// 检查表单是否提交
if ($_SERVER["REQUEST_METHOD"] == "POST") {

    // 清理和验证数据
    $firstName = clean_input($_POST['fname'] ?? '');
    $lastName = clean_input($_POST['lname'] ?? '');
    $email = clean_input($_POST['email'] ?? '');
    $age = clean_input($_POST['age'] ?? '');
    $gender = clean_input($_POST['gender'] ?? '');
    $hobbies = isset($_POST['hobbies']) ? array_map('clean_input', $_POST['hobbies']) : [];
    $country = clean_input($_POST['country'] ?? '');
    $bio = clean_input($_POST['bio'] ?? '');

    // 验证必填字段
    if (empty($firstName)) {
        $errors['fname'] = "名字是必填字段";
    } elseif (!preg_match("/^[a-zA-Z\x{4e00}-\x{9fa5} ]+$/u", $firstName)) {
        $errors['fname'] = "名字只能包含字母和中文";
    }

    if (empty($lastName)) {
        $errors['lname'] = "姓氏是必填字段";
    } elseif (!preg_match("/^[a-zA-Z\x{4e00}-\x{9fa5} ]+$/u", $lastName)) {
        $errors['lname'] = "姓氏只能包含字母和中文";
    }

    // 验证邮箱
    if (empty($email)) {
        $errors['email'] = "邮箱是必填字段";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = "邮箱格式不正确";
    }

    // 验证年龄
    if (!empty($age)) {
        if (!is_numeric($age) || $age < 1 || $age > 150) {
            $errors['age'] = "年龄必须是1-150之间的数字";
        }
    }

    // 如果没有错误,处理数据
    if (empty($errors)) {
        // 这里可以保存到数据库或进行其他操作
        echo "<div class='success'>表单提交成功!</div>";
        // 显示提交的数据...
        display_user_data($firstName, $lastName, $email, $age, $gender, $hobbies, $country, $bio);
    }
}

/**
 * 清理输入数据
 */
function clean_input($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
}

/**
 * 显示用户数据
 */
function display_user_data($fname, $lname, $email, $age, $gender, $hobbies, $country, $bio) {
    echo "<h3>提交的信息:</h3>";
    echo "<p><strong>姓名:</strong> $fname $lname</p>";
    echo "<p><strong>邮箱:</strong> $email</p>";

    if (!empty($age)) echo "<p><strong>年龄:</strong> $age</p>";
    if (!empty($gender)) echo "<p><strong>性别:</strong> $gender</p>";
    if (!empty($hobbies)) echo "<p><strong>兴趣爱好:</strong> " . implode(', ', $hobbies) . "</p>";
    if (!empty($country)) echo "<p><strong>国家:</strong> $country</p>";
    if (!empty($bio)) echo "<p><strong>个人简介:</strong> <br>$bio</p>";
}

// 显示表单
display_form($firstName, $lastName, $email, $age, $gender, $hobbies, $country, $bio, $errors);

/**
 * 显示表单
 */
function display_form($fname, $lname, $email, $age, $gender, $hobbies, $country, $bio, $errors) {
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>表单验证示例</title>
    <style>
        .error { color: red; font-size: 14px; }
        .success { color: green; font-size: 16px; padding: 10px; background: #f0fff0; }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; }
        input[type="text"], input[type="email"], select, textarea {
            width: 300px; padding: 8px; border: 1px solid #ddd; border-radius: 4px;
        }
    </style>
</head>
<body>
    <h1>用户注册表单(带验证)</h1>

    <form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
        <div class="form-group">
            <label for="fname">名字:</label>
            <input type="text" id="fname" name="fname" value="<?php echo $fname; ?>" required>
            <?php if (isset($errors['fname'])) echo "<div class='error'>{$errors['fname']}</div>"; ?>
        </div>

        <div class="form-group">
            <label for="lname">姓氏:</label>
            <input type="text" id="lname" name="lname" value="<?php echo $lname; ?>" required>
            <?php if (isset($errors['lname'])) echo "<div class='error'>{$errors['lname']}</div>"; ?>
        </div>

        <div class="form-group">
            <label for="email">邮箱:</label>
            <input type="email" id="email" name="email" value="<?php echo $email; ?>" required>
            <?php if (isset($errors['email'])) echo "<div class='error'>{$errors['email']}</div>"; ?>
        </div>

        <div class="form-group">
            <label for="age">年龄:</label>
            <input type="text" id="age" name="age" value="<?php echo $age; ?>">
            <?php if (isset($errors['age'])) echo "<div class='error'>{$errors['age']}</div>"; ?>
        </div>

        <div class="form-group">
            <label>性别:</label>
            <input type="radio" id="male" name="gender" value="male" <?php echo ($gender == 'male') ? 'checked' : ''; ?>>
            <label for="male">男</label>
            <input type="radio" id="female" name="gender" value="female" <?php echo ($gender == 'female') ? 'checked' : ''; ?>>
            <label for="female">女</label>
        </div>

        <div class="form-group">
            <label>兴趣爱好:</label>
            <?php
            $hobbyOptions = ['sports' => '运动', 'reading' => '阅读', 'music' => '音乐'];
            foreach ($hobbyOptions as $value => $label) {
                $checked = in_array($value, $hobbies) ? 'checked' : '';
                echo "<input type='checkbox' id='$value' name='hobbies[]' value='$value' $checked>";
                echo "<label for='$value'>$label</label>";
            }
            ?>
        </div>

        <div class="form-group">
            <label for="country">国家:</label>
            <select id="country" name="country">
                <option value="">请选择</option>
                <option value="china" <?php echo ($country == 'china') ? 'selected' : ''; ?>>中国</option>
                <option value="usa" <?php echo ($country == 'usa') ? 'selected' : ''; ?>>美国</option>
                <option value="uk" <?php echo ($country == 'uk') ? 'selected' : ''; ?>>英国</option>
            </select>
        </div>

        <div class="form-group">
            <label for="bio">个人简介:</label>
            <textarea id="bio" name="bio" rows="4" cols="50"><?php echo $bio; ?></textarea>
        </div>

        <input type="submit" value="提交">
        <input type="reset" value="重置">
    </form>
</body>
</html>
<?php
}
?>

安全性考虑

表单安全注意事项

  • 始终验证和过滤用户输入
  • 使用预处理语句防止SQL注入
  • 对输出进行转义防止XSS攻击
  • 使用CSRF令牌防止跨站请求伪造
  • 对文件上传进行严格限制

安全处理示例:

<?php
// 安全处理函数
function secure_form_processing() {

    // 1. 检查请求方法
    if ($_SERVER["REQUEST_METHOD"] !== "POST") {
        die("无效的请求方法");
    }

    // 2. 验证CSRF令牌(如果使用)
    if (!validate_csrf_token()) {
        die("CSRF令牌验证失败");
    }

    // 3. 数据过滤
    $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
    $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
    $age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);

    // 4. 数据验证
    $errors = [];

    if (empty($username) || strlen($username) < 3) {
        $errors[] = "用户名至少3个字符";
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "邮箱格式不正确";
    }

    if ($age === false || $age < 1 || $age > 150) {
        $errors[] = "年龄必须是1-150之间的数字";
    }

    // 5. 如果有错误,显示错误信息
    if (!empty($errors)) {
        foreach ($errors as $error) {
            echo "<div class='error'>$error</div>";
        }
        return;
    }

    // 6. 安全地使用数据(例如保存到数据库)
    save_to_database($username, $email, $age);
}

function validate_csrf_token() {
    // CSRF令牌验证逻辑
    return isset($_POST['csrf_token']) &&
           $_POST['csrf_token'] === ($_SESSION['csrf_token'] ?? '');
}

function save_to_database($username, $email, $age) {
    // 使用预处理语句防止SQL注入
    try {
        $pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password");
        $stmt = $pdo->prepare("INSERT INTO users (username, email, age) VALUES (?, ?, ?)");
        $stmt->execute([$username, $email, $age]);
        echo "数据保存成功!";
    } catch (PDOException $e) {
        echo "数据库错误: " . $e->getMessage();
    }
}
?>

文件上传处理

PHP 还可以处理文件上传,这是一个特殊的表单处理场景。

<!-- 文件上传表单 -->
<form action="upload.php" method="post" enctype="multipart/form-data">
    <div class="form-group">
        <label for="avatar">选择头像:</label>
        <input type="file" id="avatar" name="avatar" accept="image/*">
    </div>
    <input type="submit" value="上传文件">
</form>
<?php
// upload.php - 文件上传处理
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES["avatar"])) {

    $uploadDir = "uploads/";
    $maxFileSize = 2 * 1024 * 1024; // 2MB
    $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];

    $file = $_FILES["avatar"];

    // 检查错误
    if ($file["error"] !== UPLOAD_ERR_OK) {
        die("文件上传错误: " . $file["error"]);
    }

    // 检查文件大小
    if ($file["size"] > $maxFileSize) {
        die("文件太大,最大允许2MB");
    }

    // 检查文件类型
    $fileType = mime_content_type($file["tmp_name"]);
    if (!in_array($fileType, $allowedTypes)) {
        die("只允许上传JPEG, PNG, GIF格式的图片");
    }

    // 生成安全文件名
    $extension = pathinfo($file["name"], PATHINFO_EXTENSION);
    $safeFilename = uniqid() . '.' . $extension;
    $uploadPath = $uploadDir . $safeFilename;

    // 移动文件
    if (move_uploaded_file($file["tmp_name"], $uploadPath)) {
        echo "文件上传成功!";
        echo "<br>文件名: " . htmlspecialchars($safeFilename);
    } else {
        echo "文件上传失败";
    }
}
?>

最佳实践总结

  • 始终验证输入:在客户端和服务器端都进行验证
  • 使用合适的请求方法:GET用于获取数据,POST用于提交数据
  • 转义输出:使用htmlspecialchars()防止XSS攻击
  • 使用预处理语句:防止SQL注入攻击
  • 实施CSRF保护:对重要操作使用CSRF令牌
  • 限制文件上传:对文件类型、大小进行严格限制
  • 提供清晰的错误信息:帮助用户正确填写表单
  • 保持表单状态:提交失败时保留用户已填写的数据