UA字符串不可信,直接解析$_SERVER['HTTP_USER_AGENT']易出错,因其可能为空、被截断、屏蔽或伪造;应优先识别移动端和微信等关键容器,再按确定性顺序匹配浏览器,生产环境推荐使用whichbrowser/parser等专业库,并以特性检测替代UA判断。

别用 get_browser(),也别靠 strpos() 硬匹配 “Chrome” 就下结论——UA 字符串是不可信的字符串,不是浏览器身份证。
为什么直接解析 $_SERVER['HTTP_USER_AGENT'] 容易出错
这个字段只是客户端发来的原始字符串,可能为空、被截断、被隐私浏览器屏蔽,甚至被 curl 或爬虫伪造。比如 Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) 看似 iOS,但实际可能是安卓微信 WebView 里塞进来的;HarmonyOS、HMSCore、MiuiBrowser 这类新关键词根本不在老正则覆盖范围内。更麻烦的是,Chrome 和 Safari 都带 AppleWebKit 和 Safari,只看 Safari/ 会把 Chrome 误判为 Safari。
移动端和微信容器要优先单独识别
多数业务真正需要的不是“Chrome 124.0.6367”,而是“要不要加载移动端 JS”或“能不能调用微信 JS-SDK”。这类判断比精确版本号重要得多,且更稳定。
- 先检查
isset($_SERVER['HTTP_USER_AGENT']),为空就跳过全部 UA 逻辑 - 用
preg_match('/(Android|iPhone|iPod|iPad|Mobile)/i', $ua)判定是否移动设备(注意i标志处理大小写) - 微信必须单独抓:
preg_match('/MicroMessenger/i', $ua)—— iOS 和安卓微信都含这个词,但渲染能力、JS API 差异极大 - 别漏掉
MQQBrowser(QQ 浏览器),它常伪装成 Safari,但对现代 CSS/JS 支持极弱
真要解析浏览器名和版本,得按优先级顺序匹配
Chrome、Edge、Opera 都可能共用 Chrome/ 或 Edg/,必须按确定性从高到低排:先 Edge(Edg/ 或 Edge/),再 Opera(OPR/ 或 Opera/),再 Chrome(但要排除含 Edg 或 OPR 的 UA),最后才是 Safari 和 Firefox。IE 要同时匹配 MSIE 和 Trident。
立即学习“PHP免费学习笔记(深入)”;
示例关键片段:
if (preg_match('/Edg\/([0-9.]+)/i', $ua, $m)) {
$name = 'Edge';
$version = $m[1];
} elseif (preg_match('/OPR\/([0-9.]+)/i', $ua, $m)) {
$name = 'Opera';
$version = $m[1];
} elseif (preg_match('/Chrome\/([0-9.]+)/i', $ua, $m) && !preg_match('/Edg|OPR/i', $ua)) {
$name = 'Chrome';
$version = $m[1];
} elseif (preg_match('/Firefox\/([0-9.]+)/i', $ua, $m)) {
$name = 'Firefox';
$version = $m[1];
} elseif (preg_match('/Safari\/([0-9.]+)/i', $ua, $m) && !preg_match('/Chrome|Edg|OPR|Firefox/i', $ua)) {
$name = 'Safari';
$version = $m[1];
}
生产环境该用什么方案
自己维护正则很快会过时。如果项目确实需要较准识别(比如灰度新特性、上报统计),用 Composer 引入 sinergi/browser-detector 或 whichbrowser/parser。它们不依赖外部 ini 文件,规则靠代码更新,兼容性好,且能处理鸿蒙、Tizen、WebView 等边缘情况。但注意:这些库仍基于 UA 字符串,不能替代 feature detection —— 比如想用 :has() 伪类,不如直接 JS 检测 CSS.supports('selector(:has(*))')。
最常被忽略的一点:UA 判断结果永远不该决定核心功能是否可用,只适合做渐进增强或降级策略的辅助信号。



















