使用更长的字符串时,我在preg_replace_callback()
函数中收到PREG_JIT_STACKLIMIT_ERROR错误。超过2000个字符,它不是woking(超过2000个字符匹配正则表达式,而不是2000个字符串)
我已经读过它是由效率低下的正则表达式引起的,但我不能使我的正则表达式更简单。这是我的正则表达式:
/\{@([a-z0-9_]+)-((%?[a-z0-9_]+(:[a-z0-9_]+)*)+)\|(((?R)|.)*)@\}/Us
它应匹配以下字符串:
1){@if-statement|echo this|echo otherwise@}
2){@if-statement:sub|echo this|echo otherwise@}
3){@if-statement%statament2:sub|echo this@}
并且还嵌套,如下所示:
4){@if-statement|echo this|
{@if-statement2|echo this|echo otherwise@}
@}
我试图将其简化为:
/\{@([a-z0-9_]+)-([a-z0-9_]+)\|(((?R)|.)*)@\}/Us
但看起来错误是由(((?R)|.)*)
部分引起的。有什么建议吗?
测试代码:
$string = '{@if-is_not_logged_homepage|
<header id="header_home">
<div class="in">
<div class="top">
<h1 class="logo"><a href="/"><img src="/img/logo-home.png" alt=""></a></h1>
<div class="login_outer_wrapper">
<button id="login"><div class="a"><i class="stripe"><i></i></i>Log in</div></button>
<div id="login_wrapper">
<form method="post" action="{^login^}" id="form_login_global">
<div class="form_field no_description">
<label>{!auth:login_email!}</label>
<div class="input"><input type="text" name="form[login]"></div>
</div>
<div class="form_field no_description password">
<label>{!auth:password!}</label>
<div class="input"><input type="password" name="form[password]"></div>
</div>
<div class="remember">
<input type="checkbox" name="remember" id="remember_me_check" checked>
<label for="remember_me_check"><i class="fa fa-check" aria-hidden="true"></i>Remember</label>
</div>
<div class="submit_box">
<button class="btn btn_check">Log in</button>
</div>
</form>
</div>
</div>
</div>
<div class="content clr">
<div class="main_menu">
<a href="">
<i class="ico a"><i class="fa fa-lightbulb-o" aria-hidden="true"></i></i>
<span>Idea</span>
<div> </div>
</a>
<a href="">
<i class="ico b"><i class="fa fa-user" aria-hidden="true"></i></i>
<span>FFa</span>
</a>
<a href="">
<i class="ico c"><i class="fa fa-briefcase" aria-hidden="true"></i></i>
<span>Buss</span>
</a>
</div>
<div class="text_wrapper">
<div>
<div class="register_wrapper">
<a id="main_register" class="btn register">Załóż konto</a>
<form method="post" action="{^login^}" id="form_register_home">
<div class="form_field no_description">
<label>{!auth:email!}</label>
<div class="input"><input type="text" name="form2[email]"></div>
</div>
<div class="form_field no_description password">
<label>{!auth:password!}</label>
<div class="input tooltip"><input type="password" name="form2[password]"><i class="fa fa-info-circle tooltip_open" aria-hidden="true" title="{!auth:password_format!}"></i></div>
</div>
<div class="form_field terms no_description">
<div class="input">
<input type="checkbox" name="form2[terms]" id="terms_check">
<label for="terms_check"><i class="fa fa-check" aria-hidden="true"></i>Agree</label>
</div>
</div>
<div class="form_field no_description">
<div class="input captcha_wrapper">
<div class="g-recaptcha" data-sitekey="{%captcha_public_key%}"></div>
</div>
</div>
<div class="submit_box">
<button class="btn btn_check">{!auth:register_btn!}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</header>
@}';
$if_counter = 0;
$parsed_view = preg_replace_callback( '/\{@([a-z0-9_]+)-((%?[a-z0-9_]+(:[a-z0-9_]+)*)+)\|(((?R)|.)*)@\}/Us',
function( $match ) use( &$if_counter ){
return '<-{'. ( $if_counter ++ ) .'}->';
}, $string );
var_dump($parsed_view); // NULL
答案 0 :(得分:4)
什么是PCRE JIT?
即时编译是一项重量级的优化,可以大大提高 加快模式匹配。但是,它需要额外的费用 在执行比赛之前进行处理。 因此,它是最重要的 当相同的模式多次匹配时会受益。
它基本上是如何工作的?
PCRE(和JIT)是一个递归的,深度优先的引擎,所以它需要一个堆栈 其中当前节点的本地数据在检查之前被推送 子节点...当编译的JIT代码运行时,它需要一个块 用作堆栈的内存。默认情况下,它在计算机上使用 32K 堆。但是,一些大型或复杂的模式需要更多 这个。如果没有,则会给出错误
PCRE_ERROR_JIT_STACKLIMIT
足够的堆栈。
首先引用你会理解JIT是一个可选功能,默认情况下在PHP [v7。*] PCRE中打开。因此,您可以轻松将其关闭:pcre.jit = 0
(虽然不推荐)
但是,在接收6
函数的错误代码#preg_*
时,这意味着JIT可能会达到堆栈大小限制。
由于捕获组比非捕获组消耗更多内存(根据簇的量词类型,更多的内存将用于):
OP_CBRA
(pcre_jit_compile.c:#1138) - (真实记忆不止于此):case OP_CBRA:
case OP_SCBRA:
bracketlen = 1 + LINK_SIZE + IMM2_SIZE;
break;
OP_BRA
(pcre_jit_compile.c:#1134) - (真实
记忆超过这个):case OP_BRA:
bracketlen = 1 + LINK_SIZE;
break;
因此,在您自己的RegEx中将捕获组更改为非捕获组会使其提供正确的输出(我不确切地知道该节省了多少内存)
但似乎你需要捕捉群体并且它们是必要的。然后,为了提高性能,您应该重新编写RegEx。回溯几乎是RegEx中应该考虑的所有内容。
<强>解决方案强>:
(?(DEFINE)
(?<recurs>
(?! {@|@} ) [^|] [^{@|\\]* ( \\.[^{@|\\]* )* | (?R)
)
)
{@
(?<If> \w+)-
(?<Condition> (%?\w++ (:\w+)*)* )
(?<True> [|] [^{@|]*+ (?&recurs)* )
(?<False> [|] (?&recurs)* )?
\s*@}
<强> Live demo 强>
PHP代码(观看反斜杠转义):
preg_match_all('/(?(DEFINE)
(?<recurs>
(?! {@|@} ) [^|] [^{@|\\\\]* ( \\\\.[^{@|\\\\]* )* | (?R)
)
)
{@
(?<If> \w+ )-
(?<Condition> (%?\w++ (:\w+)*)* )
(?<True> [|] [^{@|]*+ (?&recurs)* )
(?<False> [|] (?&recurs)* )?
\s*@}/x', $string, $matches);
这是您自己的RegEx,它以最少的回溯步骤进行优化。因此,你自己应该匹配的任何内容都与此相匹配。
RegEx没有遵循嵌套的if
块:
{@
(?<If> \w+)-
(?<Condition> (%?\w++ (:\w+)*)* )
(?<True> [|] [^|\\]* (?: \\.[^|\\]* )* )
(?<False> [|] \X*)?
@}
大多数量词都是通过向+
附加{{1}}而占有地写入(避免回溯)。
答案 1 :(得分:3)
您可以看到的问题是您的模式效率低下。主要原因是:
(a+)+b
这是DLLScreenCap Sample: Demonstrates a Regular DLL That Statically or Dynamically Links to MFC (a|b)+
这可能是一个很好的设计,除了像pcre这样的回溯正则表达式引擎顺便说一句,有太多无用的捕获组会消耗内存。如果您不需要捕获组,请不要编写它。如果您确实需要对元素进行分组,请使用非捕获组,但不要使用非捕获组来制作模式,并且“更具可读性”。 (还有其他方法可以像命名组,自由空格和评论一样)。
如果我理解得很好,那么您正在尝试为preg_replace_callback
构建一个正则表达式来处理模板系统的控制语句。
由于这些控制语句可以嵌套,并且正则表达式引擎无法匹配相同子字符串的几次,因此您必须在以下几种策略中进行选择:
您可以编写递归模式来描述最终包含其他条件语句的条件语句。
您可以编写仅匹配最内层条件语句的模式。 (换句话说,它禁止嵌套的条件语句。)
在这两种情况下,您需要多次解析字符串,直到无法替换。 (请注意,您也可以使用第一个策略的递归函数,但它会使事情变得更复杂。)
让我们看看第二种方式:
$pattern = '~
{@ (?<cond> \w+ ) - (?<stat> \w+ (?: % \w+ )* ) (?: : (?<sub> \w+ ) )? \|
# a "THEN" part that doesn\'t have nested conditional statements
(?<then> [^{|@]*+ (?: { (?!@) [^{|@]* | @ (?!}) [^{|@]* )*+ )
# optional "ELSE" part (the content is similar to the "THEN" part)
(?: \| (?<else> \g<then> ) )? (*SKIP) @}~x';
$parsed_view = $string;
$count = 0;
do {
$parsed_view = preg_replace_callback($pattern, function ($m) {
// do what you need here. The different captures can be
// easily accessed with their names: $m['cond'], $m['stat']...
// as defined in the pattern.
return $result;
}, $parsed_view, -1, $count);
} while ($count);
正如您所看到的,嵌套语句的问题通过do..while
循环和count
的{{1}}参数来解决,以查看是否有内容被替换。
此代码未经过测试,但我确信您可以完成并最终根据您的需求进行调整。
顺便说一下,已经存在很多模板引擎(PHP已经是模板引擎)。您可以使用它们并避免创建自己的语法。您还可以查看他们的代码。