【问题标题】:How to emulate lookbehind within a regex with non-fixed width如何在非固定宽度的正则表达式中模拟lookbehind
【发布时间】:2025-12-30 02:30:07
【问题描述】:

我目前在特定情况下遇到了 Regex 的问题:我需要解析 PHP 源文件(尤其是类)以查找在这些文件中定义的常量并将它们检索回输出。

这些常量可以有一些文档(这就是为什么我放弃了反射的想法,因为通过反射检索常量只会返回它们的名称和值),这些文档可能会在 cmets 标记中提供。

我确实设法构建了正则表达式的两个独立部分(一个是注释标记,另一个是 const 声明)但我无法成功地将它们链接起来:似乎第一个常量文件中还将包含所有先前声明的元素,直到它到达第一个注释块。

我的正则表达式如下(我不是正则表达式大神,请随时提出批评):

((\t\ )*(/\*+(.|\n)*\*/)\R+)?([\t| ]*(?|(public|protected|private)\s*)?const\s+([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*=\s*(.*);)

样本测试如下:Regex101

万一初始代码消失:

/**
*
*/
class Test {

   /**
    *
    */
    public const LOL = "damn";

   /**
    *
    */
    private const TEST = 5;

   public const plop = "dong";
}

我确实到处寻找提示,我已经了解了积极的后视,但据我了解,它只适用于固定宽度的模式。

我的想法不多了。

【问题讨论】:

  • "(这就是为什么我放弃了反射的想法,因为通过反射检索常量只返回它们的名称和值)" – 从 PHP 7.1.0 开始,@ 987654322@ 类可用,您可以使用ReflectionClassConstant::getDocComment() 检索文档注释。 (small example)
  • 嗨@salathe!谢谢你的评论 !很好的通知,但我忘了说那里的开发环境被锁定到 5.4(是的,这很糟糕)
  • 哦,@salathe 我忘记了这一点,但是通过 Reflection* 检索常量的值可能会改变常量声明和结构(例如带有转义双引号的双引号字符串),这就是我没有使用它的原因第一眼

标签: php regex comments constants


【解决方案1】:

我倾向于采用多步骤方法:分离每个类,然后查找 cmets(最终)和常量。就正则表达式而言,这可以通过

class\h*(?P<classname>\w+)[^{}]* # look for class literally and capture the name
(\{
    (?:[^{}]*|(?2))*             # the whole block matches the class content
\})

a demo on regex101.com


现在,到 cmets 和常量
^\h*
(?:(?P<comment>\Q/*\E(?s:.*?)\Q*/\E)(?s:.*?))?
(?:public|private)\h*const\h*
(?P<key>\w+)\h*=\h*(?P<value>[^;]+)

参见a demo for this step on regex101.com


最后一步是清洁 cmets:
^\h*/?\*+\h*/?

查看cleansing on regex101.com 的演示。


最后,您需要两个循环:
preg_match_all($regex_class, $source, $matches, PREG_SET_ORDER);

foreach ($matches as $match) {
    preg_match_all($const_class, $match[0], $constants, PREG_SET_ORDER);
    foreach ($constants as $constant) {
        $comment = preg_replace($clean_comment, '', $constant["comment"]);

        # find the actual values here
        echo "Class: {$match["classname"]}, Constant Name: {$constant["key"]}, Constant Value: {$constant["value"]}, Comment: $comment\n";
    }
}

overall demo can be found on ideone.com.
注意演示和源代码中的各个正则表达式修饰符(尤其是 verbosemultiline !)。


您也可以在数组中执行此操作:
$result = [];
preg_match_all($regex_class, $source, $matches, PREG_SET_ORDER);

foreach ($matches as $match) {
    preg_match_all($const_class, $match[0], $constants, PREG_SET_ORDER);
    foreach ($constants as $constant) {
        $comment = trim(preg_replace($clean_comment, '', $constant["comment"]));
        $result[$match["classname"]][] = array('name' => $constant["key"], 'value' => $constant['value'], 'comment' => $comment);
    }
}

print_r($result);

【讨论】:

  • 嘿@Jan!非常感谢您的回答,这不是我会立即跳到的东西,但我不能否认它运作良好!不知道我是否会选择这个,但这是一个非常酷且完整的答案,非常感谢!
  • @Cr3aHal0:很高兴为您提供帮助。
【解决方案2】:

您可以在没有积极向后看的情况下做到这一点: 你必须匹配一个注释,紧跟一个 const 声明:

(?:(?:^/\*\*$\s+)((?:^ ?\*.*$\s*?)+)(?:\s+^\*/$\s+))?^\s+(public|protected|private) const (\S+)\s+= ([^;]+);

第一组将允许您检索文档:

  • 评论部分
    • (?:^/\*\*$\s+) 查找块注释的开头
    • ((?:^ ?\*.*$\s*?)+) 代表包含您的 cmets 内容的组
    • (?:\s+^\*/$\s+)评论结束
  • 声明部分:
    • ^\s+ 跳过行首的空格
    • (public|protected|private) const 确定可见性的组
    • (\S+)\s+= ([^;]+); 名称和值的组

【讨论】:

  • 嘿@Faibbus,非常感谢您的回答!尽管它并不完全适合每种注释(尤其是可能由某些 IDE 生成的一行 cmets),但它提供了一个很好的开始并且适用于主要案例,所以再次感谢!
  • @Cr3aHal0 :感谢您的反馈。我想删除大部分注释装饰,但我想你可以匹配任何块注释或单行注释。