PHP模板编译器@if指令正则问题:如何匹配递归括号内容?
解决PHP模板@if指令括号匹配问题
你的问题根源是原正则未处理括号的嵌套(哪怕是单层闭合括号),导致提前截断了条件内容。这里给你两个可行的解决方案:
方案一:修正正则表达式(支持嵌套括号、全局多行匹配)
用PCRE的平衡组语法可以精准匹配括号内的完整条件,同时兼容全局和多行场景:
代码实现
public function handleIf($content) { // 核心正则:匹配@if(...),支持嵌套括号,带全局/多行修饰符 $pattern = '/@if\(\s*( (?&expr) )\s*\)/xms'; // 定义平衡组规则,匹配合法的PHP表达式(含任意嵌套括号) $pattern .= ' (?(DEFINE) (?<expr> (?: [^()]+ | \( (?&expr) \) )* ) ) '; // 替换为标准PHP if结构 return preg_replace($pattern, '<?php if($1) { ?>', $content); }
关键说明
(?&expr)递归引用预定义的表达式规则,完美处理嵌套括号(比如@if( (a > b) && !empty($c) ))/xms修饰符:x忽略正则中的空白(方便写注释),m启用多行模式,s让.匹配换行符\s*忽略括号前后的空白,兼容@if( !empty($title) )这类带空格的写法
测试你的示例@if(!empty($title)),会正确输出<?php if(!empty($title)) { ?>。
方案二:词法分析(Tokenize)
如果你的模板会用到复杂表达式(比如多嵌套括号、逻辑运算符组合),正则可能仍有局限,推荐用PHP自带的语法分析器处理:
代码实现
public function parseIfDirective($content) { $offset = 0; while (($pos = strpos($content, '@if(', $offset)) !== false) { $start = $pos + 4; // 跳过"@if("的长度 // 借助PHP的tokenizer解析表达式 $tokenStream = token_get_all('<?php ' . substr($content, $start)); $parenCount = 1; $endOffset = $start; foreach ($tokenStream as $token) { if (is_array($token)) { $endOffset += strlen($token[1]); } else { if ($token === '(') $parenCount++; if ($token === ')') $parenCount--; $endOffset++; } // 找到匹配的闭合括号时停止 if ($parenCount === 0) break; } // 提取完整条件内容 $condition = substr($content, $start, $endOffset - $start - 1); // 替换为标准PHP if语法 $replaceStr = "<?php if($condition) { ?>"; $content = substr_replace($content, $replaceStr, $pos, $endOffset - $pos); // 更新偏移量,继续处理下一个@if $offset = $pos + strlen($replaceStr); } return $content; }
这种方法直接利用PHP的语法解析能力,完全避免正则的匹配盲区,适合复杂模板场景。
内容的提问来源于stack exchange,提问作者Splashsky
相关产品推荐
相关产品推荐

