PHP 8.6 的 Uri 类型:把 parse_url 的推断换成枚举判断
PHP 8.6 新增了 Uri\Rfc3986 相关类型。它替代的核心场景很具体:判断一个字符串是不是绝对 URL、host 是不是 IP,过去靠 parse_url() 的数组加一点猜测,现在直接读类型。
从 parse_url 到 Uri::parse
PHP 8.5 的写法:
$parts = parse_url($input); // an array, or false
$isAbsolute = isset($parts['scheme']); // a guess about intent
$isIp = (bool) filter_var($parts['host'], FILTER_VALIDATE_IP); // another guess
PHP 8.6:
$uri = Uri\Rfc3986\Uri::parse($input);
$isAbsolute = Uri\Rfc3986\UriType::Uri === $uri->getUriType();
$isIp = in_array($uri->getHostType(), [Uri\Rfc3986\UriHostType::IpV4, Uri\Rfc3986\UriHostType::IpV6], true);
实测下来的分类是:
https://example.com/a→Uri/a/b→AbsolutePathReference../a/b→RelativePathReference//example.com/a→NetworkPathReference
host 一侧则分成 RegisteredName、IpV4、IpV6、IpVFuture。这些都是枚举,所以对引用类型做 match 时,检查是穷尽的,不必再写一长串字符串比较。
类都在命名空间下:Uri\Rfc3986\Uri、Uri\Rfc3986\UriHostType、Uri\Rfc3986\UriType。
三个会直接踩到的行为差异
一、parse() 失败返回 null,不是 false。 判空不能省:
Uri\Rfc3986\Uri::parse('https://a b.com/x'); // null
对着 null 调 getUriType(),报错是 Call to a member function getUriType() on null。沿用 if ($parts === false) 的习惯会在这里断掉。
二、空字符串和相对路径都能解析成功。 parse() 拒绝的是畸形输入,不是相对引用。所以「解析成功」不等于「是绝对 URL」——判断绝对性仍然要看 getUriType() 的结果。
三、拼接 URL 时,畸形路径会抛异常而不是产出坏 URL。 过去手工拼串,路径里混进空格,上线后就是一个 404;现在 setPath('/a b') 直接抛 Uri\InvalidUriException,消息为:
The specified path is malformed
UriBuilder
PHP 8.5 的手工拼接:
$url = 'https://'.$host.$path.'?'.http_build_query($query); // hope every part is already safe
PHP 8.6:
$url = (new Uri\Rfc3986\UriBuilder())
->setScheme('https')
->setHost($host)
->setPath($path)
->setQuery('page=2')
->build()
->toString();
各部件分别校验,错误在构造阶段暴露,而不是在生产环境的访问日志里。
百分号编码按去向区分上下文
编码函数放在 WHATWG 一侧,所以不是全局函数,而是带命名空间:
Uri\WhatWg\url_percent_encode('a b/c?d', Uri\WhatWg\UrlPercentEncodingMode::Path);
// 'a%20b/c%3Fd'
Uri\WhatWg\url_percent_encode('a b/c?d', Uri\WhatWg\UrlPercentEncodingMode::Query);
// 'a%20b/c?d'
同一个值,两个调用给出两个正确结果,取决于它要去的上下文。以前是在 urlencode() 和 rawurlencode() 之间记哪个该用在哪,选错就多编或漏编一个字符。
迁移与命名注意
可以先在「是不是绝对 URL / 是不是 IP」这类判断上把 parse_url() 换掉,改动面最小,收益也最直接。
另外,RC2 已按 PHP 命名规范把枚举 case 从 IPv4 改成 IpV4(GH-23851),这些枚举本身是 8.6 新增的,旧版本上没有对应写法。
信源:https://wolinski.com/blog/php-8-6-new-features.html