PHP 提供了兩個強大的內建函數處理 URL 解析:parse_url() 用於分解 URL 結構,parse_str() 用於解析查詢字串。本文深度解析兩個函數的參數、返回值、邊界情況,並展示實戰應用場景(含知名專案 php-resque 的原始碼分析)。
mixed parse_url(string $url, int $component = -1)
$url:要解析的 URL,無效字元會被替換為底線$component:可選,指定返回特定組件(常數):
PHP_URL_SCHEME - 協定(如 http)PHP_URL_HOST - 主機名PHP_URL_PORT - 埠號(返回 integer)PHP_URL_USER - 用戶名PHP_URL_PASS - 密碼PHP_URL_PATH - 路徑PHP_URL_QUERY - 查詢字串(? 之後)PHP_URL_FRAGMENT - 錨點(# 之後)$component:返回關聯陣列,包含存在的組件$component:返回字串(PORT 除外返回整數)falsenull<?php
$url = 'https://username:password@hostname/path?arg=value#anchor';
print_r(parse_url($url));
/* 輸出:
Array
(
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
)
*/
echo parse_url($url, PHP_URL_PATH); // /path
?>
<?php
// 不完整 URL 也能盡力解析
parse_url('https://example.com'); // 正常
parse_url('example.com/path'); // host 會被歸類為 path
parse_url('//example.com/path'); // 正常解析 host
parse_url('mailto:user@example.com'); // scheme=mailto, path=user@example.com
// 無效 URL
parse_url('https:///path'); // 可能返回 false
?>
void parse_str(string $str, array &$arr = null)
$str:查詢字串(如 a=1&b=2)$arr:可選引用參數,若提供則將解析結果存入此陣列,否則直接註冊到當前符號表(不推薦)<?php
$str = "first=value&arr[]=foo+bar&arr[]=baz";
// 方式一:直接註冊變數(污染符號表,不推薦)
parse_str($str);
echo $first; // value
echo $arr[0]; // foo bar
echo $arr[1]; // baz
// 方式二:存入陣列(推薦,安全)
$output = array();
parse_str($str, $output);
echo $output['first']; // value
echo $output['arr'][0]; // foo bar
echo $output['arr'][1]; // baz
?>
+ 或 %20[] 表示陣列結構. 和空格在變數名中會轉為底線 _[])知名佇列專案 php-resque 巧妙運用這兩個函數解析 Redis DSN:
redis://user:pass@host:port/db?option1=val1&option2=val2
tcp://user:pass@host:port/db?option1=val1&option2=val2
host:port (簡寫格式)
<?php
/**
* 解析 Redis DSN 字串
* @param string $dsn DSN 字串
* @return array [host, port, database, user, pass, options]
*/
public static function parseDsn($dsn) {
// 空 DSN 使用預設值
if ($dsn == '') {
$dsn = 'redis://' . self::DEFAULT_HOST;
}
// 1. 使用 parse_url 分解主要結構
$parts = parse_url($dsn);
// 2. 驗證協定
$validSchemes = array('redis', 'tcp');
if (isset($parts['scheme']) && !in_array($parts['scheme'], $validSchemes)) {
throw new \InvalidArgumentException("Invalid DSN. Supported schemes: " . implode(', ', $validSchemes));
}
// 3. 處理簡寫格式:parse_url 會將 'host:port' 歸類為 path
if (!isset($parts['host']) && isset($parts['path'])) {
$parts['host'] = $parts['path'];
unset($parts['path']);
}
// 4. 提取埠號
$port = isset($parts['port']) ? intval($parts['port']) : self::DEFAULT_PORT;
// 5. 從 path 提取資料庫編號
$database = false;
if (isset($parts['path'])) {
// 移除非數字字元(如開頭的 /)
$database = intval(preg_replace('/[^0-9]/', '', $parts['path']));
}
// 6. 提取用戶名密碼
$user = isset($parts['user']) ? $parts['user'] : false;
$pass = isset($parts['pass']) ? $parts['pass'] : false;
// 7. 使用 parse_str 解析查詢參數
$options = array();
if (isset($parts['query'])) {
parse_str($parts['query'], $options);
}
return array($parts['host'], $port, $database, $user, $pass, $options);
}
?>
<?php
// 獲取原始查詢字串
$queryString = $_SERVER['QUERY_STRING'];
// 解析為陣列
$params = array();
parse_str($queryString, $params);
?>
<?php
function modifyUrlQuery($url, $modifications) {
$parts = parse_url($url);
$query = array();
if (isset($parts['query'])) {
parse_str($parts['query'], $query);
}
// 應用修改
foreach ($modifications as $key => $value) {
if ($value === null) {
unset($query[$key]);
} else {
$query[$key] = $value;
}
}
// 重建
$newQuery = http_build_query($query);
$newUrl = (isset($parts['scheme']) ? $parts['scheme'] . '://' : '')
. (isset($parts['host']) ? $parts['host'] : '')
. (isset($parts['path']) ? $parts['path'] : '')
. ($newQuery ? '?' . $newQuery : '')
. (isset($parts['fragment']) ? '#' . $parts['fragment'] : '');
return $newUrl;
}
// 使用
$url = 'https://example.com/path?a=1&b=2';
echo modifyUrlQuery($url, array('b' => 3, 'c' => 4));
// https://example.com/path?a=1&b=3&c=4
?>
<?php
class UrlParser {
public static function parse($url) {
$result = parse_url($url);
if ($result === false) {
throw new InvalidArgumentException("Invalid URL format: $url");
}
// 確保關鍵組件存在
$result += array(
'scheme' => 'http',
'host' => '',
'port' => null,
'path' => '/',
'query' => '',
'fragment' => ''
);
// 解析查詢參數
$result['query_params'] = array();
if ($result['query']) {
parse_str($result['query'], $result['query_params']);
}
return $result;
}
}
?>
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 無 scheme 的 URL | parse_url 將 host 歸類為 path | 預檢補全 scheme 或檢查 host 是否為空 |
| parse_str 污染變數 | 未提供 $arr 參數會註冊到符號表 | 永遠傳入第二個參數接收結果 |
| 方括號編碼問題 | [] 在 URL 中需編碼為 %5B%5D | 使用 http_build_query 自動處理 |
| 片段識別符不發送到伺服器 | #anchor 僅在瀏覽器端使用 | 勿依賴 $_SERVER 獲取 fragment |
| 國際化域名 | 需使用 idn_to_ascii 轉換 | 解析前先轉換 Punycode |
parse_url() 與 parse_str() 是 PHP 處理 URL 的黃金搭檔:前者負責結構分解,後者負責查詢參數解析。php-resque 的實現展示了組合使用這兩個函數解析複雜 DSN 格式的經典模式。掌握兩者的邊界行為與最佳實踐,能避免常見陷阱,編寫健壯的 URL 處理程式碼。