登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP 8.5 Uri 类怎样规范化 URL

来源:17golang原创

时间:2026-11-03 09:11:40 487浏览 收藏

在 PHP 8.5 中,处理 URL 不必再把 parse_url() 返回的数组和一串字符串替换拼在一起。URI 扩展提供了 Uri\\Rfc3986\\Uri 与 Uri\\WhatWg\\Url 两套对象模型:前者按 RFC 3986 处理通用 URI,后者按 WHATWG URL Standard 处理浏览器语境中的 URL。要把输入整理成稳定、可比较的 URI 字符串,通常从 Uri\\Rfc3986\\Uri 开始。

官方地址:https://www.php.net/manual/en/book.uri.php

规范化 URL 的关键不是“把字符串变漂亮”,而是先选定解析标准,再让 Uri 对象负责组件标准化;需要保留输入的原始编码时,使用 toRawString(),需要稳定表示时使用 toString()。

PHP 官方手册说明,PHP 8.5 新增的 URI 扩展实现 RFC 3986 和 WHATWG URL Standard。下面只围绕 URL 规范化、字段读取、等价比较和方案选择展开,不把它扩展成网络请求或完整业务校验教程。

先分清“规范化”与“原样重组”

Uri\\Rfc3986\\Uri 是只读对象。构造对象后,getScheme()、getHost()、getPath() 等方法读取的是规范化后的组件;toString() 把这些组件重新组成规范化 URI,toRawString() 则尽量保留输入中的原始表示。

RFC 3986 规范化包含几个容易混淆的细节:scheme 和 host 会转为小写,百分号编码中的十六进制字母会统一为大写,百分号编码的非保留字符可以解码,路径中的 dot segment 会被移除。它不是“所有 URL 都按浏览器地址栏规则处理”,所以不能拿这套结果替代 WHATWG URL 语义。

PHP Uri Rfc3986 Uri 将 URL 组件映射为规范化字符串与原始字符串的静态结构说明图
图1:PHP Uri\\Rfc3986\\Uri 规范化 URL 的结构示意图,不是截图或运行证据。

用 Uri\\Rfc3986\\Uri 生成规范化 URL

最小用法是把输入传给构造函数,再分别读取结构化字段和两个字符串表示。下面的示例故意同时保留规范化结果和原始结果,方便你在日志、缓存键或审计记录中按用途选择。

toString();

    // toRawString() 面向保留原始编码的场景,不要把它当成规范化结果。
    $raw = $uri->toRawString();

    // 结构化读取比手动切分字符串更容易对应 RFC 3986 的组件。
    printf("scheme=%s host=%s path=%s\n", $uri->getScheme(), $uri->getHost(), $uri->getPath());
    printf("normalized=%s\nraw=%s\n", $normalized, $raw);
} catch (\Uri\InvalidUriException $exception) {
    // 输入不符合 RFC 3986 时记录原因,由调用方决定拒绝、提示或回退。
    error_log('URI parse failed: ' . $exception->getMessage());
}

示例中的重点是对象职责:业务代码不需要先调用 strtolower(),也不应该用 str_replace() 猜测路径中的 .. 应该如何处理。规范化后的 host、scheme 和 path 由 URI 规则统一决定。异常处理也要保留,因为“能被字符串函数拼出来”不等于“能被指定标准接受”。

字段读取和字符串输出要按用途分开

如果业务只是展示或转发 URL,toString() 往往足够;如果需要把域名、路径或查询参数映射到不同字段,就直接调用组件方法。官方手册列出了 scheme、host、port、path、query、fragment、username 等读取接口,并同时提供对应的 raw 方法。

这一区分对缓存键尤其重要。缓存键需要稳定时可以使用规范化字符串,但如果系统还要回显用户输入、保留签名原文或记录代理层的原始编码,就应把 raw 表示单独保存,不能用规范化字符串覆盖审计需要的原始值。

 $uri->getScheme(),
    'host' => $uri->getHost(),
    'path' => $uri->getPath(),
    'query' => $uri->getQuery(),
    'fragment' => $uri->getFragment(),
];

// JSON 只负责展示结构,不改变 Uri 对象本身的只读性质。
echo json_encode($parts, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

// 需要保留输入样式时,原始组件要从 raw 方法读取。
echo $uri->getRawPath();

不要把 getQuery() 当成“已经按业务规则排序的参数表”。它返回的是 URI 组件,不会替你决定参数重复、参数顺序或业务签名规则。若应用的签名算法规定查询参数排序和编码方式,应在明确的签名层实现,而不是把 Uri 的规范化误认为业务 canonicalization。

等价比较时明确是否包含 fragment

URI 对象还提供 equals()。默认比较模式排除 fragment,这一点适合很多服务端资源判断,因为 fragment 通常只在客户端定位文档内部位置;如果你的业务把 fragment 作为资源身份的一部分,就要显式选择包含它的比较模式。

equals($right);

// 显式包含 fragment,适合把页面锚点也纳入身份的场景。
$sameDocumentLocation = $left->equals(
    $right,
    \Uri\UriComparisonMode::IncludeFragment
);

var_dump($sameResource, $sameDocumentLocation);

这里的选择是业务语义,不是哪个常量“更严格”。静态资源缓存、接口去重和后端权限判断通常关注不含 fragment 的 URI;前端文档锚点或需要精确复现位置的场景,才应把 fragment 纳入比较。

parse_url、RFC 3986 Uri 和 WHATWG Url 怎么选

PHP 8.5 并不是简单地把 parse_url() 换了一个类名。三种方案的结果模型和标准不同:parse_url() 返回历史兼容的数组;Uri\\Rfc3986\\Uri 提供遵循 RFC 3986 的只读对象;Uri\\WhatWg\\Url 则面向 WHATWG URL Standard,并提供 ASCII 或 Unicode 主机等 URL 语境接口。

PHP parse_url RFC 3986 Uri 和 WHATWG Url 的标准与适用边界静态对比图
图2:PHP 三种 URL 处理方案的适用边界示意图,不是截图或运行证据。
方案结果形态适合的边界不应直接假设
parse_url()数组或单个组件需要兼容既有代码和既有返回结构它不是 RFC 3986 与 WHATWG 的统一实现
Uri\\Rfc3986\\Uri只读 URI 对象服务端通用 URI、规范化、结构化比较它不等于浏览器地址栏的 WHATWG 解析
Uri\\WhatWg\\Url只读 URL 对象需要 WHATWG URL 语义、Unicode/ASCII 主机处理它不应被当作 RFC 3986 的结果

如果是新写的服务端代码,且目标是按通用 URI 标准得到稳定字符串,优先使用 Uri\\Rfc3986\\Uri。如果代码在处理浏览器表单、前端 URL 或 WHATWG 兼容行为,就选择 Uri\\WhatWg\\Url。如果旧系统大量依赖数组键名、空值行为或历史异常边界,先保留 parse_url(),再用测试覆盖迁移差异。

哪些场景不适合直接替换旧代码

第一种情况是兼容性:项目仍运行在 PHP 8.4 或更低版本时,不能只改命名空间就使用 PHP 8.5 的 URI 类。第二种情况是标准依赖:旧代码可能故意依赖 parse_url() 的历史行为,替换后数组访问、缺失组件和错误处理方式都会变化。第三种情况是业务校验:URI 规范化解决的是解析和重组,不自动完成允许域名、协议白名单、端口策略或 SSRF 防护。

尤其不要用“先解析、再拼接字符串”的方式绕过标准差异。安全边界应在解析标准确定后建立:同一套规则负责提取 host,后续 allowlist、DNS 解析、请求策略和审计记录也要明确使用哪一种表示。否则,校验阶段与真正发请求的阶段采用不同解析器,仍可能出现判断不一致。

一张表确定落地方案

你的目标建议入口理由
生成稳定的 RFC 3986 URI 字符串Uri\\Rfc3986\\Uri::toString()由对象统一处理组件规范化和重组
保留用户输入的原始编码toRawString() 或 raw 组件方法规范化输出不应覆盖展示与审计需要的原文
比较服务端资源是否相同equals() 默认模式默认不把 fragment 纳入比较
处理浏览器 URL 语义Uri\\WhatWg\\Url标准边界与 RFC 3986 不同,不能混用结果
保持旧数组接口不变parse_url()优先保证既有调用方兼容,再规划迁移

最终可以把决策压缩成一句话:先确定 URL 需要遵循 RFC 3986 还是 WHATWG,再选择对应的 Uri 类;只有在旧行为本身就是兼容要求时,才继续保留 parse_url()。规范化字符串、原始字符串和业务 canonicalization 也要分成三个概念管理。

toString() 会不会保留 URL 的原始大小写?

不应这样假设。RFC 3986 Uri 的规范化字符串会按规则处理 scheme、host、百分号编码和路径点段;如果需要保留输入表示,应使用 raw 方法。

能不能用 Uri 类直接验证域名是否允许访问?

不能。Uri 类负责按指定标准解析和重组;允许域名、端口、协议以及网络访问安全仍需由应用自己的策略层处理。

为什么不把所有项目都改成 WHATWG Url?

因为 WHATWG 和 RFC 3986 面向的语境不同。浏览器 URL 兼容性是 WHATWG 的优势,服务端通用 URI 规范化则通常更适合 RFC 3986。迁移前要看调用方真正依赖的标准。

本文依据 PHP 官方 URI 手册、Uri\\Rfc3986\\Uri 类文档、toString() 文档、parse_url() 文档和 PHP 8.5 发布说明整理:https://www.php.net/manual/en/book.uri.php、https://www.php.net/manual/en/class.uri-rfc3986-uri.php、https://www.php.net/manual/en/uri-rfc3986-uri.tostring.php、https://www.php.net/manual/en/function.parse-url.php。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>