Skip to content

$_GET 与 $_POST — 请求参数

概述

$_GET$_POST 是 PHP 中用于接收 HTTP 请求参数的两个最常用的超全局变量。$_GET 获取 URL 查询字符串中的参数,$_POST 获取 HTTP 请求体中 application/x-www-form-urlencodedmultipart/form-data 格式的参数。理解它们之间的区别、正确的使用方式和安全处理方法,是 PHP Web 开发的核心基础。

前置知识

在阅读本节之前,你需要了解:

  • HTTP 协议基础(GET/POST 方法、请求体、URL 结构)
  • HTML 表单(<form> 标签及 method 属性)
  • URL 编码概念
  • XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)的基本概念

基础概念

GET 与 POST 的本质区别

特性GET ($_GET)POST ($_POST)
数据来源URL 查询字符串(?key=valueHTTP 请求体
数据位置URL 中可见请求体中不可见
数据大小受 URL 长度限制(通常 2048 字符)无硬性限制(受 post_max_size 控制)
缓存可被浏览器缓存不会被缓存
书签可收藏为书签不可收藏
幂等性幂等非幂等
数据类型仅文本支持文件上传
安全性参数暴露在 URL 中参数在请求体中
Content-Type无要求application/x-www-form-urlencodedmultipart/form-data
编码application/x-www-form-urlencodedapplication/x-www-form-urlencoded

数据流向图

客户端浏览器

    ├── GET 请求 ──────────────────────────────────────┐
    │   URL: /search.php?q=php&sort=date              │
    │   └─→ PHP 解析查询字符串 → $_GET['q'] = 'php'   │
    │                              $_GET['sort'] = 'date'
    │                                                   │
    ├── POST 请求 (表单) ───────────────────────────────┤
    │   Content-Type: application/x-www-form-urlencoded│
    │   Body: username=alice&password=secret            │
    │   └─→ PHP 解析请求体 → $_POST['username']='alice' │
    │                        $_POST['password']='secret'│
    │                                                   │
    └── POST 请求 (JSON) ─────────────────────────────┘
        Content-Type: application/json
        Body: {"name":"alice"}
        └─→ $_POST 为空!需用 php://input 读取

语法与代码

$_GET 基本使用

php
<?php
declare(strict_types=1);

// URL: /search.php?keyword=PHP+8&category=backend&page=2

// 获取单个参数
$keyword  = $_GET['keyword'] ?? '';   // 'PHP 8'(+ 号被解码为空格)
$category = $_GET['category'] ?? 'all';
$page     = $_GET['page'] ?? 1;

// 类型安全的获取方式
$safePage = (int)($_GET['page'] ?? 1);

echo "关键词: {$keyword}";
echo "分类: {$category}";
echo "页码: {$safePage}";

$_POST 基本使用

php
<?php
declare(strict_types=1);

// HTML 表单提交 (method="post")
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'] ?? '';
    $email    = $_POST['email'] ?? '';
    $age      = (int)($_POST['age'] ?? 0);

    // 验证
    if ($username === '' || $email === '') {
        die('用户名和邮箱不能为空');
    }
}

URL 编码与解码

php
<?php
declare(strict_types=1);

// URL 编码函数
// urlencode() — 将空格编码为 +
// rawurlencode() — 将空格编码为 %20(推荐用于 URL 路径)

$original = 'Hello World PHP/8.1!';

// 编码
$encoded      = urlencode($original);      // Hello+World+PHP%2F8.1%21
$rawEncoded   = rawurlencode($original);   // Hello%20World%20PHP%2F8.1%21

// 解码
echo urldecode($encoded);       // Hello World PHP/8.1!
echo rawurldecode($rawEncoded); // Hello World PHP/8.1!

// 实际应用:构建带参数的 URL
function buildUrl(string $baseUrl, array $params): string
{
    $queryString = http_build_query($params);
    return $baseUrl . '?' . $queryString;
}

$url = buildUrl('https://example.com/search', [
    'q'    => 'PHP 教程',
    'page' => 1,
    'lang' => 'zh-CN',
]);
// https://example.com/search?q=PHP+%E6%95%99%E7%A8%8B&page=1&lang=zh-CN

使用 filter_input 安全获取参数

php
<?php
declare(strict_types=1);

// 使用 filter_input 代替直接访问 $_GET / $_POST
// filter_input() 不会触发 undefined index 警告

// 获取 GET 参数
$page    = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT, [
    'options' => ['default' => 1, 'min_range' => 1, 'max_range' => 100],
]);

$email   = filter_input(INPUT_GET, 'email', FILTER_VALIDATE_EMAIL);
$url     = filter_input(INPUT_GET, 'url', FILTER_VALIDATE_URL);

// 获取 POST 参数
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_SPECIAL_CHARS);
$age      = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 0, 'max_range' => 150],
]);
$website  = filter_input(INPUT_POST, 'website', FILTER_VALIDATE_URL);

// 获取多个参数
$inputs = filter_input_array(INPUT_POST, [
    'username' => FILTER_SANITIZE_SPECIAL_CHARS,
    'email'    => FILTER_VALIDATE_EMAIL,
    'age'      => [
        'filter'  => FILTER_VALIDATE_INT,
        'options' => ['min_range' => 1, 'max_range' => 120],
    ],
    'bio'      => [
        'filter'  => FILTER_SANITIZE_STRING,
        'flags'   => FILTER_FLAG_STRIP_LOW,
    ],
]);

详细说明

URL 编码规则

URL 中只允许使用以下字符:

  • 大小写字母:A-Z a-z
  • 数字:0-9
  • 特殊字符:- . _ ~

所有其他字符都必须进行百分号编码(%XX,其中 XX 为十六进制值):

原始字符urlencode()rawurlencode()
空格+%20
+%2B%2B
/%2F%2F
?%3F%3F
=%3D%3D
&%26%26
%%25%25
中文%XX%XX%XX(UTF-8 逐字节)%XX%XX%XX

urlencode vs rawurlencode

  • urlencode() 将空格编码为 +,用于查询字符串(?key=value 部分)
  • rawurlencode() 将空格编码为 %20,用于 URL 路径部分(RFC 3986 标准)
  • http_build_query() 内部使用 urlencode()

$_POST 与 JSON 请求体

当客户端发送 JSON 格式的请求体(Content-Type: application/json)时,$_POST 将为空数组:

php
<?php
declare(strict_types=1);

// 客户端发送 JSON: {"name": "Alice", "age": 30}
// Content-Type: application/json

// $_POST 将为空
var_dump($_POST); // array(0) { }

// 正确方式:从 php://input 读取并解析
$rawInput = file_get_contents('php://input');

if (!empty($rawInput)) {
    $data = json_decode($rawInput, true);
    if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
        $name = $data['name'] ?? '';
        $age  = (int)($data['age'] ?? 0);
    }
}

多值参数处理

php
<?php
declare(strict_types=1);

// URL: /search.php?tag=php&tag=web&tag=backend

// PHP 会自动将同名参数解析为数组
$tags = $_GET['tag'] ?? [];

if (is_array($tags)) {
    foreach ($tags as $tag) {
        echo "标签: " . htmlspecialchars($tag) . PHP_EOL;
    }
}
// 输出:
// 标签: php
// 标签: web
// 标签: backend

// HTML 表单中的多选框
// <input type="checkbox" name="hobbies[]" value="reading">
// <input type="checkbox" name="hobbies[]" value="coding">
// <input type="checkbox" name="hobbies[]" value="music">
if (isset($_POST['hobbies']) && is_array($_POST['hobbies'])) {
    $hobbies = array_map('trim', $_POST['hobbies']);
    echo '爱好: ' . implode(', ', $hobbies);
}

实战示例

安全的表单处理

php
<?php
declare(strict_types=1);

/**
 * 完整的表单处理示例:用户注册
 */
class RegistrationHandler
{
    private array $errors = [];
    private array $data = [];

    public function handle(): array
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            return ['success' => false, 'error' => '仅支持 POST 请求'];
        }

        // CSRF Token 验证(简化示例)
        $sessionToken = $_SESSION['csrf_token'] ?? '';
        $submittedToken = $_POST['csrf_token'] ?? '';
        if (!hash_equals($sessionToken, $submittedToken)) {
            return ['success' => false, 'error' => 'CSRF 令牌验证失败'];
        }

        // 获取并过滤输入
        $this->data = [
            'username' => trim($_POST['username'] ?? ''),
            'email'    => trim($_POST['email'] ?? ''),
            'password' => $_POST['password'] ?? '',
            'age'      => (int)($_POST['age'] ?? 0),
        ];

        // 验证
        $this->validate();

        if (!empty($this->errors)) {
            return ['success' => false, 'errors' => $this->errors];
        }

        // 保存用户(模拟)
        // $this->saveUser();

        return ['success' => true, 'data' => $this->data];
    }

    private function validate(): void
    {
        // 用户名验证
        if ($this->data['username'] === '') {
            $this->errors['username'] = '用户名不能为空';
        } elseif (mb_strlen($this->data['username']) < 3) {
            $this->errors['username'] = '用户名至少 3 个字符';
        } elseif (mb_strlen($this->data['username']) > 50) {
            $this->errors['username'] = '用户名不能超过 50 个字符';
        } elseif (!preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]+$/u', $this->data['username'])) {
            $this->errors['username'] = '用户名包含非法字符';
        }

        // 邮箱验证
        if ($this->data['email'] === '') {
            $this->errors['email'] = '邮箱不能为空';
        } elseif (!filter_var($this->data['email'], FILTER_VALIDATE_EMAIL)) {
            $this->errors['email'] = '邮箱格式不正确';
        }

        // 密码验证
        if ($this->data['password'] === '') {
            $this->errors['password'] = '密码不能为空';
        } elseif (strlen($this->data['password']) < 8) {
            $this->errors['password'] = '密码至少 8 个字符';
        }

        // 年龄验证
        if ($this->data['age'] < 1 || $this->data['age'] > 150) {
            $this->errors['age'] = '请输入有效的年龄';
        }
    }
}

$handler = new RegistrationHandler();
$result  = $handler->handle();

if ($result['success']) {
    echo '注册成功!';
} else {
    echo '注册失败:' . ($result['errors']['username'] ?? $result['error'] ?? '未知错误');
}

分页参数处理

php
<?php
declare(strict_types=1);

/**
 * 安全的分页参数处理
 */
class Pagination
{
    private readonly int $page;
    private readonly int $perPage;
    private readonly int $total;
    private readonly int $totalPages;

    public function __construct(
        int $total,
        int $perPage = 20,
        ?int $page = null
    ) {
        $this->total  = max(0, $total);
        $this->perPage = max(1, min(100, $perPage));

        // 从 $_GET 获取页码,或使用传入值
        if ($page === null) {
            $page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
        }

        $this->totalPages = (int)ceil($this->total / $this->perPage);
        $this->page = max(1, min($this->totalPages, $page));
    }

    public function getPage(): int
    {
        return $this->page;
    }

    public function getOffset(): int
    {
        return ($this->page - 1) * $this->perPage;
    }

    public function getPerPage(): int
    {
        return $this->perPage;
    }

    public function getTotalPages(): int
    {
        return $this->totalPages;
    }

    public function hasNextPage(): bool
    {
        return $this->page < $this->totalPages;
    }

    public function hasPrevPage(): bool
    {
        return $this->page > 1;
    }
}

// 使用示例
$pagination = new Pagination(total: 156, perPage: 20);
echo "当前页: {$pagination->getPage()}";
echo "偏移量: {$pagination->getOffset()}";
echo "总页数: {$pagination->getTotalPages()}";

注意事项

1. 永远不要信任用户输入

php
<?php
declare(strict_types=1);

// 错误:直接使用用户输入
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = {$id}"; // SQL 注入风险!

// 正确:使用预处理语句
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $_GET['id'] ?? 0]);

2. $_POST 的大小限制

ini
; php.ini 配置
post_max_size = 8M        ; POST 请求体最大大小
upload_max_filesize = 2M  ; 单个文件上传最大大小
max_input_vars = 1000     ; 最大输入变量数

3. 表单提交的字符编码

确保 HTML 表单的字符编码与 PHP 处理时一致:

html
<form method="post" accept-charset="UTF-8">
    <input type="text" name="name">
    <button type="submit">提交</button>
</form>

4. $_GET 的 URL 长度限制

不同浏览器和服务器的 URL 长度限制不同:

  • IE:2083 字符
  • Chrome:约 32,000 字符
  • Firefox:约 65,000 字符
  • Nginx 默认:4096 字符(large_client_header_buffers
  • Apache:约 8,000 字符

最佳实践

  1. 使用 filter_input 替代直接访问filter_input()$_GET['key'] 更安全
  2. 始终设置默认值:使用 ?? 运算符防止 undefined index
  3. 类型转换:对数值参数使用 FILTER_VALIDATE_INT(int) 强制转换
  4. HTML 输出转义:输出到 HTML 时使用 htmlspecialchars()
  5. CSRF 保护:所有 POST 表单都应包含 CSRF Token
  6. 输入验证:在服务端对每个参数进行验证,不依赖前端验证
  7. 使用 http_build_query:构建 URL 查询字符串时使用标准函数
php
<?php
declare(strict_types=1);

// 最佳实践模板:处理 GET 请求
function handleSearchRequest(): array
{
    $keyword = filter_input(INPUT_GET, 'keyword', FILTER_SANITIZE_SPECIAL_CHARS);
    $page    = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT, [
        'options' => ['default' => 1, 'min_range' => 1],
    ]);
    $limit   = filter_input(INPUT_GET, 'limit', FILTER_VALIDATE_INT, [
        'options' => ['default' => 20, 'min_range' => 1, 'max_range' => 100],
    ]);
    $sort    = filter_input(INPUT_GET, 'sort', [
        'filter'  => FILTER_CALLBACK,
        'options' => function (string $value): string {
            $allowed = ['date', 'name', 'price'];
            return in_array($value, $allowed, true) ? $value : 'date';
        },
    ]);

    if ($keyword === null || $keyword === '') {
        return ['error' => '请输入搜索关键词'];
    }

    return [
        'keyword' => $keyword,
        'page'    => $page,
        'limit'   => $limit,
        'sort'    => $sort,
        'offset'  => ($page - 1) * $limit,
    ];
}

下一节

下一节将详细介绍 $_FILES 超全局变量,了解 PHP 文件上传的机制、上传信息获取和多文件上传处理。

参考链接