開發關鍵字高亮功能時,本地測試正常,上線卻出現大量亂碼甚至死迴圈。經排查,根因在於 PHP 早期版本(PHP 5.6 以前)核心採用單位元組字串處理,不支援 Unicode,導致中文在正則運算時被拆解為多個位元組,產生預期外的匹配結果。本文深度剖析問題本質,提供多種相容性解決方案。
$pattern = '/[漢字]+/ism';
$subject = '我是漢字,看你把我怎麼著!';
preg_match_all($pattern, $subject, $matches);
// 結果:大量亂碼、甚至死迴圈
原因:[漢字] 在單位元組模式下,會將「漢」(3-4 bytes)與「字」(3-4 bytes)拆解為 6-8 個獨立位元組,字符類別實際匹配的是這些位元組的任意組合,完全偏離原意。
eregi_replace('性', '沒有', '有責任感');
// 預期:無「性」字,回傳原字串
// 實際:'用揮敘任感' —— 「責」字第二位元組與「性」第一位元組相同,被錯誤替換
/u 模式前,逐位元組匹配,不知曉字元邊界。ereg/eregi 系列更是純位元組操作,完全不支援多位元組。| 方案 | 適用版本 | 優點 | 缺點 | 推薦度 |
|---|---|---|---|---|
PCRE + /u 修飾符preg_match_all('/漢字+/u', $str, $m) |
PHP 4.1.0+ | 原生支援 Unicode,語意清晰,效能佳 | 需確保輸入為合法 UTF-8;PHP 5.3 以下部分邊界情況有 bug | ★★★★★ |
明確列舉替代字符類別preg_match_all('/(漢|字)+/ism', $str, $m) |
所有版本 | 不依賴 /u,相容舊版本 | 字元多時模式極長;無法處理任意中文 | ★★☆☆☆ |
mb_ereg 系列mb_ereg_match('漢字+', $str) |
PHP 4.2.0+ (需 mbstring) | 原生多位元組感知,支援多種編碼 | 語法與 PCRE 差異大;效能較慢;需設定 mb_regex_encoding() | ★★★☆☆ |
字串分割後逐字處理preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY) |
PHP 5.0+ | 將字串轉為字元陣列,繞過正則匹配問題 | 額外記憶體與 CPU 開銷 | ★★★★☆ |
| 升級 PHP 7.0+ / 8.x | 現代專案 | 核心全面 Unicode 化,preg_* 預設 UTF-8 感知,移除 ereg |
舊專案遷移成本 | ★★★★★ |
<?php
function highlight_keywords($text, $keywords, $tag = 'mark') {
if (empty($keywords) || empty($text)) return $text;
// 確保關鍵字為陣列
$keywords = is_array($keywords) ? $keywords : explode(' ', $keywords);
// 過濾空值、排序(長優先,避免部分替換干擾)
$keywords = array_filter($keywords);
usort($keywords, function($a, $b) { return strlen($b) - strlen($a); });
// 建構模式:使用 /u 修飾符,預期輸入為 UTF-8
$pattern = '/(' . implode('|', array_map('preg_quote', $keywords)) . ')/u';
// 替換
return preg_replace($pattern, "<$tag>$0$tag>", $text);
}
// 測試
$text = 'PHP 中文正則匹配與替換陷阱:多位元組字元導致亂碼';
$keywords = ['正則', '中文', '位元組', '亂碼'];
echo highlight_keywords($text, $keywords);
// 輸出:PHP 中文正則匹配與替換陷阱:多位元組字元導致亂碼
?>
SET NAMES utf8mb4(而非 utf8)。<meta charset="UTF-8">。/u 修飾符。ereg/eregi/split 等已移除函數,全面改用 preg_* 或 mb_*。mb_substr()、mb_strlen() 等多位元組安全函數處理中文截取/長度。PHP 早期單位元組架構導致中文正則匹配頻發亂碼、誤替換、死迴圈。核心解法是啟用 PCRE 的 /u 修飾符,配合全鏈路 UTF-8 編碼一致性。現代 PHP 7/8 已從核心層面解決此問題,新專案請直接升級;維護舊專案則需嚴守「加 /u、用 preg_*、統一 UTF-8」三原則。