PHP 8.5 的 URI 扩展:用 Uri\Rfc3986\Uri 和 Uri\WhatWg\Url 替掉 parse_url
PHP 8.5 新增内置 URI 扩展,按 RFC 3986 和 WHATWG URL 两套标准解析、规范化、修改 URL。底层分别由 uriparser(RFC 3986)和 Lexbor(WHATWG URL)驱动。
parse_url 的问题
parse_url() 自 PHP 4 就存在,它不遵循任何标准,官方文档明确警告不要用于不可信或畸形 URL。
一个能复现的例子:输入 example.com/example/:8080/foo。
- 按 RFC 3986,这是一个只含相对路径的合法 URL。
- 按 WHATWG,在没有 base 的情况下它是非法输入。
- 而
parse_url()给出host=example.com、port=8080、path=/example/:8080/foo——把8080同时放进了两个组件里。
新扩展里对应两个类,两者不可互换:
Uri\Rfc3986\Uri:遵循 RFC 3986,通用 URI、严格校验、可选规范化、在安全的地方做百分号解码;可以没有 scheme。提供 raw 视图与规范化解码视图两种读法。Uri\WhatWg\Url:遵循 WHATWG URL,对齐浏览器行为,支持 IDNA/Unicode 主机、解析期变换、软错误与硬错误;必须有 scheme。多数组件保留百分号编码,解析时自动规范化。
二者都支持解析、解析相对引用(resolve)、读写组件、比较、序列化。
解析与失败
use Uri\Rfc3986\Uri;
$rfc = Uri::parse("https://example.com/path?x=1"); // Uri 或 null
$bad = Uri::parse("invalid uri"); // null(严格失败)
$errors = [];
$whatwg = Uri\WhatWg\Url::parse(" invalid url", null, $errors); // null 且带软/硬错误
RFC 3986 侧是严格失败:解析不出来就是 null。WHATWG 侧走浏览器的软错误模型,失败时仍会通过 $errors 带回具体问题。
WHATWG 类在组件层面保留编码:
new Url("HTTPS://%61pple:p%61ss@ex%61mple.com:433/foob%61r?%61bc=%61bc#%61bc");
// getScheme() => https
// getUsername() => %61pple
// getPath() => /foob%61r
// getQuery() => %61bc=%61bc
IDNA/Unicode 主机只在 WHATWG 侧提供:
new Url("https://🐘.com");
// getAsciiHost() => xn--go8h.com
// getUnicodeHost() => 🐘.com
不可变与 with-er
两个类都是不可变的,修改组件通过 withScheme()、withPort() 这类 with-er 返回新实例:
use Uri\Rfc3986\Uri;
$url = new Uri('HTTPS://thephp.foundation:443/sp%6Fnsor/');
$url = $url->withPort(null); // 去掉默认端口
// 取值器默认规范化;Raw 变体返回输入原样
echo $url->toRawString(); // HTTPS://thephp.foundation/sp%6Fnsor/
重定向与校验
$uri = Uri::parse($incoming);
if ($uri === null) {
http_response_code(400);
exit;
}
$secure = $uri->withScheme("https");
if (!$secure->equals($uri, UriComparisonMode::ExcludeFragment)) {
header("Location: " . $secure->toString(), true, 301);
exit;
}
取舍
规范化视图适合做路由和缓存键——同一个资源的不同写法会收敛到一个形式。raw 视图保留精确字节,适合签名校验或转发代理:任何重新编码都可能让签名失效,所以这类场景必须走 raw。
WHATWG 那套对齐浏览器行为,代价是它的结果不是 RFC 3986 的合法子集,Unicode 域名和自动百分号编码都由它接管;反过来,它的软错误模型意味着你得主动检查 $errors 才知道输入哪里不规范。RFC 3986 那套更严格,但浏览器不按它走。两边都留着,是因为要处理的 URL 来源本来就分这两类。
顺带修掉的一个老问题:FILTER_VALIDATE_URL 与客户端遵循 RFC 3986 之间的不一致,会引发上面那种解析混淆。
链接
- PHP 8.5 发布说明:https://www.php.net/releases/8.5/zh.php
- URI 扩展手册:https://www.php.net/manual/zh/book.uri.php
- The PHP Foundation 公告:https://thephp.foundation/blog/2025/10/10/php-85-uri-extension/
- Amit Merchant 的介绍:https://www.amitmerchant.com/the-new-standards-compliant-uri-url-api-in-php-85/