$_GET 与 $_POST — 请求参数
概述
$_GET 和 $_POST 是 PHP 中用于接收 HTTP 请求参数的两个最常用的超全局变量。$_GET 获取 URL 查询字符串中的参数,$_POST 获取 HTTP 请求体中 application/x-www-form-urlencoded 或 multipart/form-data 格式的参数。理解它们之间的区别、正确的使用方式和安全处理方法,是 PHP Web 开发的核心基础。
前置知识
在阅读本节之前,你需要了解:
- HTTP 协议基础(GET/POST 方法、请求体、URL 结构)
- HTML 表单(
<form>标签及method属性) - URL 编码概念
- XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)的基本概念
基础概念
GET 与 POST 的本质区别
| 特性 | GET ($_GET) | POST ($_POST) |
|---|---|---|
| 数据来源 | URL 查询字符串(?key=value) | HTTP 请求体 |
| 数据位置 | URL 中可见 | 请求体中不可见 |
| 数据大小 | 受 URL 长度限制(通常 2048 字符) | 无硬性限制(受 post_max_size 控制) |
| 缓存 | 可被浏览器缓存 | 不会被缓存 |
| 书签 | 可收藏为书签 | 不可收藏 |
| 幂等性 | 幂等 | 非幂等 |
| 数据类型 | 仅文本 | 支持文件上传 |
| 安全性 | 参数暴露在 URL 中 | 参数在请求体中 |
Content-Type | 无要求 | application/x-www-form-urlencoded 或 multipart/form-data |
| 编码 | application/x-www-form-urlencoded | application/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-Za-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 字符
最佳实践
- 使用 filter_input 替代直接访问:
filter_input()比$_GET['key']更安全 - 始终设置默认值:使用
??运算符防止 undefined index - 类型转换:对数值参数使用
FILTER_VALIDATE_INT或(int)强制转换 - HTML 输出转义:输出到 HTML 时使用
htmlspecialchars() - CSRF 保护:所有 POST 表单都应包含 CSRF Token
- 输入验证:在服务端对每个参数进行验证,不依赖前端验证
- 使用 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 文件上传的机制、上传信息获取和多文件上传处理。