超全局变量
OxPHP 会在你的脚本执行之前填充好每一个标准 PHP 超全局变量,与 PHP 开发者在传统服务器环境下所期望的行为完全一致。这些值从你代码的第一行起就可用,无需任何初始化。
$_SERVER
OxPHP 依照 CGI/1.1 规范,从传入的 HTTP 请求构建 $_SERVER。它会先导入进程的环境变量,再设置 CGI 变量,因此请求相关的值总是会覆盖任何与之冲突的环境变量键。
标准变量
| 变量 | 说明 | 示例 |
|---|---|---|
SCRIPT_FILENAME |
正在执行的 PHP 脚本的绝对文件系统路径 | /var/www/html/public/index.php |
DOCUMENT_ROOT |
通过 DOCUMENT_ROOT 环境变量配置的 Web 根目录 |
/var/www/html/public |
SERVER_SOFTWARE |
服务器标识(包含正在运行的 OxPHP 版本) | OxPHP/0.11.0 |
SERVER_PROTOCOL |
协商得到的 HTTP 协议版本 | HTTP/2 |
REQUEST_METHOD |
HTTP 方法 | GET |
REQUEST_URI |
包含查询字符串的完整 URI | /app?page=2 |
SCRIPT_NAME |
所执行脚本相对于 DOCUMENT_ROOT 的路径——在 Framework 模式下是前端控制器,而非请求 URI |
/index.php |
DOCUMENT_URI |
SCRIPT_NAME 的别名,用于兼容 nginx/PHP-FPM |
/index.php |
PHP_SELF |
存在 PATH_INFO 时为 SCRIPT_NAME 加上 PATH_INFO,否则等于 SCRIPT_NAME |
/index.php/user/42 |
QUERY_STRING |
URI 的查询部分(不存在时为空字符串) | page=2 |
SERVER_NAME |
来自 Host 请求头的主机名 |
example.com |
SERVER_PORT |
来自 Host 请求头的端口 |
8080 |
REMOTE_ADDR |
客户端 IP 地址 | 172.17.0.1 |
REMOTE_PORT |
客户端端口号 | 54321 |
HTTPS |
连接使用 TLS 时设为 "on",否则不存在 |
on |
REQUEST_SCHEME |
TLS 连接为 "https",否则为 "http" |
https |
CONTENT_TYPE |
Content-Type 请求头的值(无 HTTP_ 前缀) |
application/json |
CONTENT_LENGTH |
Content-Length 请求头的值(无 HTTP_ 前缀) |
128 |
REQUEST_TIME |
请求开始时的 Unix 时间戳(整数) | 1738800000 |
REQUEST_TIME_FLOAT |
精确到微秒的 Unix 时间戳 | 1738800000.123456 |
GATEWAY_INTERFACE |
CGI 版本字符串 | CGI/1.1 |
当 Host 请求头缺失时,SERVER_NAME 默认为 localhost,SERVER_PORT 默认为 80(TLS 下为 443)。
HTTP 请求头
所有 HTTP 请求头都会以 HTTP_ 前缀添加到 $_SERVER 中。请求头名称会按照 CGI/1.1 约定转换为大写,并将短横线替换为下划线:
Accept: text/html -> HTTP_ACCEPT
X-Forwarded-For: 1.2.3.4 -> HTTP_X_FORWARDED_FOR
Authorization: Bearer abc -> HTTP_AUTHORIZATION
Cookie: session=xyz -> HTTP_COOKIEContent-Type 和 Content-Length 不带 HTTP_ 前缀——分别为 CONTENT_TYPE 和 CONTENT_LENGTH——这是 CGI 规范的要求。
反向代理之后
当配置了 TRUSTED_PROXIES 且请求的对端处于可信集合中时,OxPHP 会从转发头(X-Forwarded-* 或 RFC 7239 Forwarded)重写以下 $_SERVER 键:
| 变量 | 对端可信时的值 | 其他情况下的值 |
|---|---|---|
REMOTE_ADDR |
来自 X-Forwarded-For / Forwarded 中最右侧的不可信地址 |
直连对端 IP |
REMOTE_PORT |
来自 Forwarded: for=ip:port 的客户端源端口,否则为 0 |
直连对端端口 |
HTTPS |
当 X-Forwarded-Proto: https 时为 "on" |
仅当对端连接为 TLS 时设置 |
REQUEST_SCHEME |
来自 X-Forwarded-Proto 的 "https" / "http" |
基于实际 TLS 状态 |
SERVER_NAME |
X-Forwarded-Host 的主机部分 |
Host 请求头的主机部分 |
SERVER_PORT |
X-Forwarded-Port,否则取 X-Forwarded-Host 的端口部分,否则按 scheme 取 443/80 |
Host 的端口部分,或 443/80 |
原始的 HTTP_X_FORWARDED_FOR、HTTP_X_FORWARDED_PROTO、HTTP_X_FORWARDED_HOST、HTTP_X_FORWARDED_PORT 和 HTTP_FORWARDED 键会原封不动地保留在 $_SERVER 中——重写后的值和原始请求头都可以获取。
在可信代理之后,REMOTE_PORT 为 "0",除非代理发送了 RFC 7239 Forwarded: for=ip:port——X-Forwarded-For 和最右侧不可信地址的选取都不携带客户端源端口,因此这个合成值会被置零而不是靠猜测填充。
当未设置 TRUSTED_PROXIES 时,不会发生任何重写,REMOTE_ADDR 始终是直连对端——通常是你的负载均衡器,而非最终客户端。手动解析 X-Forwarded-For 容易出错(最左侧还是最右侧、缺少 CIDR 信任校验);建议配置 TRUSTED_PROXIES。信任算法和配置语法请参阅 可信代理。
追踪上下文变量
启用分布式追踪后,OxPHP 会向 $_SERVER 添加追踪上下文变量:
| 变量 | 说明 | 示例 |
|---|---|---|
OXPHP_TRACE_ID |
当前请求的 W3C 追踪 ID | 4bf92f3577b34da6a3ce929d0e0e4736 |
OXPHP_SPAN_ID |
OxPHP 服务器 span 的 Span ID | 00f067aa0ba902b7 |
OXPHP_PARENT_SPAN_ID |
来自上游服务的父 span ID(若为根则为空) | b9c7c989f97918e1 |
只有在收到有效的 traceparent 请求头,或 OxPHP 生成了一个新的 trace 时,这些变量才会存在。如果未配置追踪,这些键将不存在。
与 PHP-FPM 的差异
以下变量的行为与标准 PHP-FPM 环境不同:
| 变量 | 行为 |
|---|---|
SERVER_ADDR |
不设置。OxPHP 不填充本地服务器 IP 地址。 |
PATH_INFO |
自动设置——参见下文 PATH_INFO 行为。 |
PATH_TRANSLATED |
不设置。 |
PHP_AUTH_USER / PHP_AUTH_PW / AUTH_TYPE |
不从 Authorization 请求头中提取。请直接读取 $_SERVER['HTTP_AUTHORIZATION']。 |
REDIRECT_STATUS |
不设置。OxPHP 不使用内部重定向机制。 |
示例
<?php
$method = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
$ip = $_SERVER['REMOTE_ADDR'];
$host = $_SERVER['SERVER_NAME'];
$scheme = $_SERVER['REQUEST_SCHEME']; // "http" or "https"
// Read a custom header
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
// REMOTE_ADDR is already the real client IP when TRUSTED_PROXIES is configured.
// Without it, REMOTE_ADDR is the direct peer (usually a load balancer).
$clientIp = $_SERVER['REMOTE_ADDR'];
// Check TLS without checking the port
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
// Secure connection
}PATH_INFO 行为
$_SERVER['PATH_INFO'] 会根据当前生效的路由模式自动填充。这里没有功能开关——此前的 SPLIT_PATH_INFO_ENABLED 环境变量已被移除。
| 路由模式 | 何时设置 | 值 |
|---|---|---|
Traditional(未设置 ENTRY_FILE) |
仅当 URI 包含 .php/ 且脚本前缀在磁盘上存在时 |
脚本段之后的尾部 |
Framework(ENTRY_FILE=index.php) |
仅当请求显式指定入口文件并带有尾部段时(/index.php/extra) |
入口文件之后的尾部,例如 /news |
SPA(ENTRY_FILE=index.html) |
永不设置——PHP 只为精确匹配的 .php 文件运行,无 PATH_INFO |
— |
SCRIPT_NAME 始终标识被执行的脚本(相对于文档根目录解析出的文件),因此在正常路由下,只有当 SCRIPT_NAME 是请求路径的字面前缀时,PATH_INFO 才存在。当请求被重写到一个它并未指名的前端控制器时(应用路由、目录索引、静态文件未命中回退),PATH_INFO 缺失,原始路径存在于 REQUEST_URI 中。(PHP_DENY_PATHS 回退是一个有意的例外:它会将 PATH_INFO 设为原始经清理的 URI,以便回退脚本可以据此路由。)
Traditional 模式示例
OxPHP 从左到右扫描 URI,寻找第一个对应磁盘上真实文件的 .php 段。其后的所有内容都会成为 PATH_INFO:
| 请求 URI | 磁盘上的文件 | SCRIPT_NAME |
PATH_INFO |
PHP_SELF |
|---|---|---|---|---|
/app.php/user/42 |
app.php 存在 |
/app.php |
/user/42 |
/app.php/user/42 |
/index.php/api/v2/users |
index.php 存在 |
/index.php |
/api/v2/users |
/index.php/api/v2/users |
/app.php |
app.php 存在 |
/app.php |
(不存在) | /app.php |
/missing.php/foo |
文件未找到 | 回退到 /index.php |
— | 取决于回退情况 |
Framework 模式示例
每个非静态请求都会被重写到 index.php 上。只有当请求显式指定入口文件并带有尾部段时,PATH_INFO 才会被设置;对于应用路由,原始路径从 REQUEST_URI 读取。
| 请求 URI | SCRIPT_NAME |
PATH_INFO |
|---|---|---|
/api/users |
/index.php |
(不存在) |
/about.php |
/index.php |
(不存在) |
/index.php/news/local |
/index.php |
/news/local |
/index.php |
/index.php |
(不存在) |
PATH_TRANSLATED 不会被填充。它在实践中很少使用,nginx 和 PHP-FPM 默认也不设置它。
$_GET
查询字符串参数会自动从请求 URI 中解析。
<?php
// Request: GET /search?q=oxphp&page=2
$query = $_GET['q']; // "oxphp"
$page = $_GET['page']; // "2"数组语法也能如预期般工作:
<?php
// Request: GET /filter?tags[]=php&tags[]=async
$tags = $_GET['tags']; // ["php", "async"]filter_input()、filter_input_array() 和 filter_has_var() 读的不是 $_GET、$_POST 或 $_COOKIE。filter 扩展自己保存着一份已解析输入的副本,而在持久化工作进程中,这份副本会被每个请求填充——因此它也必须被每个请求交还,否则一个客户端的查询值、会话 cookie 和请求体字段,会一直可被该工作进程之后服务的每个请求读到。OxPHP 会在每个请求开始时把它交还,所以这三个函数只为发问的那个请求作答,不为任何其他请求作答。
工作进程模式增加了一条限制。如果你的请求暂停了(sleep()、一次 await、一个被钩住的调用),而在这个窗口里有另一个请求在该工作进程上运行,那么这份存储就变成了那个请求的。恢复之后的读取会得到 null,而不是别人的输入,你的请求原本那份副本到那时也已经没了。另一个请求只要在那里开始或恢复就足够了——不需要跑完。$_GET、$_POST 和 $_COOKIE 会随请求跨越挂起、不受影响,所以要么读它们,要么在挂起之前读 filter 函数。
有一个调用完全不允许暂停。传入按字段定义数组的 filter_input_array() 会对每个字段读一次该存储,因此做 I/O 的 FILTER_CALLBACK 会在其持续期间占住工作进程,而不是把它交给另一个请求;在这样的回调里显式调用 Fiber::suspend() 会抛出异常。等待仍然会发生——等待的是工作线程而不是请求——所以慢回调会表现为排在它后面所有请求的延迟,而超过 QUEUE_WAIT_TIMEOUT_MS 后那些排队的请求会被卸除。它能持续多久由回调正在与之通信的对象决定,而不是由服务器决定:流封装器在 default_socket_timeout 后放弃(默认 60 秒),mysqlnd 则会等 mysqlnd.net_read_timeout(默认长达一天)。请给这类调用一个显式超时——或者更好:等 filter_input_array() 把值返回之后,再对它运行需要访问数据库或缓存的校验。
$_POST
OxPHP 支持表单提交的两种标准内容类型:
application/x-www-form-urlencoded—— 标准 HTML 表单数据multipart/form-data—— 文件上传与表单字段的组合
<?php
// Request: POST /login
// Content-Type: application/x-www-form-urlencoded
// Body: username=admin&password=secret
$username = $_POST['username']; // "admin"
$password = $_POST['password']; // "secret"对于 JSON 或其他内容类型,请改用 php://input:
<?php
// Request: POST /api/users
// Content-Type: application/json
// Body: {"name":"Alice","email":"[email protected]"}
$data = json_decode(file_get_contents('php://input'), true);
$name = $data['name']; // "Alice"
$email = $data['email']; // "[email protected]"filter_input(INPUT_POST, …) 读取的是 filter 扩展自己那份请求体副本,而不是 $_POST——那份副本是什么、能存活多久,参见 $_GET 下的说明。
$_COOKIE
Cookie 会从 Cookie 请求头中解析。
<?php
// Request with: Cookie: session=abc123; theme=dark
$session = $_COOKIE['session']; // "abc123"
$theme = $_COOKIE['theme']; // "dark"带有 __oxp_ 前缀的 Cookie 保留给 OxPHP 内部插件使用。它们会在 Cookie 请求头到达 PHP 之前被剥离,不会出现在 $_COOKIE 中。
filter_input(INPUT_COOKIE, …) 读取的是 filter 扩展自己那份 cookie 副本,而不是 $_COOKIE——那份副本是什么、能存活多久,参见 $_GET 下的说明。
$_FILES
通过 multipart/form-data 发送的文件上传会以标准 PHP 结构填充 $_FILES 数组:
<?php
// $_FILES['avatar'] structure:
// [
// 'name' => 'photo.jpg', // Original filename sent by the client
// 'type' => 'image/jpeg', // MIME type declared by the client
// 'tmp_name' => '/tmp/phpAb12Cd', // Temporary file path on the server
// 'error' => 0, // UPLOAD_ERR_OK (0 means no error)
// 'size' => 204800, // File size in bytes
// ]
if ($_FILES['avatar']['error'] === UPLOAD_ERR_OK) {
$tmp = $_FILES['avatar']['tmp_name'];
$name = basename($_FILES['avatar']['name']);
move_uploaded_file($tmp, "/uploads/$name");
}$_REQUEST
$_REQUEST 是 $_GET、$_POST 以及(可选的)$_COOKIE 合并后的数组,由 PHP 根据 request_order INI 指令构建(默认为 "GP"——先 GET,后 POST)。合并本身完全遵循 PHP 的规则,不作改动。
<?php
// GET /form?action=preview with POST body: action=submit
$action = $_REQUEST['action']; // "submit" (POST overrides GET with default order)$_REQUEST 会为每个请求重建。PHP 通常是惰性构建它的——在提到它的脚本第一次加载时构建一次——这在持久化工作进程中意味着之后的每个请求读到的都是第一个请求的参数。OxPHP 强制重建,因此这个合并数组描述的始终是正在被服务的那个请求。
$_ENV
$_ENV 保存进程环境。在传统模式下,它的行为与 PHP-FPM 完全一致:PHP 会在每个请求上依照 variables_order 从环境重新填充它。
**工作进程模式会把它固定下来。**工作进程只引导一次,而 .env 加载器(vlucas/phpdotenv、symfony/dotenv、Laravel 的 Env)会把它们的值直接写进 $_ENV,不触碰进程环境。若按请求重新填充 $_ENV,应用的配置从第二个请求起就会被抹掉,所以在工作进程模式下,这个数组一旦存在,就会在工作进程的整个生命期内保留——你的引导代码写进去的内容,对该工作进程服务的每个请求都保持可见。
<?php
// bootstrap, before oxphp_worker()
Dotenv\Dotenv::createImmutable(__DIR__)->load(); // writes into $_ENV
oxphp_worker(function () {
echo $_ENV['DATABASE_URL']; // still there on request 10_000
});在工作进程模式下,filter_input(INPUT_ENV, …)、filter_input_array(INPUT_ENV) 和 filter_has_var(INPUT_ENV, …) 读的是进程环境。它们不读 $_ENV,所以你的引导代码写进去的值不在其中。PHP 在任何地方都是这样表现的,因为写入 $_ENV 会让这个数组拥有自己的副本,而 filter 扩展继续读引擎当初拍下的环境快照。工作进程模式加了一个细节:这份快照是 $_ENV 在该工作进程上第一次被构建时拍下的,所以之后的 putenv() 调用也不在里面。getenv() 读的是实时环境,不受影响;$_ENV 保存的是进程值加上你的引导代码添加的内容。以上只针对 INPUT_ENV:INPUT_GET、INPUT_POST 和 INPUT_COOKIE 读的是 filter 扩展内部它们自己的存储,见上文 $_GET 下的说明。
这种固定依赖 PHP 默认的 auto_globals_jit=1。在 auto_globals_jit=0 时,PHP 会在任何扩展来得及介入之前,于每个请求上从进程环境重新填充 $_ENV,.env 加载器的值在工作进程模式下将无法存活。
php://input
原始请求体可通过 php://input 流获取。这是读取 JSON 负载(请求体)、XML 或除表单提交之外任何内容类型的标准方式。
<?php
$body = file_get_contents('php://input');
$data = json_decode($body, true);php://input 可倒回(rewindable),在同一请求内可多次读取。
对于 multipart/form-data 请求,php://input 为空。这类请求请使用 $_POST 和 $_FILES。
禁用超全局变量
设置 SUPERGLOBALS_ENABLED=false 可禁用对 $_GET、$_POST、$_COOKIE、$_FILES 和 $_SERVER 的填充。禁用后,这些数组为空。请改用 HTTP 请求 API(oxphp_http_request())来访问请求数据。
SUPERGLOBALS_ENABLED=false # superglobals are empty arrays无论此设置如何,以下内容始终可用:
| 内容 | 原因 |
|---|---|
$_SESSION |
由 PHP 的会话模块管理,而非 SAPI |
php://input |
是一个流,而非超全局变量 |
header()、headers_list() 等 |
SAPI 函数,而非超全局变量 |
session_start() 及其他 session_*() 函数 |
原生 PHP 函数 |
oxphp_http_request() |
始终可用——推荐的替代方案 |
你可以在运行时检查当前设置:
if (!oxphp_superglobals_enabled()) {
$request = oxphp_http_request();
$page = $request->query('page', 1);
}另请参阅
- HTTP 请求 API —— 类型化、惰性加载的请求对象,作为超全局变量的替代方案
- PHP 函数 ——
oxphp_request_id()、oxphp_worker_id()及其他扩展函数 - 工作进程模式 —— 工作进程各次请求之间如何刷新超全局变量
- 配置参考 ——
DOCUMENT_ROOT及其他服务器配置变量