外部变量
PHP 作为一种服务端脚本语言,最核心的功能之一就是处理来自 HTTP 请求的外部数据。PHP 通过超级全局变量数组 $_GET、$_POST、$_COOKIE 等来自动将表单数据、URL 参数和 Cookie 等外部数据提供给脚本使用。
基础概念
当浏览器向 PHP 脚本发送 HTTP 请求时,请求中的数据会自动填充到 PHP 的超级全局变量中。这些变量在脚本的任何位置都可直接访问,无需 global 声明。
外部变量的来源
| 超级全局变量 | 数据来源 | 示例 |
|---|---|---|
$_GET | URL 查询字符串 | ?id=1&name=test |
$_POST | HTTP POST 请求体 | 表单提交 |
$_COOKIE | HTTP Cookie 头 | 浏览器存储的 Cookie |
$_REQUEST | GET + POST + COOKIE 的合集 | 综合访问 |
$_FILES | HTTP 文件上传 | 上传的文件信息 |
$_SERVER | 服务器和执行环境 | HTTP 头、路径信息 |
GET 和 POST 数据
HTML 表单与 PHP 处理
<?php
declare(strict_types=1);
// 假设有一个 HTML 表单
// <form action="process.php" method="POST">
// <input type="text" name="username">
// <input type="email" name="email">
// <button type="submit">Submit</button>
// </form>
// process.php 中处理 POST 数据
$username = $_POST["username"] ?? "";
$email = $_POST["email"] ?? "";
// 输出接收到的数据(实际项目中不要直接输出)
echo "Username: " . htmlspecialchars($username, ENT_QUOTES, "UTF-8") . "\n";
echo "Email: " . htmlspecialchars($email, ENT_QUOTES, "UTF-8") . "\n";GET 请求参数
GET 参数通过 URL 的查询字符串传递:
http://example.com/search.php?keyword=php&page=1&sort=desc<?php
declare(strict_types=1);
// 访问 GET 参数
$keyword = $_GET["keyword"] ?? "";
$page = (int)($_GET["page"] ?? 1);
$sort = $_GET["sort"] ?? "asc";
// 验证排序参数
$allowedSorts = ["asc", "desc"];
if (!in_array($sort, $allowedSorts, true)) {
$sort = "asc";
}
echo "Search: {$keyword}, Page: {$page}, Sort: {$sort}\n";表单数组
PHP 支持表单中使用数组语法来传递多个值:
<?php
declare(strict_types=1);
// 假设表单:
// <input name="product[name]" value="Laptop">
// <input name="product[price]" value="5999">
// <input name="tags[]" value="electronics">
// <input name="tags[]" value="computer">
// 访问嵌套表单数据
$productName = $_POST["product"]["name"] ?? "";
$productPrice = $_POST["product"]["price"] ?? 0;
$tags = $_POST["tags"] ?? [];
echo "Product: {$productName}, Price: {$productPrice}\n";
echo "Tags: " . implode(", ", $tags) . "\n";详细说明
变量名中的特殊字符处理
重要规则
PHP 会自动将外部变量名中的某些特殊字符转换为下划线 _。以下字符会被转换:
- 空格 (
) - 点 (
.) - 左方括号 (
[) - 字节值 128~159 的字符
<?php
declare(strict_types=1);
// 表单中:
// <input name="user.name" value="Alice">
// <input name="user email" value="alice@example.com">
// <input name="user[name]" value="Alice">
// PHP 接收到的键名:
// $_POST["user_name"] (点变为下划线)
// $_POST["user_email"] (空格变为下划线)
// $_POST["user"]["name"] (数组语法保留)这是因为 . 在 PHP 中是字符串连接运算符,如果保留在变量名中会导致语法歧义:
<?php
declare(strict_types=1);
// $varname.ext 会被解析为 $varname . "ext"
// 所以 PHP 自动将点替换为下划线$_REQUEST 变量
$_REQUEST 是 $_GET、$_POST 和 $_COOKIE 的合集。由于包含多个来源的数据,使用时需要注意同名参数的优先级:
<?php
declare(strict_types=1);
// $_REQUEST 包含 GET、POST 和 COOKIE 的数据
// 默认顺序由 php.ini 的 variables_order 指令决定
// 默认:variables_order = "GPC"(GET > POST > COOKIE)
// 不推荐使用 $_REQUEST,因为来源不明确
// 推荐明确使用 $_GET 或 $_POST
// 安全的写法
$id = $_GET["id"] ?? null;
$data = $_POST["data"] ?? null;
$token = $_COOKIE["token"] ?? null;安全建议
不建议使用 $_REQUEST,因为它混合了不同来源的数据,可能导致安全问题。应该明确指定数据来源($_GET 或 $_POST)。
HTTP Cookie 处理
Cookie 通过 setcookie() 函数设置,通过 $_COOKIE 超级全局变量访问:
<?php
declare(strict_types=1);
// 设置 Cookie(必须在任何输出之前)
setcookie("user_preferences", "dark_theme", [
"expires" => time() + 86400, // 24小时后过期
"path" => "/",
"domain" => "example.com",
"secure" => true,
"httponly" => true,
"samesite" => "Strict",
]);
// 读取 Cookie
$theme = $_COOKIE["user_preferences"] ?? "light";
echo "Current theme: {$theme}\n";
// 安全地删除 Cookie
setcookie("user_preferences", "", [
"expires" => time() - 3600,
"path" => "/",
]);Cookie 安全(PHP 7.3+)
从 PHP 7.3.0 起,setcookie() 支持数组选项。推荐使用数组形式,因为可以更清晰地设置每个选项。从 PHP 7.2.34 / 7.3.23 / 7.4.11 起,Cookie 名称不再进行 URL 解码,防止注入攻击。
超级全局变量的类型
重要
HTTP 是文本协议,$_GET、$_POST、$_COOKIE 中的值始终是字符串(或字符串数组)。PHP 不会自动将它们转换为其他类型。即使 URL 是 ?id=123,$_GET["id"] 也是字符串 "123",不是整数。
<?php
declare(strict_types=1);
// GET 参数始终是字符串
// URL: ?id=123&flag=true&count=0
echo gettype($_GET["id"] ?? null) . "\n"; // string
echo gettype($_GET["flag"] ?? null) . "\n"; // string
echo gettype($_GET["count"] ?? null) . "\n"; // string
// 需要手动类型转换
$id = (int)($_GET["id"] ?? 0);
$flag = filter_var($_GET["flag"] ?? "false", FILTER_VALIDATE_BOOLEAN);实战示例
安全的表单处理
<?php
declare(strict_types=1);
/**
* 处理用户注册表单
*/
function handleRegistration(): array
{
$errors = [];
$data = [];
// 用户名验证
$username = trim($_POST["username"] ?? "");
if (strlen($username) < 3 || strlen($username) > 50) {
$errors["username"] = "用户名长度必须在 3~50 个字符之间";
} elseif (!preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]+$/u', $username)) {
$errors["username"] = "用户名包含非法字符";
} else {
$data["username"] = $username;
}
// 邮箱验证
$email = trim($_POST["email"] ?? "");
if ($email === "") {
$errors["email"] = "请输入邮箱地址";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors["email"] = "邮箱格式不正确";
} else {
$data["email"] = $email;
}
// 密码验证
$password = $_POST["password"] ?? "";
if (strlen($password) < 8) {
$errors["password"] = "密码长度至少 8 个字符";
} else {
$data["password"] = password_hash($password, PASSWORD_DEFAULT);
}
return [
"success" => empty($errors),
"errors" => $errors,
"data" => $data,
];
}
// 处理 POST 请求
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$result = handleRegistration();
if ($result["success"]) {
echo "Registration successful!\n";
echo "Username: " . htmlspecialchars($result["data"]["username"]) . "\n";
} else {
echo "Errors:\n";
foreach ($result["errors"] as $field => $error) {
echo " {$field}: {$error}\n";
}
}
}过滤和验证输入
<?php
declare(strict_types=1);
/**
* 安全获取 GET 参数并过滤
*/
function getFilteredGet(string $key, int $filterType, mixed $default = null): mixed
{
$value = $_GET[$key] ?? null;
if ($value === null) {
return $default;
}
return filter_var($value, $filterType);
}
// 使用示例
$page = getFilteredGet("page", FILTER_VALIDATE_INT, ["options" => ["default" => 1, "min_range" => 1]]);
$sort = getFilteredGet("sort", FILTER_SANITIZE_SPECIAL_CHARS);
$email = getFilteredGet("email", FILTER_VALIDATE_EMAIL);
echo "Page: " . ($page["default"] ?? $page) . "\n";
/**
* 获取并验证 POST 数据
*/
function getValidatedPost(array $rules): array
{
$validated = [];
$errors = [];
foreach ($rules as $field => $rule) {
$value = $_POST[$field] ?? null;
if ($value === null && ($rule["required"] ?? false)) {
$errors[$field] = "{$field} 是必填项";
continue;
}
if ($value !== null) {
$filtered = filter_var($value, $rule["filter"] ?? FILTER_DEFAULT, $rule["options"] ?? 0);
if ($filtered === false && ($rule["filter"] ?? 0) !== FILTER_DEFAULT) {
$errors[$field] = $rule["message"] ?? "{$field} 格式不正确";
} else {
$validated[$field] = $filtered;
}
}
}
return ["data" => $validated, "errors" => $errors];
}图片提交按钮的坐标处理
当使用图片作为提交按钮时,PHP 会接收到点击坐标:
<?php
declare(strict_types=1);
// 表单:
// <input type="image" src="submit.png" name="submit" alt="Submit">
// 接收坐标(浏览器发送的是 submit.x 和 submit.y,PHP 转换为 submit_x 和 submit_y)
if (isset($_POST["submit_x"], $_POST["submit_y"])) {
$clickX = (int)$_POST["submit_x"];
$clickY = (int)$_POST["submit_y"];
echo "Clicked at position: ({$clickX}, {$clickY})\n";
}注意事项
1. 始终验证外部数据
永远不要信任来自客户端的数据,无论数据来自 GET、POST 还是 COOKIE:
<?php
declare(strict_types=1);
// 危险!直接使用外部数据
// $id = $_GET["id"];
// $sql = "SELECT * FROM users WHERE id = $id";
// 安全:使用参数化查询和类型验证
$id = (int)($_GET["id"] ?? 0);
if ($id <= 0) {
die("Invalid ID");
}2. 输出时始终转义
使用外部数据生成 HTML 时,必须进行转义防止 XSS 攻击:
<?php
declare(strict_types=1);
// 危险
// echo $_GET["name"];
// 安全
echo htmlspecialchars($_GET["name"] ?? "", ENT_QUOTES, "UTF-8");3. register_globals 已废弃
PHP 4.2.0 之前的 register_globals 选项会自动将外部变量注册为全局变量(如 $id 代替 $_GET["id"])。此选项在 PHP 5.3.0 中被废弃,PHP 5.4.0 中被移除。
最佳实践
- 明确指定数据来源:使用
$_GET或$_POST,避免$_REQUEST - 始终验证和过滤输入:使用
filter_var()或自定义验证 - 输出时转义:使用
htmlspecialchars()防止 XSS - 使用类型转换:将外部数据转换为期望的类型
- 参数化 SQL 查询:使用 PDO 预处理语句防止 SQL 注入
- 设置 Cookie 使用安全选项:
httponly、secure、samesite - 使用 CSRF Token:表单提交时验证 CSRF Token
下一节
掌握了外部变量的处理方式后,接下来进入常量的学习。首先是常量的语法,包括 define() 和 const 两种定义方式的区别与选择。请阅读 常量语法。