php preg_match_all返回数组数组

don*_*ame 3 php regex

我想替换一些模板标签:

$tags = '{name} text {first}';
preg_match_all('~\{(\w+)\}~', $tags, $matches);
var_dump($matches);
Run Code Online (Sandbox Code Playgroud)

输出是:

array(2) { 
          [0]=> array(2) { 
                         [0]=> string(6) "{name}" 
                         [1]=> string(7) "{first}" 
                         } 
          [1]=> array(2) { 
                         [0]=> string(4) "name" 
                         [1]=> string(5) "first" 
                         }
         }
Run Code Online (Sandbox Code Playgroud)

为什么有2个阵列?如何实现只有第二个?

Eli*_*gem 5

排序答案:

还有其他选择吗?当然有:lookaround断言允许您轻松使用零宽度(非捕获)单个字符匹配:

preg_match_all('/(?<=\{)\w+(?=})/', $tags, $matches);
var_dump($matches);
Run Code Online (Sandbox Code Playgroud)

将转储此:

array(1) {
  [0]=>
  array(2) {
    [0]=>
    string(4) "name"
    [1]=>
    string(5) "first"
  }
}
Run Code Online (Sandbox Code Playgroud)

模式:

  • (?<=\{):正面lookbehind - 如果{前面有一个字符(但不捕获它),则只匹配模式的其余部分
  • \w+:单词字符是匹配
  • (?=}):如果后跟一个}字符(但不捕获}字符),则仅匹配前一个模式

就这么简单:模式使用{}分隔符字符作为匹配的条件,但不捕获它们

$matches稍微解释一下这个数组结构:

之所以$matches看起来很简单:使用时preg_match(_all),匹配数组中的第一个条目将始终是给定正则表达式匹配的整个字符串.这就是我使用零宽度环视断言而不是组的原因.您的表达式完全匹配"{name}",并"name"通过分组提取.
匹配数组将保持索引的完全匹配0,并在每个后续索引处添加组,在您的情况下,这意味着:

  • $matches[0]将包含匹配/\{\w+\}/为模式的所有子字符串.
  • $matches[1]将包含捕获的所有子串(/\{(\w+)\}/捕获(\w+)).

如果你有这样的正则表达式:/\{((\w)([^}]+))}/matches数组看起来像这样:

[
    0 => [
        '{name}',//as if you'd written /\{\w[^}]+}/
    ],
    1 => [
        'name',//matches group  (\w)([^}]+), as if you wrote (\w[^}]+)
    ],
    2 => [
        'n',//matches (\w) group
    ],
    3 => [
        'ame',//and this is the ([^}]+) group obviously
    ]
]
Run Code Online (Sandbox Code Playgroud)

为什么?很简单,因为模式包含3个匹配的组.就像我说的:匹配数组中的第一个索引将始终是完全匹配,无论捕获组如何.然后按照表达式中出现的顺序将这些组附加到数组中.所以,如果我们分析表达式:

  • \{:不匹配,但模式的一部分,只会在$matches[0]值中
  • ((\w)([^}]+)):第一个匹配组的开始,\w[^}]+匹配在此处分组,$matches[1]将包含这些值
  • (\w):第二组,一个\w字符(即后面的第一个字符{.$matches[2]因此将包含一个后面的所有第一个字符{
  • ([^}]+):第三组,在遇到{\wa 之后匹配其余的字符串},这将得出$matches[3]

为了更好地理解,并能够预测$matches将填充的方式,我强烈建议您使用此站点:regex101.在那里写下你的表达,它会在右侧为你打破,列出组.例如:

/\{((\w)([^}]+))}/
Run Code Online (Sandbox Code Playgroud)

像这样分解:

/\{((\w)([^}]+))}/
  \{ matches the character { literally
  1st Capturing group ((\w)([^}]+))
    2nd Capturing group (\w)
      \w match any word character [a-zA-Z0-9_]
    3rd Capturing group ([^}]+)
      [^}]+ match a single character not present in the list below
      Quantifier: + Between one and unlimited times, as many times as possible, giving back as needed [greedy]
      } the literal character }
  } matches the character } literally
Run Code Online (Sandbox Code Playgroud)

查看捕获组,您现在可以自信地说出$matches会是什么样子,并且您可以放心地说这$matches[2]将是一个单个字符数组.

当然,这可能会让你想知道为什么$matches是2D阵列.那么,这又是非常简单的:您可以预测的是$matches数组将包含多少匹配索引:1表示完整模式,然后+1表示每个捕获组.但是,你无法预测的是你会发现多少匹配.
那么preg_match_all真正非常简单:填充$matches[0]与整个模式匹配的所有子串,然后从这些匹配中提取每个组子串并将该值附加到相应的$matches数组上.换句话说,您可以找到的数组的数量$matches是给定的:它取决于模式.您可以在子数组中找到的键数$matches是未知的,这取决于您正在处理的字符串.如果preg_match_all 要返回一维数组,处理匹配将会困难得多,现在你可以简单地写一下:

$total = count($matches);
foreach ($matches[0] as $k => $full) {
    echo $full . ' contains: ' . PHP_EOL;
    for ($i=1;$i<$total;++$i) {
        printf(
            'Group %d: %s' . PHP_EOL,
            $i, $matches[$i][$k]
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

如果preg_match_all创建了一个平面数组,则必须跟踪模式中的组数量.每当模式发生变化时,您还必须确保更新其余代码以反映对模式所做的更改,从而使代码更难维护,同时使代码更容易出错.