PHP8.5 的新 URI 擴(kuò)展
PHP8.5 的新 URI 擴(kuò)展
URL 是我們每天依賴的 Web 的基礎(chǔ)構(gòu)建塊。
它們的熟悉度讓它們看起來(lái)簡(jiǎn)單得有些欺騙性:看似清晰劃分的組件,如 scheme、hostname、path 等,讓人覺(jué)得從 URL 中提取信息是件小事。但實(shí)際上,多年來(lái)已經(jīng)構(gòu)建了數(shù)千個(gè)自定義解析器,每個(gè)都有自己對(duì)細(xì)節(jié)的理解。
對(duì)我們 Web 開(kāi)發(fā)者來(lái)說(shuō),有兩個(gè)主要標(biāo)準(zhǔn)規(guī)定了 URL 應(yīng)該如何工作。RFC 3986,這是 2005 年的原始 URI 標(biāo)準(zhǔn);以及 WHATWG URL Living Standard,Web 瀏覽器遵循的標(biāo)準(zhǔn)。因?yàn)槭虑椴⒉幌裾Э雌饋?lái)那么簡(jiǎn)單,這兩個(gè)常用標(biāo)準(zhǔn)彼此不兼容!混合使用不同的標(biāo)準(zhǔn)及其解析器,特別是當(dāng)它們沒(méi)有完全遵循標(biāo)準(zhǔn)時(shí),通常會(huì)導(dǎo)致安全問(wèn)題。
為什么需要改變
盡管正確解析 URL 很重要,但 PHP 長(zhǎng)期以來(lái)在標(biāo)準(zhǔn)庫(kù)中并沒(méi)有包含任何符合標(biāo)準(zhǔn)的解析器。雖然有 parse_url() 函數(shù),它從 PHP 4 就存在了,但它不遵循任何標(biāo)準(zhǔn),并且在文檔中明確說(shuō)明不要用于不受信任或格式錯(cuò)誤的 URL。盡管如此,由于缺乏更好的替代方案,而且它對(duì)開(kāi)發(fā)者日常工作中遇到的大多數(shù)格式良好的輸入似乎都能正常工作,所以它被廣泛使用。這可能會(huì)誤導(dǎo)開(kāi)發(fā)者認(rèn)為 parse_url() 的安全問(wèn)題純粹是理論問(wèn)題,而不是遲早會(huì)導(dǎo)致問(wèn)題的東西。
舉個(gè)例子,輸入 URL example.com/example/:8080/foo 根據(jù) RFC 3986 是一個(gè)有效的 URL,僅由相對(duì)路徑組成。如果不針對(duì)基礎(chǔ) URL 解析,根據(jù) WHATWG URL 標(biāo)準(zhǔn)它是無(wú)效的。然而,根據(jù) parse_url() 的解析,它是主機(jī)為 example.com、端口為 8080、路徑為 /example/:8080/foo 的 URL,因此在兩個(gè)結(jié)果組件中都包含了 8080:
<?php
var_dump(parse_url('example.com/example/:8080/foo'));
/*
array(3) {
["host"]=> string(11) "example.com"
["port"]=> int(8080)
["path"]=> string(18) "/example/:8080/foo"
}
*/
引入新 API
這在 PHP 8.5 中發(fā)生了變化。從現(xiàn)在開(kāi)始,PHP 將在新的 "URI" 擴(kuò)展中包含符合 RFC 3986 和 WHATWG URL 標(biāo)準(zhǔn)的解析器,作為標(biāo)準(zhǔn)庫(kù)中始終可用的一部分。這不僅能夠根據(jù)各自的標(biāo)準(zhǔn)輕松、正確、安全地解析 URL,而且 URI 擴(kuò)展還包含修改 URL 各個(gè)組件的功能。
<?php
use Uri\Rfc3986\Uri;
$url = new Uri('HTTPS://thephp.foundation:443/sp%6Fnsor/');
$defaultPortForScheme = match ($url->getScheme()) {
'http' => 80,
'https' => 443,
'ssh' => 22,
default => null,
};
// 從 URL 中刪除默認(rèn)端口
if ($url->getPort() === $defaultPortForScheme) {
$url = $url->withPort(null);
}
// Getter 默認(rèn)會(huì)規(guī)范化 URL。`Raw`
// 變體會(huì)原樣返回輸入。
echo $url->toString(), PHP_EOL;
// 輸出: https://thephp.foundation/sponsor/
echo $url->toRawString(), PHP_EOL;
// 輸出: HTTPS://thephp.foundation/sp%6Fnsor/
精心構(gòu)建以持久使用
在這篇文章中,我們不僅想展示功能,還想告訴你這個(gè)項(xiàng)目是如何發(fā)展的,以及在 PHP 中如何完成工作以保持語(yǔ)言現(xiàn)代化,成為 Web 開(kāi)發(fā)的絕佳選擇。新 PHP 功能背后的工作往往比表面看到的要多。我們希望提供一些見(jiàn)解,說(shuō)明為什么我們更喜歡把事情做對(duì)而不是做快。
來(lái)自 PHP 基金會(huì)開(kāi)發(fā)團(tuán)隊(duì)的 Máté Kocsis 最初在 2024 年 6 月開(kāi)始討論他關(guān)于新 URL 解析 API 的 RFC。鑒于 PHP 強(qiáng)大的向后兼容性承諾,新 API 需要在第一次嘗試時(shí)就把事情做對(duì),以便在未來(lái)十年內(nèi)為 PHP 社區(qū)提供良好服務(wù),而不會(huì)引入破壞性變化。因此,在將近一年的時(shí)間里,PHP Internals 郵件列表上發(fā)送了 150 多封電子郵件。此外,還在各種聊天室進(jìn)行了多次非列表討論。在整個(gè)過(guò)程中,來(lái)自 PHP 社區(qū)的各種專家不斷完善 RFC。他們討論了即使是看似微不足道的細(xì)節(jié),不僅要提供符合標(biāo)準(zhǔn)的實(shí)現(xiàn),還要提供一個(gè)干凈、健壯的 API,引導(dǎo)開(kāi)發(fā)者為他們的用例找到正確的解決方案。我們還提前規(guī)劃,確保新的 URI 擴(kuò)展及其專用的 Uri 命名空間為在未來(lái)版本的 PHP 中添加額外的 URI/URL 相關(guān)功能提供了清晰的路徑。
RFC 最終在 2025 年 5 月進(jìn)行投票,并以 30:1 的投票結(jié)果獲得通過(guò)。但工作并沒(méi)有就此停止:提議的 API 還必須實(shí)現(xiàn)和審查。Máté 沒(méi)有構(gòu)建一個(gè) PHP 特定的解決方案,而是選擇站在巨人的肩膀上,選擇了兩個(gè)庫(kù)來(lái)完成繁重的工作。uriparser 庫(kù)提供 RFC 3986 解析器,而 Lexbor 庫(kù)(已被 PHP 8.4 的新 DOM API 使用)提供 WHATWG 解析器。
開(kāi)源協(xié)作
作為集成的一部分,Máté 和 PHP 基金會(huì)與上游維護(hù)者合作,在各自的庫(kù)中包含缺失的功能。例如,這兩個(gè)庫(kù)都不包含廉價(jià)復(fù)制內(nèi)部數(shù)據(jù)結(jié)構(gòu)的功能,而這對(duì)于支持在嘗試使用所謂的 with-er 方法(例如 ->withPort(8080))修改各個(gè)組件時(shí)克隆表示已解析 URL 的只讀 PHP 對(duì)象是必需的。uriparser 庫(kù)也不包含任何用于修改已解析 URL 組件的函數(shù)。所有這些功能現(xiàn)在都可以在上游庫(kù)中供所有人使用和受益。
Máté 的 PHP 實(shí)現(xiàn)的審查和測(cè)試由 PHP 社區(qū)貢獻(xiàn)者 Niels Dossche 和 Ignace Nyamagana Butera 進(jìn)行。這包括審查和測(cè)試已添加到兩個(gè)上游庫(kù)的新功能。Tideways,PHP 基金會(huì)的創(chuàng)始成員和白銀贊助商,也贊助了工程時(shí)間;他們的貢獻(xiàn)以 Tim Düsterhus 的形式出現(xiàn)。在審查和測(cè)試期間,這些審查者在上游庫(kù)中發(fā)現(xiàn)了幾個(gè)預(yù)先存在的 bug。他們向上游維護(hù)者 Sebastian Pipping(uriparser)和 Alexander Borisov(Lexbor)提交了修復(fù),他們迅速審查并應(yīng)用了這些修復(fù)。
立即測(cè)試
這項(xiàng)工作得到了回報(bào),PHP 的新 URI 擴(kuò)展不僅有一個(gè)而是兩個(gè)功能豐富且符合標(biāo)準(zhǔn)的 URI 實(shí)現(xiàn),現(xiàn)在可以在 PHP 8.5 RC 1 中完全測(cè)試。

浙公網(wǎng)安備 33010602011771號(hào)