关于php:用preg_replace_callback替换preg_replace()e修饰符

Replace preg_replace() e modifier with preg_replace_callback

我很讨厌正则表达式。 我正在尝试替换为:

1
2
3
public static function camelize($word) {
   return preg_replace('/(^|_)([a-z])/e', 'strtoupper("\\2")', $word);
}

与带有匿名函数的preg_replace_callback一起使用。 我不知道\ 2在做什么。 或就此而言,preg_replace_callback的工作方式完全一样。

实现该目标的正确代码是什么?


在正则表达式中,您可以使用(brackets)"捕获"匹配的字符串部分;在这种情况下,您正在捕获匹配的(^|_)([a-z])部分。这些从1开始编号,因此您具有后向引用1和2。匹配项0是整个匹配的字符串。

/e修饰符采用替换字符串,并用适当的反引用替换反斜杠后跟数字(例如\1)-但由于您位于字符串中,因此需要转义反斜杠,因此得到< x5>。然后,它(有效地)运行eval来运行结果字符串,就好像它是PHP代码一样(这就是为什么不推荐使用它的原因,因为它很容易以不安全的方式使用eval)。

相反,preg_replace_callback函数采用回调函数,并将包含匹配的反向引用的数组传递给它。因此,在您要编写'\\1'的位置,您可以访问该参数的元素1-例如如果您具有格式为function($matches) { ... }的匿名函数,则第一个向后引用是该函数内的$matches[1]

所以/e的参数

1
'do_stuff(\\1) ."and" . do_stuff(\\2)'

可能成为

1
function($m) { return do_stuff($m[1]) ."and" . do_stuff($m[2]); }

还是你的情况

1
'strtoupper("\\2")'

可能成为

1
function($m) { return strtoupper($m[2]); }

请注意,$m$matches并不是魔术名称,它们只是我在声明回调函数时提供的参数名称。另外,您不必传递匿名函数,它可以是字符串形式的函数名称,也可以是array($object, $method)形式的形式,例如PHP中的任何回调。

1
2
3
4
function stuffy_callback($things) {
    return do_stuff($things[1]) ."and" . do_stuff($things[2]);
}
$foo = preg_replace_callback('/([a-z]+) and ([a-z]+)/', 'stuffy_callback', 'fish and chips');

与任何函数一样,默认情况下,您无法在回调之外(从周围的范围)访问变量。使用匿名函数时,可以使用use关键字导入需要访问的变量,如PHP手册中所述。例如如果以前的论据是

1
'do_stuff(\\1, $foo)'

那么新的回调可能看起来像

1
function($m) use ($foo) { return do_stuff($m[1], $foo); }

陷阱

  • 使用preg_replace_callback代替了正则表达式上的/e修饰符,因此您需要从"模式"参数中删除该标志。因此,像/blah(.*)blah/mei这样的模式将变成/blah(.*)blah/mi
  • /e修饰符在内部在参数上使用了addslashes()的变体,因此某些替代使用stripslashes()删除了它。在大多数情况下,您可能希望从新的回调中删除对stripslashes的调用。

带评估功能的preg_replace垫片

这是非常不可取的。但是,如果您不是程序员,或者真的不喜欢糟糕的代码,则可以使用替代的preg_replace函数使/e标志暂时保持工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * Can be used as a stopgap shim for preg_replace() calls with /e flag.
 * Is likely to fail for more complex string munging expressions. And
 * very obviously won't help with local-scope variable expressions.
 *
 * @license: CC-BY-*.*-comment-must-be-retained
 * @security: Provides `eval` support for replacement patterns. Which
 *   poses troubles for user-supplied input when paired with overly
 *   generic placeholders. This variant is only slightly stricter than
 *   the C implementation, but still susceptible to varexpression, quote
 *   breakouts and mundane exploits from unquoted capture placeholders.
 * @url: https://stackoverflow.com/q/15454220
 */

function preg_replace_eval($pattern, $replacement, $subject, $limit=-1) {
    # strip /e flag
   $pattern = preg_replace('/(\W[a-df-z]*)e([a-df-z]*)$/i', '$1$2', $pattern);
    # warn about most blatant misuses at least
   if (preg_match('/\(\.[+*]/', $pattern)) {
        trigger_error("preg_replace_eval(): regex contains (.*) or (.+) placeholders, which easily causes security issues for unconstrained/user input in the replacement expression. Transform your code to use preg_replace_callback() with a sane replacement callback!");
    }
    # run preg_replace with eval-callback
   return preg_replace_callback(
        $pattern,
        function ($matches) use ($replacement) {
            # substitute $1/$2/… with literals from $matches[]
           $repl = preg_replace_callback(
                '/(?<!\\\\)(?:[$]|\\\\)(\d+)/',
                function ($m) use ($matches) {
                    if (!isset($matches[$m[1]])) { trigger_error("No capture group for '$m[0]' eval placeholder"); }
                    return addcslashes($matches[$m[1]], '"\'\`\$\\\0'); # additionally escapes '$' and backticks
               },
                $replacement
            );
            # run the replacement expression
           return eval("return $repl;");
        },
        $subject,
        $limit
    );
}

本质上,您只需在代码库中包含该函数,然后编辑preg_replace
到使用/e标志的任何位置的preg_replace_eval

利弊:

  • 真的只是用Stack Overflow的一些样本进行了测试。
  • 仅支持简单情况(函数调用,不支持变量查找)。
  • 包含更多限制和咨询通告。
  • 对于表达式失败,将产生错位且难以理解的错误。
  • 但是,这仍然是一个可用的临时解决方案,不会使向preg_replace_callback的正确过渡复杂化。
  • 许可证注释只是为了防止人们过度使用或传播过多此类许可证。

替换代码生成器

现在,这有点多余。但是可能会帮助那些仍然不知所措的用户
手动将其代码重构为preg_replace_callback。虽然这实际上更耗时,但是代码生成器将/e替换字符串扩展为表达式的麻烦较少。这是一个非常不起眼的转换,但对于大多数流行的示例而言,可能就足够了。

要使用此功能,请将任何损坏的preg_replace调用编辑为preg_replace_eval_replacement并运行一次。这将打印出相应的preg_replace_callback块以在其位置使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * Use once to generate a crude preg_replace_callback() substitution. Might often
 * require additional changes in the `return …;` expression. You'll also have to
 * refit the variable names for input/output obviously.
 *
 * >>>  preg_replace_eval_replacement("/\w+/", 'strtopupper("$1")', $ignored);
 */

function preg_replace_eval_replacement($pattern, $replacement, $subjectvar="IGNORED") {
    $pattern = preg_replace('/(\W[a-df-z]*)e([a-df-z]*)$/i', '$1$2', $pattern);
    $replacement = preg_replace_callback('/[\'"]?(?<!\\\\)(?:[$]|\\\\)(\d+)[\'"]?/', function ($m) { return"\$m[{$m[1]}]"; }, $replacement);
    $ve ="var_export";
    $bt = debug_backtrace(0, 1)[0];
    print"[cc lang="php"]
    #----------------------------------------------------
    # replace preg_*() call in '$bt[file]' line $bt[line] with:
    #----------------------------------------------------
    \$OUTPUT_VAR = preg_replace_callback(
        {$ve($pattern, TRUE)},
        function (\$m) {
            return {$replacement};
        },
        \$YOUR_INPUT_VARIABLE_GOES_HERE
    )
    #----------------------------------------------------

n";
}

请注意,仅复制粘贴不是编程。您必须将生成的代码改编回实际的输入/输出变量名称或使用上下文。

  • 特别是,如果在if中使用了先前的preg_replace调用,则必须去$OUTPUT =分配。
  • 最好还是保留临时变量或多行代码块结构。

并且替换表达式可能需要更多的可读性改进或返工。

  • 例如,stripslashes()通常在文字表达式中变得多余。
  • 变量范围查找需要在回调中使用useglobal引用。
  • 用引号括起来的"-$1-$2"捕获引用在语法上最终会被普通转换为"-$m[1]-$m[2]破坏。

代码输出仅仅是一个起点。是的,这作为在线工具会更加有用。这种代码重写方法(编辑,运行,编辑,编辑)有些不切实际。对于习惯以任务为中心的编码(更多的步骤,更多的发现)的人来说,它可能更容易接近。因此,这种替代方法可能会减少一些重复的问题。


您不应该使用标志e(或通常为eval)。

您也可以使用T-Regx库

1
pattern('(^|_)([a-z])')->replace($word)->by()->group(2)->callback('strtoupper');