$_REQUEST — 请求参数合集
概述
$_REQUEST 是 PHP 中的一个超全局变量,它默认包含了 $_GET、$_POST 和 $_COOKIE 三个数组的内容。其设计初衷是为了方便开发者统一访问请求参数,而不必区分数据来源。然而,在实际开发中,$_REQUEST 的使用被广泛认为是一种不推荐的做法,存在安全性和可维护性方面的问题。
前置知识
在阅读本节之前,你需要了解:
$_GET、$_POST、$_COOKIE的基本用法- HTTP 请求方法(GET/POST)的区别
- PHP 配置指令
request_order和variables_order
基础概念
$_REQUEST 的组成
$_REQUEST 默认包含以下三个超全局变量的数据:
$_REQUEST
├── $_GET — URL 查询字符串参数
├── $_POST — POST 请求体参数
└── $_COOKIE — HTTP Cookie 数据数据来源优先级
当三个来源中存在同名的键时,优先级由 php.ini 中的 request_order 指令决定:
; 默认值
request_order = "GP"
; GP 含义:
; G = $_GET
; P = $_POST
; C = $_COOKIE历史变化
在 PHP 4.3.0 到 5.3.x 中,request_order 默认值是 "GP"。在 PHP 5.4.0+ 中,默认值仍然是 "GP",这意味着 $_REQUEST 默认不包含 $_COOKIE。如果需要包含 Cookie 数据,需要手动修改为 "GPC"。
语法与代码
$_REQUEST 基本使用
<?php
declare(strict_types=1);
// URL: /page.php?id=5
// Cookie: id=10
// POST Body: id=3
// $_GET['id'] = 5
// $_POST['id'] = 3
// $_COOKIE['id'] = 10
// $_REQUEST 取决于 request_order 配置
// request_order = "GP" 时:
echo $_REQUEST['id']; // 可能是 3 或 5(取决于优先级)
// request_order = "GPC" 时:
echo $_REQUEST['id']; // 可能是 5(GET 优先)、3(POST 优先)或 10(COOKIE 优先)$_REQUEST 与 $_GET/$_POST 的区别
<?php
declare(strict_types=1);
// 场景一:URL 中包含 id 参数
// URL: /update.php?id=100
// POST Body: id=200
// 使用 $_GET 明确获取 URL 参数
$getId = (int)($_GET['id'] ?? 0); // 100
// 使用 $_POST 明确获取 POST 参数
$postId = (int)($_POST['id'] ?? 0); // 200
// 使用 $_REQUEST —— 你不知道 id 来自哪里
$requestId = (int)($_REQUEST['id'] ?? 0); // 可能是 100 或 200
echo "GET id: {$getId}, POST id: {$postId}, REQUEST id: {$requestId}";request_order 配置的影响
<?php
declare(strict_types=1);
/**
* 检查当前 request_order 配置
*/
function checkRequestOrder(): string
{
return ini_get('request_order'); // 返回如 "GP" 或 "GPC"
}
/**
* 安全获取请求参数 —— 明确指定来源
*/
function getParam(string $key, mixed $default = null, string $source = 'request'): mixed
{
return match ($source) {
'get' => $_GET[$key] ?? $default,
'post' => $_POST[$key] ?? $default,
'cookie' => $_COOKIE[$key] ?? $default,
default => $_REQUEST[$key] ?? $default,
};
}
echo '当前 request_order: ' . checkRequestOrder();
// 明确从 GET 获取
$page = getParam('page', 1, 'get');
// 明确从 POST 获取
$name = getParam('name', '', 'post');详细说明
为什么不推荐使用 $_REQUEST
1. 安全风险:Cookie 数据污染
$_REQUEST 将 Cookie 数据与请求参数混在一起。攻击者可以通过伪造 Cookie 来覆盖预期的 GET/POST 参数:
<?php
declare(strict_types=1);
// 假设应用使用 $_REQUEST['user_id'] 获取当前用户 ID
// 攻击者在 Cookie 中设置 user_id=1(管理员 ID)
// 即使 GET/POST 中没有 user_id 参数,攻击者的 Cookie 也会生效
// 不安全
$userId = (int)($_REQUEST['user_id'] ?? 0);
// 安全:明确从会话中获取用户身份
$userId = (int)($_SESSION['user_id'] ?? 0);2. 参数来源不明确
使用 $_REQUEST 时,你无法知道一个参数到底是来自 URL、表单还是 Cookie。这会导致:
- 调试困难:难以追踪参数的真正来源
- 逻辑冲突:同名参数在不同来源中可能有不同含义
- 安全漏洞:无法对特定来源实施不同的验证策略
3. 优先级导致的意外行为
<?php
declare(strict_types=1);
// 场景:重置密码功能
// GET: /reset.php?token=abc123 (邮件中的重置链接)
// POST: token=abc123 (用户提交新密码表单)
// 如果 request_order = "GP",GET 的 token 会覆盖 POST 的 token
// 在某些框架中,这可能导致验证逻辑混乱
// 推荐:明确区分来源
$getToken = $_GET['token'] ?? '';
$postToken = $_POST['token'] ?? '';$_REQUEST 的适用场景(有限)
$_REQUEST 在极少数场景下是可接受的:
- 快速原型开发:开发阶段快速获取参数
- 统一的参数读取接口:当 GET 和 POST 参数的含义完全相同时
- 简单的搜索/过滤表单:表单可能通过 GET 或 POST 提交
<?php
declare(strict_types=1);
// 可接受的使用场景:搜索页面
// 搜索表单可能通过 GET(URL 分享)或 POST 提交
$searchTerm = filter_input(INPUT_REQUEST, 'q', FILTER_SANITIZE_SPECIAL_CHARS);
$page = filter_input(INPUT_REQUEST, 'page', FILTER_VALIDATE_INT) ?: 1;
if ($searchTerm !== null && $searchTerm !== '') {
echo "搜索: {$searchTerm}, 第 {$page} 页";
}与 filter_input 的配合
<?php
declare(strict_types=1);
// filter_input 支持 INPUT_REQUEST 常量
// 等价于从 $_REQUEST 中获取并过滤
// 获取并验证
$id = filter_input(INPUT_REQUEST, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
echo "无效的 ID";
}
// 批量获取
$inputs = filter_input_array(INPUT_REQUEST, [
'action' => FILTER_SANITIZE_SPECIAL_CHARS,
'id' => FILTER_VALIDATE_INT,
'token' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
]);实战示例
模拟 $_REQUEST 的安全替代方案
<?php
declare(strict_types=1);
/**
* 安全的请求参数管理器
* 明确区分不同来源的参数
*/
class SafeRequest
{
private array $getParams;
private array $postParams;
private array $cookieParams;
public function __construct(
array $get = null,
array $post = null,
array $cookie = null
) {
$this->getParams = $get ?? $_GET;
$this->postParams = $post ?? $_POST;
$this->cookieParams = $cookie ?? $_COOKIE;
}
/**
* 从 GET 参数获取
*/
public function query(string $key, mixed $default = null): mixed
{
return $this->getParams[$key] ?? $default;
}
/**
* 从 POST 参数获取
*/
public function post(string $key, mixed $default = null): mixed
{
return $this->postParams[$key] ?? $default;
}
/**
* 从 Cookie 获取
*/
public function cookie(string $key, mixed $default = null): mixed
{
return $this->cookieParams[$key] ?? $default;
}
/**
* 优先从 POST 获取,其次 GET(明确优先级,不使用 $_REQUEST)
*/
public function input(string $key, mixed $default = null): mixed
{
return $this->postParams[$key]
?? $this->getParams[$key]
?? $default;
}
/**
* 仅当 POST 和 GET 参数一致时才返回值(额外安全检查)
*/
public function requireConsistent(string $key, mixed $default = null): mixed
{
$getVal = $this->getParams[$key] ?? null;
$postVal = $this->postParams[$key] ?? null;
if ($getVal !== null && $postVal !== null && $getVal !== $postVal) {
trigger_error(
"参数 {$key} 在 GET 和 POST 中值不一致",
E_USER_WARNING
);
return $default;
}
return $postVal ?? $getVal ?? $default;
}
}
// 使用示例
$request = new SafeRequest();
// 明确从 GET 获取
$page = $request->query('page', 1);
// 明确从 POST 获取
$email = $request->post('email');
// POST 优先,GET 备用(语义明确)
$keyword = $request->input('keyword', '');注意事项
1. request_order 配置的重要性
不同服务器环境可能有不同的 request_order 配置,这会导致 $_REQUEST 的行为不一致。部署到不同环境时可能出现难以排查的 Bug。
2. PHP 5.4+ 的变化
PHP 5.4.0 修改了 request_order 的默认值,移除了默认包含的 Cookie。如果你从旧版本升级,需要检查代码是否依赖 $_REQUEST 中的 Cookie 数据。
3. 性能影响
$_REQUEST 是由 GET、POST、Cookie 数据合并而成的副本,在处理大量参数时会有额外的内存开销。
4. 框架的替代方案
主流 PHP 框架(Laravel、Symfony 等)都提供了自己的请求对象来替代 $_REQUEST:
<?php
// Laravel 示例
$id = $request->input('id'); // POST/GET 合并获取
$id = $request->query('id'); // 仅 GET
$id = $request->post('id'); // 仅 POST
$id = $request->cookie('name'); // 仅 Cookie最佳实践
- 不使用 $_REQUEST:始终明确使用
$_GET或$_POST获取参数 - 明确数据来源:知道每个参数来自 GET 还是 POST
- 使用 filter_input:如果需要过滤,使用
INPUT_GET或INPUT_POST - 封装请求类:在项目中封装统一的请求访问层
- 检查服务器配置:确保
request_order符合预期 - 代码审查:禁止团队成员使用
$_REQUEST
<?php
declare(strict_types=1);
// 反面示例:不推荐
function processForm(): void
{
$name = $_REQUEST['name'] ?? ''; // 来源不明
$id = $_REQUEST['id'] ?? 0; // 来源不明
}
// 正面示例:推荐
function processForm(): void
{
$name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS) ?? '';
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?? 0;
}下一节
下一节将详细介绍 $_SESSION 超全局变量,了解 PHP 会话机制的原理和使用方法。
进阶用法
调试与测试技巧
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');