PHP 正則 preg_match 匹配長度限制

李志成發表於2017-12-21

什麼?有長度限制?我怎麼沒有碰到過?是的,有限制的,總不能無底洞一直執行下去吧!

故事背景

有一天,發現以前使用正規表示式寫的HTML格式轉換程式碼出問題了!壓根沒有進行格式轉換!但是把部分內容(任意內容)刪除之後,就可以匹配成功並且正常轉換。於是我想這應該是長度限制,導致無法匹配結果。

於是,在網上搜尋了一下,發現的確是有這個限制:

搜尋關鍵詞: preg_match 長度限制

得到結果:

// preg_match有字串長度限制,果然,發現最大回溯“pcre.backtrack_limit ”的值預設只設了100000。 正則的回溯是什麼?這個不是本文重點,有需要可以自行在網上查詢

// 解決辦法:
ini_set('pcre.backtrack_limit', 999999999);

// 注:這個引數在php 5.2.0版本之後可用。

// 另外說說關於:pcre.recursion_limit

//pcre.recursion_limit是PCRE的遞迴限制,這個項如果設很大的值,會消耗所有程式的可用堆疊,最後導致PHP崩潰。

// 也可以透過修改配置來限制:
ini_set('pcre.recursion_limit', 99999);

但是,我都設定還是不行。於是發現這個preg_last_error函式:
preg_last_error官方解釋 :返回最後一個PCRE正則執行產生的錯誤程式碼

於是在preg_match_all之後執行preg_last_error得到結果6!!

PORE預定義常量中,把所有常量列印一遍,發現 是 PREG_JIT_STACKLIMIT_ERROR。問題找到了,接下來是解決問題~

// 這裡應該按照需要設定,儘量不要設定0
ini_set('pcre.jit', 0);

就這樣解決了!

程式碼(故事主人公):

// ini_set('pcre.jit', 0);

//不要管這坨東西,當時沒想那麼多。就別問我為什麼不用(?P<name>)語法。
$pattern = '/<span([^<\/>]*)>(([^<>]|<br\/>)*<span[^<\/>]*>([^<>]|<br\/>)*<\/span>([^<>]|<br\/>)*)+<\/span>/i';

$content = '<span style="font-size: 24px;">
1、新增功能&nbsp;&nbsp;
<br/>1)新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>2)新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>3)新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能
<br/>新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>
<br/>2、新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>1)新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能
<br/>2)新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能
<br/>
<span style="font-size: 24px; color: rgb(255, 0, 0);">溫馨提示:新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;</span>
<br/>
<br/>3、新增功能新增功能&nbsp;&nbsp;
<br/>1)新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/> 2)新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>
<br/>4、新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp; 新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>
<br/>5、新增功能新增功能新增功能新增功能新增功能新增功能新增功能&nbsp;&nbsp;
<br/>6、新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能新增功能
</span>
';

preg_match_all($pattern, $content, $matches);
// 輸出 preg_match_all 的錯誤碼 0 表示沒有錯誤
var_dump(preg_last_error());
$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,
];

// 輸出錯誤對照表
var_dump($error);

// 檢視是否有匹配內容
var_dump($matches);

上面程式碼,最終執行失敗,preg_last_error() 得到結果:6。解決方案:

  • 要麼刪除中間部分內容
  • 要麼去除上面ini_set('pcre.jit', 0);的註釋

總結:

  1. preg_match等正則匹配是有各種限制的,最好使用 preg_last_error 跟蹤一下錯誤。
  2. preg_matchpreg_match_all 返回匹配的次數(有可能為0),但是失敗也會返回false。因此需要注意返回值
  3. 根本原因是:我寫的正則效率太低了
    • 沒有使用固定分組,導致回溯次數過多
    • 正則過長。正則過長就會匹配更多內容,如果不使用固定分組,那麼就會回溯次數過多。
  4. 如果可以,將一個複雜的正則簡單化,甚至拆分為簡單的正則

簡單最佳化:

// 需要最佳化 因為回溯次數過多也會導致匹配失敗的
$pattern = '/<span([^<\/>]*)>(([^<>]|<br\/>)*<span[^<\/>]*>([^<>]|<br\/>)*<\/span>([^<>]|<br\/>)*)+<\/span>/i';

// 簡單最佳化:使用固定分組。
$pattern = '/<span([^\<\>]*)>(?>(?>[^\<\>]|<br\/>)*<span[^\<\>]*>(?>[^\<\>]|<br\/>)*<\/span>(?>[^\<\>]|<br\/>)*)+<\/span>/i';

php中正規表示式詳解

使用固定分組之後不僅速度快了,而且避免了正則匹配的限制而導致匹配失敗

本作品採用《CC 協議》,轉載必須註明作者和本文連結
有什麼想法歡迎提問或者資訊

相關文章