Kodokon kodokon.com

表单:接收并验证数据

像专业人士一样处理表单:选对 HTTP 方法、严格验证输入,并安全地重新显示。

9 分钟 · 3 题

在 Kodokon 中打开本课

表单是你的用户与你的 PHP 代码之间的第一个接触点。get 方法把数据放在 URL 中(?q=pizza):非常适合搜索或筛选,因为页面可以被收藏和分享。post 方法把数据放在请求体中:只要请求会改变某些东西(创建账户、下单、删除)或携带敏感数据,它就是必须使用的方法。PHP 会把接收到的值存放在 $_GET$_POST 超全局变量中。

HTML
<form action="search.php" method="get">
  <label for="q">Search</label>
  <input id="q" name="q" type="text">
  <button type="submit">Search</button>
</form>
<?php
$query = trim($_GET['q'] ?? '');

if ($query !== '') {
    echo "<p>You are searching for: " .
        htmlspecialchars($query, ENT_QUOTES) . "</p>";
}
?>
search.php:表单及其处理逻辑写在同一个页面里。

职业铁律第一条:永远不要信任传入的数据。用户可以发送任何东西,包括在你的表单之外精心伪造的值。filter_input() 函数一步完成读取和验证:它返回验证通过的值,无效时返回 false,字段缺失时返回 null - 这是你在错误提示中需要区分的三种情况。

PHP
<?php

declare(strict_types=1);

$errors = [];

$email = filter_input(
    INPUT_POST,
    'email',
    FILTER_VALIDATE_EMAIL
);

$age = filter_input(
    INPUT_POST,
    'age',
    FILTER_VALIDATE_INT,
    ['options' => ['min_range' => 18, 'max_range' => 120]]
);

$name = trim((string) ($_POST['name'] ?? ''));

if ($name === '' || mb_strlen($name) > 50) {
    $errors[] = "Name required (50 characters max).";
}

if (!is_string($email)) {
    $errors[] = "Invalid email address.";
}

if (!is_int($age)) {
    $errors[] = "Invalid age (between 18 and 120).";
}

if ($errors === []) {
    echo "Registration valid.";
}
用 filter_input() 验证一次注册。

当验证失败时,不要返回一个空表单:重新显示用户输入过的值(一个 "粘性" 表单),并在相关字段旁边显示错误提示。任何重新显示的值都必须经过 htmlspecialchars():正是它中和了标签并阻止了脚本注入。

HTML
<?php
$name = trim($_POST['name'] ?? '');
$hasError = isset($_POST['name']) && $name === '';
$safeName = htmlspecialchars($name, ENT_QUOTES);
?>
<form method="post" action="">
  <label for="name">Your name</label>
  <input id="name" name="name"
    value="<?= $safeName ?>">
  <?php if ($hasError): ?>
    <p>Name is required.</p>
  <?php endif; ?>
  <button type="submit">Submit</button>
</form>
粘性表单:输入的内容在出错后依然保留。

知识检测

确认你已牢记本课的重点内容。

  1. 对于一个创建用户账户的表单,哪种 HTTP 方法是合适的?
    • GET,这样数据就一直显示在 URL 中
    • POST,因为该请求会改变服务器状态,而且数据不应出现在 URL 中
    • 两者都行,这只是风格问题
  2. 如果字段根本没有被发送,filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL) 会返回什么?
    • false
    • 一个空字符串
    • null
    • 它会抛出一个异常
  3. 为什么在把一个值重新显示到页面之前,要先让它经过 htmlspecialchars()?
    • 为了中和 HTML 标签并阻止脚本注入(XSS 漏洞)
    • 为了自动纠正拼写错误
    • 为了压缩这个值并加快显示速度