從完整 URL 或主機名中精準萃取「根域名」(如 www.example.co.uk → example.co.uk)是常見需求。本文整理三種不同複雜度的方案,涵蓋常見 TLD 與國別碼(如 .com.cn、.gov.cn)。
<?php
echo $_SERVER['HTTP_HOST']; // 含 port(若非 80/443)
echo $_SERVER['SERVER_NAME']; // Server 設定的主機名
// 注意:若使用反向代理,需檢查 X-Forwarded-Host
function getRootDomain($url) {
// 確保結尾有斜線方便匹配
$url = rtrim($url, '/') . '/';
// 匹配 scheme:// 可選、子網域可選、主域名+頂級域
$pattern = '~^(?:https?://)?(?:[^/]+\.)?([a-z0-9-]+\.(?:com|net|org|gov|edu|co|me|io|ai|app|dev|cn|com\.cn|net\.cn|org\.cn|gov\.cn))~i';
if (preg_match($pattern, $url, $m)) {
return $m[1];
}
// IP 直連情況
if (preg_match('~^https?://(\d{1,3}(?:\.\d{1,3}){3})~i', $url, $m)) {
return $m[1];
}
return '';
}
function getRootDomainV2($host) {
$parts = explode('.', $host);
$cnt = count($parts);
if ($cnt < 2) return $host;
$twoLevelTlds = ['com','net','org','gov','edu','com.cn','net.cn','org.cn','gov.cn','cn'];
$last = strtolower($parts[$cnt - 1]);
$secondLast = strtolower($parts[$cnt - 2]);
if ($last === 'cn' && in_array($secondLast, $twoLevelTlds)) {
// xxx.com.cn / xxx.gov.cn 等
return implode('.', array_slice($parts, -3));
}
// 一般情況:取最後兩段
return $secondLast . '.' . $last;
}
生產環境強烈建議引用 php-domain-parser,內建 Public Suffix List,自動處理數千種 TLD 變化:
composer require jeremykendall/php-domain-parser
use Pdp\Manager;
$manager = new Manager();
$domain = $manager->parse('https://shop.blog.example.co.uk/path');
echo $domain->getRegistrableDomain(); // example.co.uk
| 方法 | 優點 | 缺點 | 建議場景 |
|---|---|---|---|
| 正則 | 無依賴、單一函式 | 難維護新 TLD | 小專案、已知域名集合 |
| 陣列切割 | 邏輯清晰、效能極佳 | 需手動維護雙層 TLD 列表 | 中型專案、以 .cn/.tw 為主 |
| Public Suffix List | 標準化、自動更新、最準確 | 需 Composer、稍大 | 所有生產環境、多國域名 |
選擇原則:能用 Composer 就用 Public Suffix List;受限環境下用方法二並定期更新 $twoLevelTlds 陣列。