Skip to content

外部变量

PHP 作为一种服务端脚本语言,最核心的功能之一就是处理来自 HTTP 请求的外部数据。PHP 通过超级全局变量数组 $_GET$_POST$_COOKIE 等来自动将表单数据、URL 参数和 Cookie 等外部数据提供给脚本使用。

前置知识

基础概念

当浏览器向 PHP 脚本发送 HTTP 请求时,请求中的数据会自动填充到 PHP 的超级全局变量中。这些变量在脚本的任何位置都可直接访问,无需 global 声明。

外部变量的来源

超级全局变量数据来源示例
$_GETURL 查询字符串?id=1&name=test
$_POSTHTTP POST 请求体表单提交
$_COOKIEHTTP Cookie 头浏览器存储的 Cookie
$_REQUESTGET + POST + COOKIE 的合集综合访问
$_FILESHTTP 文件上传上传的文件信息
$_SERVER服务器和执行环境HTTP 头、路径信息

GET 和 POST 数据

HTML 表单与 PHP 处理

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
<?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
<?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
<?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
<?php
declare(strict_types=1);

// $varname.ext 会被解析为 $varname . "ext"
// 所以 PHP 自动将点替换为下划线

$_REQUEST 变量

$_REQUEST$_GET$_POST$_COOKIE 的合集。由于包含多个来源的数据,使用时需要注意同名参数的优先级:

php
<?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)。

Cookie 通过 setcookie() 函数设置,通过 $_COOKIE 超级全局变量访问:

php
<?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
<?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
<?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
<?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
<?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
<?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
<?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 中被移除。

最佳实践

  1. 明确指定数据来源:使用 $_GET$_POST,避免 $_REQUEST
  2. 始终验证和过滤输入:使用 filter_var() 或自定义验证
  3. 输出时转义:使用 htmlspecialchars() 防止 XSS
  4. 使用类型转换:将外部数据转换为期望的类型
  5. 参数化 SQL 查询:使用 PDO 预处理语句防止 SQL 注入
  6. 设置 Cookie 使用安全选项httponlysecuresamesite
  7. 使用 CSRF Token:表单提交时验证 CSRF Token

下一节

掌握了外部变量的处理方式后,接下来进入常量的学习。首先是常量的语法,包括 define()const 两种定义方式的区别与选择。请阅读 常量语法

参考链接