首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP preg_match 字串長度限制問題與解決方案

     編輯:PHP 綜合知識

問題現象

對大型字串 (如完整 HTML 頁面、大型 JSON、日誌) 使用 preg_match() 或 preg_match_all() 時,函數靜默失敗回傳 false,preg_last_error() 回傳 PREG_BACKTRACK_LIMIT_ERROR (錯誤碼 5)。

根本原因

PCRE 引擎為防止災難性回溯,設有兩項限制 (PHP 5.2.0+ 可調整):

設定項預設值用途
pcre.backtrack_limit1,000,000回溯步數上限,防止正則效能指數級惡化
pcre.recursion_limit100,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');
?>

方案二:php.ini 永久調整 (需重載 PHP-FPM)

; /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";
?>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved