PHP 基础、日期与表单请求
本文目录
原始发布日期保留为 2018-07-06。本文于 2026-10-19 按 PHP 8.x 重整,保留数组、日期、文件复用和表单处理这些学习主题,并替换旧的密码 GET 示例、直接转储请求数据和未转义的
PHP_SELF。
数组键、空值与 falsey 值
PHP 数组既可以像列表一样使用整数键,也可以使用字符串键。检查一个值时,先想清楚要判断的是“键存在”“值不是 null”还是“值不为空”,它们不是同一件事。
| 检查方式 | 适用问题 | 0、''、false | null |
|---|---|---|---|
isset($array['key']) | 键存在且值不是 null | 返回 true | 返回 false |
array_key_exists('key', $array) | 键是否存在 | 返回 true | 即使值为 null 也返回 true |
empty($value) | 值是否缺失或 falsey | 都会被视为空 | 返回 true |
<?php
$profile = [
'nickname' => '',
'age' => 0,
'optional' => null,
];
var_dump(isset($profile['nickname'])); // true
var_dump(empty($profile['nickname'])); // true
var_dump(isset($profile['optional'])); // false
var_dump(array_key_exists('optional', $profile)); // true
// 外部请求数据可能缺少键,也可能把值提交成 null 或其他类型。
$displayName = $profile['nickname'] ?? '访客';
?>
empty() 常适合判断允许 0、"0"、空字符串都算“没有内容”的业务字段;如果 0 是合法值,不要用它代替类型与范围校验。读取数组前可用 ?? 提供默认值,接收用户输入后仍要单独验证类型和业务规则。
日期、时区与可预测格式
同一时间戳在不同时区会显示成不同的本地时间。服务端应明确使用的时区;跨服务存储时间时通常统一保存 UTC,再按用户时区格式化展示。
<?php
date_default_timezone_set('Asia/Shanghai');
$now = new DateTimeImmutable('now', new DateTimeZone('Asia/Shanghai'));
echo $now->format('Y-m-d H:i:s');
$input = '2026-10-19 09:30';
$meeting = DateTimeImmutable::createFromFormat(
'!Y-m-d H:i',
$input,
new DateTimeZone('Asia/Shanghai'),
);
$parseErrors = DateTimeImmutable::getLastErrors();
if (
$meeting === false
|| (
$parseErrors !== false
&& ($parseErrors['warning_count'] > 0 || $parseErrors['error_count'] > 0)
)
|| $meeting->format('Y-m-d H:i') !== $input
) {
throw new InvalidArgumentException('日期格式不正确。');
}
echo $meeting->format(DateTimeInterface::ATOM);
?>
格式标记区分大小写,例如 m 是月份、i 是分钟。createFromFormat() 可能把超出范围的月份、日期或时间自动滚动到后续单位,并返回一个对象;因此还要检查解析警告,并把格式化结果与原输入往返比较。getLastErrors() 在 PHP 8.2 及更高版本无错误时返回 false,较早 PHP 8 版本则返回计数为零的数组。不能因为 strtotime() 返回了时间戳,就认为输入符合业务日期要求。需要“当前时间”作为测试输入时,可把时钟封装或注入,让测试不依赖真实系统时间。
require、include 与文件路径
require适用于缺少后就无法继续运行的必需文件;载入失败会终止当前脚本。include适用于可选模板;载入失败会产生警告,后续语句仍可能继续执行。require_once和include_once会避免同一文件在一次请求中被重复载入。- 服务端代码应使用
__DIR__拼接固定的项目内路径;不要把用户提交的路径直接传给这些语句。
<?php
require_once __DIR__ . '/bootstrap.php';
// 页面布局是可选的展示片段时,可以使用 include。
include __DIR__ . '/partials/sidebar.php';
?>
配置文件和模板文件有不同的失败策略:数据库连接、认证配置等核心依赖缺失时应尽早失败;可选组件则应有明确的降级界面。错误详情记录在受控日志中,不能把文件路径、堆栈或密钥输出给访客。
表单提交:GET 与 POST
GET 参数位于 URL,可能进入浏览器历史、代理日志、分析系统和 Referer;不要用 GET 传密码、验证码、访问令牌或执行有副作用的操作。POST 把数据放在请求体里,但并不加密,也不自动防止 CSRF;敏感请求仍必须使用 HTTPS、鉴权、CSRF 防护和服务端校验。
$_GET 读取查询参数;$_POST 读取 application/x-www-form-urlencoded 或 multipart/form-data 表单字段;JSON 请求体要从 php://input 按 JSON 解析。避免用 $_REQUEST 隐式混合来源,因为同名值可能来自不同位置,代码难以判断信任边界。
<form method="post" action="" autocomplete="on">
<label for="username">用户名</label>
<input id="username" name="username" autocomplete="username" required>
<label for="password">密码</label>
<input
id="password"
name="password"
type="password"
autocomplete="current-password"
required
>
<button type="submit" name="action" value="login">登录</button>
</form>
密码字段必须使用 type="password",但这只是减少屏幕上的直接暴露,不是加密。登录流程还必须校验 CSRF token、限速,并使用 password_verify() 与数据库中的密码哈希比对;不要把密码放进 URL、HTML 注释、日志或错误信息。注册、登录的完整安全示例见表单提交与安全注册。
在当前页面处理请求
把处理逻辑放在任何 HTML 输出之前,才能正确设置响应状态、Cookie 或重定向。固定使用当前路由即可;不要直接把 $_SERVER['PHP_SELF'] 原样塞进 action,因为它受请求环境影响且可能包含用户输入。
<?php
$message = '';
$username = '';
$requestMethod = $_SERVER['REQUEST_METHOD'] ?? '';
if ($requestMethod === 'POST') {
$submittedUsername = $_POST['username'] ?? null;
$submittedPassword = $_POST['password'] ?? null;
if (!is_string($submittedUsername) || !is_string($submittedPassword)) {
$message = '请检查表单内容。';
} else {
$username = trim($submittedUsername);
if ($username === '' || strlen($username) > 128) {
$message = '用户名格式不符合要求。';
} elseif ($submittedPassword === '') {
$message = '请输入密码。';
} else {
// 在这里调用认证服务;绝不能把原始密码直接写入文件或数据库。
$message = '表单格式有效,接下来由认证服务验证账号。';
}
}
}
function escapeHtml(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
POST 字段仍是不可信输入:字段可能不存在,也可能被构造成数组。strlen() 计算字节数,涉及中文字符数时应选择明确的 UTF-8 处理方式和相应扩展。真实注册/登录不能停留在格式检查,还要执行 CSRF 校验、账号策略、数据库唯一约束、密码哈希验证和安全的错误映射。
<?php if ($message !== ''): ?>
<p role="status"><?= escapeHtml($message) ?></p>
<?php endif; ?>
<label for="username">用户名</label>
<input
id="username"
name="username"
value="<?= escapeHtml($username) ?>"
autocomplete="username"
>
转义要发生在输出时,并按输出上下文选择编码方式。htmlspecialchars() 适用于 HTML 文本和引号包围的普通属性值,不是 SQL、JavaScript、CSS 或 URL 的通用转义函数;数据库查询用参数化语句,URL 要按 URL 规则验证和编码。
请求调试与生产错误
开发阶段应打开错误报告,让问题尽早暴露;生产环境关闭面向访客的错误详情,但把异常写入权限受控、去除密码和令牌的日志。不要用 var_dump($_POST) 或 var_dump($_REQUEST) 调试认证请求,它可能把用户隐私写进页面、日志或截图。
查看请求时只读取完成任务所需的字段,并把调试输出限制在本地开发环境。收到 POST 不代表一定是表单提交,也不代表可以信任其中任何字段;先检查请求方法、Content-Type、字段形状和权限。
原始流程图
这张图保留为 GET/POST 表单请求的概念示意,不是完整的生产认证流程:

参考资料
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。