對大型字串 (如完整 HTML 頁面、大型 JSON、日誌) 使用 preg_match() 或 preg_match_all() 時,函數靜默失敗回傳 false,preg_last_error() 回傳 PREG_BACKTRACK_LIMIT_ERROR (錯誤碼 5)。
PCRE 引擎為防止災難性回溯,設有兩項限制 (PHP 5.2.0+ 可調整):
| 設定項 | 預設值 | 用途 |
|---|---|---|
pcre.backtrack_limit | 1,000,000 | 回溯步數上限,防止正則效能指數級惡化 |
pcre.recursion_limit | 100,000 | 遞迴深度上限,防止堆疊溢位 |
<?php
// 在 preg_match() 呼叫前設定
ini_set('pcre.backtrack_limit', '10000000'); // 1000 萬
ini_set('pcre.recursion_limit', '1000000'); // 100 萬
$pattern = '/.../';
$result = preg_match($pattern, $hugeString);
// 用完可還原 (非必要)
ini_restore('pcre.backtrack_limit');
ini_restore('pcre.recursion_limit');
?>
; /etc/php/8.1/fpm/php.ini
pcre.backtrack_limit = 10000000
pcre.recursion_limit = 1000000
調高限制只是治標,優化正則才能治本:
<?php
// ❌ 惡劣模式:貪婪量詞 + 回溯陷阱
$bad = '/.*<\/div>/s';
// ✅ 優化:非貪婪 + 明確邊界
$good = '/.*?<\/div>/s';
// ✅ 更好:使用否定字元類別避免回溯
$better = '/[^<]*(?:<(?!\/div>)[^<]*)*<\/div>/s';
// ✅ 最佳:不該用正則解析 HTML,改用 DOMDocument
$dom = new DOMDocument();
@$dom->loadHTML($html);
$divs = $dom->getElementsByTagName('div');
記憶體保護建議
大字串處理時同時限制記憶體,防止 OOM:
<?php
// 設定合理上限 (視可用記憶體調整)
ini_set('memory_limit', '256M');
// 組合保護
ini_set('pcre.backtrack_limit', '5000000');
ini_set('pcre.recursion_limit', '500000');
try {
$result = preg_match_all($pattern, $largeContent, $matches);
} catch (Throwable $e) {
error_log("Regex failed: " . $e->getMessage());
// 降級處理:分塊處理、改用流式解析器等
}
進階:分塊處理超大字串
若字串超過 10MB,建議分塊處理而非一次載入:
<?php
function pregMatchChunked($pattern, $filePath, $chunkSize = 1024 * 1024) // 1MB
{
$handle = fopen($filePath, 'r');
if (!$handle) return false;
$buffer = '';
$overlap = 1024; // 重疊區確保跨塊匹配
while (!feof($handle)) {
$chunk = fread($handle, $chunkSize);
$buffer .= $chunk;
// 保留重疊部分供下一輪
if (strlen($buffer) > $chunkSize) {
$searchPart = substr($buffer, 0, -$overlap);
if (preg_match($pattern, $searchPart, $matches)) {
fclose($handle);
return $matches;
}
$buffer = substr($buffer, -$overlap);
}
}
// 處理剩餘緩衝
if ($buffer && preg_match($pattern, $buffer, $matches)) {
fclose($handle);
return $matches;
}
fclose($handle);
return false;
}
?>
檢查錯誤碼對照表
<?php
$result = @preg_match($pattern, $subject);
$error = preg_last_error();
$errors = [
PREG_NO_ERROR => '無錯誤',
PREG_INTERNAL_ERROR => '內部錯誤',
PREG_BACKTRACK_LIMIT_ERROR => '回溯限制超過 (pcre.backtrack_limit)',
PREG_RECURSION_LIMIT_ERROR => '遞迴限制超過 (pcre.recursion_limit)',
PREG_BAD_UTF8_ERROR => 'UTF-8 字串無效',
PREG_BAD_UTF8_OFFSET_ERROR => 'UTF-8 偏移量無效',
PREG_JIT_STACKLIMIT_ERROR => 'JIT 堆疊限制',
];
echo $errors[$error] ?? "未知錯誤碼: $error";
?>
- 上一篇文章: PHPThumb 圖片處理庫完整使用指南
- 下一篇文章: PHP 字串大小寫轉換函數完整速查表