各位观众老爷,早上好/中午好/晚上好! 欢迎来到今天的“WordPress 源码扒皮”特别节目。今天我们要聊聊一个藏得很深,但又非常重要的函数:wp_fix_server_vars()
。 它的作用,简单来说,就是“擦屁股”,哦不,是“标准化” $_SERVER
全局变量,让 WordPress 在各种服务器环境下都能愉快地玩耍。
我知道,一提到 $_SERVER
,大家可能就觉得头大,这玩意儿里面塞满了各种服务器信息,乱七八糟的。 不同服务器、不同配置,里面的内容还不一样,简直是 PHP 世界的百慕大。 但 WordPress 作为一个要运行在各种服务器上的 CMS,必须对这些变量进行统一处理,才能保证代码的兼容性和稳定性。
所以,wp_fix_server_vars()
就诞生了。 它的任务,就是把 $_SERVER
这个百慕大,变成一个可预测、可控制的花园。
一、 为什么要标准化 $_SERVER
?
在深入源码之前,我们先来聊聊为什么要这么做。 想象一下,你要写一个 WordPress 插件,需要获取当前页面的 URL。 你可能会这样写:
$current_url = $_SERVER['REQUEST_URI'];
看起来没问题,对吧? 但如果你的插件跑在一个 IIS 服务器上,而这个服务器并没有设置 REQUEST_URI
变量,你的代码就会报错,整个页面就崩溃了。
更糟糕的是,有些服务器可能会使用其他变量来表示 URL,比如 ORIG_PATH_INFO
或 QUERY_STRING
。 如果你的代码只认 REQUEST_URI
,那在这些服务器上就彻底歇菜了。
所以,为了解决这些问题,WordPress 需要一个统一的入口,来获取当前页面的 URL,以及其他常用的服务器信息。 这个统一的入口,就是通过 wp_fix_server_vars()
来实现的。
二、 wp_fix_server_vars()
源码解析
废话不多说,让我们直接进入源码。 wp_fix_server_vars()
函数位于 wp-includes/functions.php
文件中。 以下是精简后的代码,去除了注释和一些不常用的分支:
function wp_fix_server_vars() {
global $_SERVER;
$default_server_values = array(
'SERVER_NAME' => '',
'SERVER_PORT' => '80',
'REQUEST_URI' => '',
'PATH_INFO' => '',
'PATH_TRANSLATED' => '',
'SCRIPT_NAME' => '',
'SCRIPT_FILENAME' => '',
'PHP_SELF' => '',
'REMOTE_ADDR' => '',
'REMOTE_PORT' => '',
'SERVER_PROTOCOL' => '',
'QUERY_STRING' => '',
'HTTP_ACCEPT' => '',
'HTTP_USER_AGENT' => '',
'HTTP_ACCEPT_ENCODING' => '',
'HTTP_ACCEPT_LANGUAGE' => '',
'SERVER_SOFTWARE' => '',
'REMOTE_HOST' => '',
);
// Merge with some empty default values.
$_SERVER = array_merge( $default_server_values, $_SERVER );
// Fix for IIS when running with PHP ISAPI.
if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && strpos( $_SERVER['SERVER_SOFTWARE'], 'IIS' ) !== false ) {
// IIS Mod-Rewrite.
if ( ! isset( $_SERVER['REQUEST_URI'] ) || empty( $_SERVER['REQUEST_URI'] ) ) {
$_SERVER['REQUEST_URI'] = wp_get_server_var( 'SCRIPT_NAME' );
if ( isset( $_SERVER['QUERY_STRING'] ) && ! empty( $_SERVER['QUERY_STRING'] ) ) {
$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
}
}
// Handle PATH_INFO and PATH_TRANSLATED for IIS.
if ( empty( $_SERVER['PATH_INFO'] ) && ! empty( $_SERVER['ORIG_PATH_INFO'] ) ) {
$_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
}
if ( empty( $_SERVER['PATH_TRANSLATED'] ) && ! empty( $_SERVER['ORIG_PATH_TRANSLATED'] ) ) {
$_SERVER['PATH_TRANSLATED'] = $_SERVER['ORIG_PATH_TRANSLATED'];
}
}
// Fix for CGI scripts that set SCRIPT_FILENAME to something ending in .php only. PHP oddness.
if ( isset( $_SERVER['SCRIPT_FILENAME'] ) && ( false !== strpos( $_SERVER['SCRIPT_FILENAME'], '.php' ) ) && ( basename( $_SERVER['SCRIPT_FILENAME'] ) != basename( __FILE__ ) ) ) {
$_SERVER['SCRIPT_FILENAME'] = realpath( __FILE__ );
}
// Fix for PHP as CGI hosts that set SCRIPT_NAME to a broken path.
$script_name = wp_get_server_var( 'SCRIPT_NAME' );
if ( isset( $script_name ) && ( basename( $script_name ) != basename( __FILE__ ) ) ) {
$dirname = dirname( __FILE__ );
if ( strpos( $script_name, $dirname ) === 0 ) {
$_SERVER['SCRIPT_NAME'] = str_replace( $dirname, '', $script_name );
}
}
// Fix for when PATH_INFO is not set but REQUEST_URI is set.
$path_info = wp_get_server_var( 'PATH_INFO' );
$request_uri = wp_get_server_var( 'REQUEST_URI' );
if ( empty( $path_info ) && ! empty( $request_uri ) ) {
$file_name = basename( wp_get_server_var( 'SCRIPT_FILENAME' ) );
if ( strpos( $request_uri, $file_name ) !== false ) {
$_SERVER['PATH_INFO'] = str_replace( $file_name, '', $request_uri );
}
}
// Further sanitizing of PATH_INFO.
if ( ! empty( $_SERVER['PATH_INFO'] ) ) {
$_SERVER['PATH_INFO'] = str_replace( array( '?', '&' ), '', $_SERVER['PATH_INFO'] );
$_SERVER['PATH_INFO'] = '/' . ltrim( $_SERVER['PATH_INFO'], '/' );
}
}
/**
* Retrieves a value from the $_SERVER array.
*
* @since 4.6.0
*
* @param string $key The index in the $_SERVER array.
* @param mixed $default Optional. Value to return if the key does not exist. Default null.
* @return mixed The value in the $_SERVER array, or the default value if not set.
*/
function wp_get_server_var( $key, $default = null ) {
return isset( $_SERVER[ $key ] ) ? $_SERVER[ $key ] : $default;
}
让我们一行一行地分析这段代码:
-
global $_SERVER;
: 声明$_SERVER
为全局变量,以便在函数内部可以修改它。 -
$default_server_values
: 定义一个包含常用$_SERVER
变量的数组,并设置了默认值。 如果服务器没有提供这些变量,WordPress 将使用这些默认值。变量名 默认值 描述 SERVER_NAME
''
服务器主机名 SERVER_PORT
'80'
服务器端口号 REQUEST_URI
''
请求的 URI PATH_INFO
''
包含关于请求路径的信息 PATH_TRANSLATED
''
服务器将 PATH_INFO 映射到文件系统的路径 SCRIPT_NAME
''
当前执行脚本的路径 SCRIPT_FILENAME
''
当前执行脚本的完整路径 PHP_SELF
''
当前脚本相对于根目录的路径 REMOTE_ADDR
''
客户端 IP 地址 REMOTE_PORT
''
客户端端口号 SERVER_PROTOCOL
''
服务器使用的协议 QUERY_STRING
''
查询字符串 HTTP_ACCEPT
''
客户端可以接收的内容类型 HTTP_USER_AGENT
''
客户端 User-Agent 字符串 HTTP_ACCEPT_ENCODING
''
客户端支持的编码方式 HTTP_ACCEPT_LANGUAGE
''
客户端支持的语言 SERVER_SOFTWARE
''
服务器软件信息 REMOTE_HOST
''
客户端主机名 -
$_SERVER = array_merge( $default_server_values, $_SERVER );
: 将$default_server_values
数组和$_SERVER
数组合并。 如果$_SERVER
中没有某个变量,就使用$default_server_values
中的默认值。 这样可以确保$_SERVER
数组中至少包含这些常用的变量,即使服务器没有提供它们。 -
if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && strpos( $_SERVER['SERVER_SOFTWARE'], 'IIS' ) !== false ) { ... }
: 这是一个针对 IIS 服务器的特殊处理。 IIS 服务器在某些情况下,不会正确设置REQUEST_URI
、PATH_INFO
和PATH_TRANSLATED
变量。 这段代码会尝试根据其他变量来推断这些值。- 如果
REQUEST_URI
为空,就使用SCRIPT_NAME
和QUERY_STRING
来构建它。 - 如果
PATH_INFO
或PATH_TRANSLATED
为空,就使用ORIG_PATH_INFO
或ORIG_PATH_TRANSLATED
来填充它们。
- 如果
-
if ( isset( $_SERVER['SCRIPT_FILENAME'] ) && ( false !== strpos( $_SERVER['SCRIPT_FILENAME'], '.php' ) ) && ( basename( $_SERVER['SCRIPT_FILENAME'] ) != basename( __FILE__ ) ) { ... }
: 针对某些 CGI 服务器的修复。 这些服务器可能会将SCRIPT_FILENAME
设置为一个以.php
结尾的路径,但这并不是真正的脚本文件。 这段代码会尝试将SCRIPT_FILENAME
设置为当前脚本的真实路径。 -
if ( isset( $script_name ) && ( basename( $script_name ) != basename( __FILE__ ) ) { ... }
: 也是一个针对 CGI 服务器的修复。 有些 CGI 服务器会将SCRIPT_NAME
设置为一个错误的路径。 这段代码会尝试修正SCRIPT_NAME
。 -
if ( empty( $path_info ) && ! empty( $request_uri ) ) { ... }
: 如果PATH_INFO
为空,但REQUEST_URI
不为空,就尝试从REQUEST_URI
中提取PATH_INFO
。 -
if ( ! empty( $_SERVER['PATH_INFO'] ) ) { ... }
: 对PATH_INFO
进行清理,移除?
和&
等字符,并确保以/
开头。 -
wp_get_server_var( $key, $default = null )
: 这是一个辅助函数,用于从$_SERVER
数组中获取值。 如果指定的 key 不存在,就返回指定的默认值。 这个函数可以避免直接访问$_SERVER
数组时出现 "Undefined index" 错误。
三、 重点变量解析
wp_fix_server_vars()
主要关注以下几个变量:
-
REQUEST_URI
: 请求的 URI,包括路径和查询字符串。 这是获取当前页面 URL 的关键变量。 -
PATH_INFO
: 包含关于请求路径的额外信息。 通常用于 URL 重写。 -
SCRIPT_NAME
: 当前执行脚本的路径。 -
SCRIPT_FILENAME
: 当前执行脚本的完整路径。
这些变量在不同的服务器环境下,可能会有不同的值,甚至不存在。 wp_fix_server_vars()
的目标,就是确保这些变量在所有服务器环境下都可用,并且具有一致的含义。
四、 实际应用场景
那么,wp_fix_server_vars()
在 WordPress 中是如何使用的呢?
-
wp()
函数: 在 WordPress 的主循环之前,wp()
函数会调用wp_fix_server_vars()
,以确保$_SERVER
变量被正确初始化。 -
get_site_url()
和get_bloginfo()
函数: 这些函数在获取站点 URL 和博客信息时,会依赖于$_SERVER
变量。 -
插件和主题开发: 插件和主题开发者可以使用
wp_get_server_var()
函数来安全地访问$_SERVER
变量,而不用担心 "Undefined index" 错误。
五、 总结
wp_fix_server_vars()
是一个不起眼,但至关重要的函数。 它通过标准化 $_SERVER
变量,确保 WordPress 在各种服务器环境下都能稳定运行。 虽然我们平时很少直接接触它,但它却默默地为我们解决了很多兼容性问题。
六、 最后的彩蛋
大家可能注意到了,wp_fix_server_vars()
函数中有很多针对 IIS 服务器的特殊处理。 这说明 IIS 服务器在某些方面确实比较特殊,需要特殊照顾。 所以,如果你正在使用 IIS 服务器,并且遇到了 WordPress 的问题,不妨检查一下 $_SERVER
变量是否正确设置。
好了,今天的“WordPress 源码扒皮”节目就到这里。 希望大家通过今天的学习,对 WordPress 的底层机制有了更深入的了解。 下次再见!