在 Web 開發中,根據用戶的瀏覽器類型、版本及偏好語言提供差異化體驗(如舊版 IE 降級提示、多語系自動切換、移動端適配)是常見需求。PHP 透過 $_SERVER['HTTP_USER_AGENT'] 與 $_SERVER['HTTP_ACCEPT_LANGUAGE'] 即可實現無依賴的偵測。本文提供生產級的解析函數、正則模式庫、以及現代化的替代方案建議。
HTTP_USER_AGENT 字串包含瀏覽器引擎、版本、作業系統、裝置等資訊。例如:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1
<?php
/**
* 解析 User-Agent,返回結構化資訊
* @return array ['browser'=>名稱, 'version'=>版本, 'engine'=>引擎, 'os'=>作業系統, 'device'=>裝置類型]
*/
function parseUserAgent(string $ua = ''): array {
$ua = $ua ?: ($_SERVER['HTTP_USER_AGENT'] ?? '');
$result = [
'browser' => 'Unknown',
'version' => '0.0',
'engine' => 'Unknown',
'os' => 'Unknown',
'device' => 'Desktop', // Desktop / Mobile / Tablet / Bot
];
// 1. 機器人/爬蟲優先判斷
$botPatterns = [
'Googlebot' => 'Googlebot',
'Baiduspider' => 'Baiduspider',
'Bingbot' => 'Bingbot',
'YandexBot' => 'YandexBot',
'facebookexternalhit' => 'FacebookBot',
'Twitterbot' => 'TwitterBot',
'Slackbot' => 'SlackBot',
'WhatsApp' => 'WhatsAppBot',
];
foreach ($botPatterns as $pattern => $name) {
if (stripos($ua, $pattern) !== false) {
$result['browser'] = $name;
$result['device'] = 'Bot';
return $result;
}
}
// 2. 作業系統判斷
$osPatterns = [
'/Windows NT 10\.0/' => 'Windows 10/11',
'/Windows NT 6\.3/' => 'Windows 8.1',
'/Windows NT 6\.2/' => 'Windows 8',
'/Windows NT 6\.1/' => 'Windows 7',
'/Windows NT 6\.0/' => 'Windows Vista',
'/Windows NT 5\.1/' => 'Windows XP',
'/Mac OS X (\d+[._]\d+)/' => 'macOS $1',
'/iPhone OS (\d+[._]\d+)/' => 'iOS $1',
'/iPad.*OS (\d+[._]\d+)/' => 'iPadOS $1',
'/Android (\d+[._]\d+)/' => 'Android $1',
'/Linux/' => 'Linux',
'/CrOS/' => 'Chrome OS',
];
foreach ($osPatterns as $pattern => $name) {
if (preg_match($pattern, $ua, $m)) {
$result['os'] = is_string($m[0]) ? preg_replace($pattern, $name, $m[0]) : $name;
break;
}
}
// 3. 裝置類型
if (stripos($ua, 'Mobile') !== false || stripos($ua, 'Android') !== false) {
$result['device'] = stripos($ua, 'Tablet') !== false || stripos($ua, 'iPad') !== false ? 'Tablet' : 'Mobile';
}
// 4. 瀏覽器與引擎判斷(順序很重要:特定瀏覽器優於通用引擎)
$browserPatterns = [
// 格式: '正則' => ['browser' => 名稱, 'version_group' => 1, 'engine' => 引擎]
'/Edg\/(\d+\.\d+)/' => ['browser' => 'Edge', 'version' => 1, 'engine' => 'Blink'],
'/Chrome\/(\d+\.\d+)/' => ['browser' => 'Chrome', 'version' => 1, 'engine' => 'Blink'],
'/CriOS\/(\d+\.\d+)/' => ['browser' => 'Chrome iOS', 'version' => 1, 'engine' => 'WebKit'],
'/Firefox\/(\d+\.\d+)/' => ['browser' => 'Firefox', 'version' => 1, 'engine' => 'Gecko'],
'/FxiOS\/(\d+\.\d+)/' => ['browser' => 'Firefox iOS', 'version' => 1, 'engine' => 'WebKit'],
'/Safari\/(\d+\.\d+)/' => ['browser' => 'Safari', 'version' => 1, 'engine' => 'WebKit'],
'/Version\/(\d+\.\d+).*Safari/' => ['browser' => 'Safari', 'version' => 1, 'engine' => 'WebKit'],
'/MSIE (\d+\.\d+)/' => ['browser' => 'IE', 'version' => 1, 'engine' => 'Trident'],
'/Trident\/.*rv:(\d+\.\d+)/' => ['browser' => 'IE', 'version' => 1, 'engine' => 'Trident'],
'/Opera\/(\d+\.\d+)/' => ['browser' => 'Opera', 'version' => 1, 'engine' => 'Presto'],
'/OPR\/(\d+\.\d+)/' => ['browser' => 'Opera', 'version' => 1, 'engine' => 'Blink'],
'/SamsungBrowser\/(\d+\.\d+)/' => ['browser' => 'Samsung Internet', 'version' => 1, 'engine' => 'Blink'],
'/UCBrowser\/(\d+\.\d+)/' => ['browser' => 'UC Browser', 'version' => 1, 'engine' => 'Blink'],
'/MQQBrowser\/(\d+\.\d+)/' => ['browser' => 'QQ Browser', 'version' => 1, 'engine' => 'Blink'],
];
foreach ($browserPatterns as $pattern => $info) {
if (preg_match($pattern, $ua, $m)) {
$result['browser'] = $info['browser'];
$result['version'] = $m[$info['version']] ?? '0.0';
$result['engine'] = $info['engine'] ?? $result['engine'];
break;
}
}
return $result;
}
// 使用範例
$info = parseUserAgent();
echo "瀏覽器: {$info['browser']} {$info['version']}\n";
echo "引擎: {$info['engine']}\n";
echo "作業系統: {$info['os']}\n";
echo "裝置: {$info['device']}\n";
?>
<?php
function getBrowserName(string $ua = ''): string {
$ua = $ua ?: ($_SERVER['HTTP_USER_AGENT'] ?? '');
if (str_contains($ua, 'Edg/')) return 'Edge';
if (str_contains($ua, 'Chrome') && !str_contains($ua, 'Edg') && !str_contains($ua, 'OPR')) return 'Chrome';
if (str_contains($ua, 'Firefox')) return 'Firefox';
if (str_contains($ua, 'Safari') && !str_contains($ua, 'Chrome')) return 'Safari';
if (str_contains($ua, 'MSIE') || str_contains($ua, 'Trident/')) return 'Internet Explorer';
if (str_contains($ua, 'OPR') || str_contains($ua, 'Opera')) return 'Opera';
return 'Unknown';
}
?>
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
格式:語言代碼[-地區代碼][;q=權重],權重 0~1,預設 1。按權重降序排列。
<?php
/**
* 解析 Accept-Language,返回按優先級排序的語言代碼陣列
* @return array 如 ['zh-CN', 'zh', 'en-US', 'en']
*/
function parseAcceptLanguage(string $header = ''): array {
$header = $header ?: ($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '');
if (empty($header)) return ['zh-CN']; // 預設
$langs = [];
foreach (explode(',', $header) as $part) {
$part = trim($part);
$q = 1.0;
if (strpos($part, ';q=') !== false) {
[$lang, $qStr] = explode(';q=', $part, 2);
$q = (float)$qStr;
} else {
$lang = $part;
}
$lang = trim($lang);
if ($lang) $langs[$lang] = $q;
}
// 按權重降序排序
arsort($langs);
return array_keys($langs);
}
/**
* 獲取最佳匹配語言(支援應用支援語言白名單)
*/
function getBestLanguage(array $supported = ['zh-CN', 'zh-TW', 'en-US', 'ja', 'ko']): string {
$preferred = parseAcceptLanguage();
foreach ($preferred as $lang) {
// 精確匹配
if (in_array($lang, $supported, true)) return $lang;
// 語言碼匹配(如 zh-CN → zh)
$primary = explode('-', $lang)[0];
foreach ($supported as $sup) {
if (str_starts_with($sup, $primary . '-')) return $sup;
}
}
return $supported[0]; // fallback
}
// 使用範例
$bestLang = getBestLanguage();
echo "最佳語言: $bestLang\n"; // 如 zh-CN
// 設置語系
putenv("LANG=$bestLang");
setlocale(LC_ALL, $bestLang . '.UTF-8');
bindtextdomain('messages', __DIR__ . '/locale');
textdomain('messages');
?>
手寫正則難以覆蓋海量 UA 變體,生產環境強烈建議使用:
composer require ua-parser/uap-php
<?php
require 'vendor/autoload.php';
use UAParser\Parser;
$parser = Parser::create();
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$result = $parser->parse($ua);
echo "瀏覽器: {$result->ua->family} {$result->ua->major}.{$result->ua->minor}\n";
echo "作業系統: {$result->os->family} {$result->os->major}\n";
echo "裝置: {$result->device->family} ({$result->device->brand})\n";
?>
composer require symfony/http-foundation
<?php
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
// 獲取最佳語言(需配置支援語言)
$locale = $request->getPreferredLanguage(['zh_CN', 'zh_TW', 'en_US', 'ja', 'ko']);
// 獲取 User-Agent 物件(需額外安裝 ua-parser 整合)
// $request->headers->get('User-Agent');
?>
| 場景 | 技術方案 | 注意事項 |
|---|---|---|
| 多語系自動切換 | Middleware 解析 Accept-Language → 設定 Session/Cookie/URL 前綴 | 提供手動切換入口,優先級:用戶設定 > Cookie > Header > 預設 |
| 舊版 IE 降級/阻擋 | 偵測 IE ≤ 11 → 顯示升級提示、載入 Polyfill、或導向靜態降級頁 | IE 11 UA 含 "Trident/7.0; rv:11.0",Edge 含 "Edg/" |
| 移動端適配 | 偵測 Mobile/Tablet → 輸出不同模板、CSS、圖片尺寸 | 優先使用響應式設計,UA 偵測作為補充 |
| 爬蟲識別/SEO | 偵測 Bot → 返回純內容、關閉追蹤碼、預渲染頁面 | 需維護 Bot UA 白名單,防止偽裝 |
| 統計分析 | 記錄 UA、語言、OS、裝置 → 數據倉庫分析用戶畫像 | 遵守 GDPR/個資法,匿名化、加密儲存 |
PHP 透過 $_SERVER['HTTP_USER_AGENT'] 與 $_SERVER['HTTP_ACCEPT_LANGUAGE'] 即可實現零依賴的瀏覽器與語言偵測。本文提供了從簡易正則到結構化解析函數的完整實現,並強烈建議生產環境採用 ua-parser/php 或 Symfony HttpFoundation 等成熟庫,以應對龐大且持續演進的 User-Agent 生態。正確的語言協商機制(Header → 用戶設定 → 預設)能顯著提升多語系產品的用戶體驗。