PHP路由匹配失败因$_SERVER['REQUEST_URI']含子目录前缀,需用dirname($_SERVER['SCRIPT_NAME'])剥离;捕获组为空常因未trim()和rtrim('/', $uri)统一路径;header跳转报错因输出已发送。

PHP 路由匹配失败时,$_SERVER['REQUEST_URI'] 为什么总带前缀?
常见现象是:你写了 ^/user/(\d+)$,但实际 $_SERVER['REQUEST_URI'] 是 /myapp/user/123,导致正则永远不匹配。这不是路由引擎 bug,而是 PHP 运行在子目录(如 /myapp/)下,而你没剥离上下文路径。
解决方法不是改正则,而是先标准化 URI:
- 用
preg_replace剥离$_SERVER['SCRIPT_NAME']的目录部分,例如:$uri = $_SERVER['REQUEST_URI']; $script = $_SERVER['SCRIPT_NAME']; $base = dirname($script); if ($base !== '/') { $uri = preg_replace('#^' . preg_quote($base, '#') . '#', '', $uri); } $uri = parse_url($uri, PHP_URL_PATH); // 确保只留路径 - Apache 用户可加
RewriteBase /myapp配合.htaccess,但 PHP 层仍建议主动处理,避免环境依赖 - Nginx 用户注意
fastcgi_param SCRIPT_NAME是否被覆盖,常见错误是漏写fastcgi_param SCRIPT_NAME $fastcgi_script_name;
用 preg_match 做路由匹配时,为什么捕获组全为空?
典型错误是忘记加 PREG_OFFSET_CAPTURE 以外的标志,或正则本身没启用全局/多行模式——但其实最常踩坑的是:没对 $uri 做 trim() 和尾部斜杠归一化。
比如请求 /user/123/,你的规则 ^/user/(\d+)$ 就会失败。
立即学习“PHP免费学习笔记(深入)”;
- 统一处理尾部斜杠:
$uri = rtrim($uri, '/') - 匹配时用
^和$锚定,避免部分匹配(如/users/123被/user错误命中) - 检查 PCRE 版本兼容性:
u修饰符必须加在 UTF-8 场景下,否则中文路径会崩;D修饰符可防止$匹配换行符后位置 - 调试技巧:用
var_dump($uri, $pattern)对照输出,别只看preg_match返回值
想实现「匹配成功后跳转」,header('Location: ...') 为何报错「headers already sent」?
本质是输出已提前触发——哪怕只是 echo ''、BOM 字节、或 ?> 之间的空格,都会让 HTTP 头无法发送。
- 确保路由分发逻辑在任何
echo、print、HTML 输出前执行 - 开启
output_buffering只是掩盖问题,不是解法;生产环境应禁用 - 跳转前加
exit;或die;,否则后续代码仍会执行 - 如果必须在模板后跳转,改用 JS 跳转:
echo '';(仅作降级,非推荐)
自定义路由类里,dispatch() 方法怎么安全支持参数透传?
很多人把 call_user_func_array 直接套在用户回调上,结果路由匹配出的 $matches[1]、$matches[2] 类型混乱(字符串 vs 整数),或缺失默认值。
- 显式提取并类型转换:
$id = filter_var($matches[1], FILTER_VALIDATE_INT) ?: 0; - 用
func_get_args()+array_slice($matches, 1)避免索引越界,尤其当正则有可选组时 - 不要信任
$matches长度,始终用isset($matches[2])判断是否存在第二个参数 - 更健壮的做法:把匹配结果封装成
RouteParams对象,提供->int('id')、->string('slug')等方法
$_SERVER 变量没清洗干净,或者跳转前意外触发了缓冲区 flush。











