當正則觸發 **回溯限制 (pcre.backtrack_limit)** 或 **遞迴限制 (pcre.recursion_limit)** 時,preg_match()、preg_replace() 等回傳 null (非 false),且不拋出例外。**必須主動檢查 preg_last_error()**。
| 常數 | 值 | 含義 | 常見原因 |
|---|---|---|---|
| PREG_NO_ERROR | 0 | 無錯誤 | 正常 |
| PREG_INTERNAL_ERROR | 1 | PCRE 內部錯誤 | 極罕見,可能正則過於複雜導致內部堆疊溢位 |
| PREG_BACKTRACK_LIMIT_ERROR | 2 | **回溯次數超限** | 預設 1,000,000;惡意/低效正則 (如 (.*)*) 配合長字串觸發 |
| PREG_RECURSION_LIMIT_ERROR | 3 | **遞迴深度超限** | 預設 100,000;深層巢狀群組、長字串匹配 |
| PREG_BAD_UTF8_ERROR | 4 | UTF-8 字串無效 | 輸入含非法 UTF-8 位元組,需 mb_check_encoding() 預檢 |
| PREG_BAD_UTF8_OFFSET_ERROR | 5 | UTF-8 偏移量無效 | preg_match 配合 PREG_OFFSET_CAPTURE 時,偏移落在多位元組字元中間 |
| PREG_JIT_STACKLIMIT_ERROR | 6 | JIT 堆疊超限 (PHP 7.3+) | 開啟 PCRE JIT 時,複雜正則超過 JIT 堆疊大小 |
$pattern = '/your-regex/';
$subject = $templateContent;
$result = preg_replace($pattern, $replacement, $subject);
if (is_null($result)) { // 關鍵:null 表示錯誤,false 表示無匹配
$errCode = preg_last_error();
$errMsg = [
PREG_NO_ERROR => 'PREG_NO_ERROR',
PREG_INTERNAL_ERROR => 'PREG_INTERNAL_ERROR',
PREG_BACKTRACK_LIMIT_ERROR => 'PREG_BACKTRACK_LIMIT_ERROR',
PREG_RECURSION_LIMIT_ERROR => 'PREG_RECURSION_LIMIT_ERROR',
PREG_BAD_UTF8_ERROR => 'PREG_BAD_UTF8_ERROR',
PREG_BAD_UTF8_OFFSET_ERROR => 'PREG_BAD_UTF8_OFFSET_ERROR',
PREG_JIT_STACKLIMIT_ERROR => 'PREG_JIT_STACKLIMIT_ERROR',
][$errCode] ?? 'UNKNOWN_ERROR';
// 輸出完整上下文,極大加速排查
error_log("正則錯誤: $errMsg (code: $errCode)");
error_log("Pattern: $pattern");
error_log("Subject length: " . strlen($subject));
error_log("Subject preview (first 500): " . substr($subject, 0, 500));
error_log("ini pcre.backtrack_limit: " . ini_get('pcre.backtrack_limit'));
error_log("ini pcre.recursion_limit: " . ini_get('pcre.recursion_limit'));
error_log("ini pcre.jit: " . ini_get('pcre.jit'));
// 可選:拋出異常中斷流程
throw new RuntimeException("Template regex failed: $errMsg");
}
// php.ini 或執行時調整 (謹慎,影響全域)
ini_set('pcre.backtrack_limit', '10000000'); // 1000 萬
ini_set('pcre.recursion_limit', '1000000'); // 100 萬
// 或針對單次調用 (PHP 7.3+ 不支援單次覆蓋,僅全域)
**更好做法**:優化正則,**避免災難性回溯 (Catastrophic Backtracking)**。
.* → [^<]* (匹配非標籤字元)、\d+ 而非 .+。*+、++、?+、{n,m}+ (PHP 支援),匹配失敗不回溯。(?>...) 內部匹配完成不回溯外部。(a*)*、(.+)+ 等指數級回溯陷阱。^、$、\A、\z 縮小搜尋範圍。Discuz 大量使用 preg_replace 處理模板標籤,透過**嚴格限制標籤格式** (如 {template header} 固定前綴)、**避免貪婪匹配跨行**、**預編譯正則陣列** 等方式,在預設限制下穩定運行。參考其 template_compile.php 實現可學習工業級模板引擎正則設計。
**is_null($result) 檢查 + preg_last_error() 完整輸出 (錯誤碼、Pattern、Subject 前段、ini 設定)** 是模板引擎正則除錯鐵律。優化正則結構優於單純調大限制,兼顧效能與穩定。