将 preg_replace() e 修饰符替换为 preg_replace_callback戈查斯

我对正则表达式很糟糕。我正在尝试替换它:

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

preg_replace_callback具有匿名功能。我不明白 \\2 在做什么。或者就此而言,preg_replace_callback是如何工作的。

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


答案 1

在正则表达式中,您可以使用 “捕获” 匹配字符串的部分;在本例中,您将捕获匹配项的 和 部分。它们从 1 开始编号,因此您有反向引用 1 和 2。匹配 0 是整个匹配字符串。(brackets)(^|_)([a-z])

修饰符采用替换字符串,并用相应的反引号替换反斜杠后跟一个数字(例如) - 但是由于您位于字符串中,因此您需要转义反斜杠,因此您将获得 .然后(有效地)运行以运行生成的字符串,就好像它是PHP代码一样(这就是为什么它被弃用的原因,因为它很容易以不安全的方式使用)。/e\1'\\1'evaleval

该函数改为采用回调函数,并为其传递一个包含匹配的反向引用的数组。因此,在你本来应该写的地方,你可以访问该参数的元素1 - 例如,如果你有一个匿名函数的形式,第一个反向引用就在该函数内部。preg_replace_callback'\\1'function($matches) { ... }$matches[1]

所以一个论点/e

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

可能成为

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

或者就您而言

'strtoupper("\\2")'

可能成为

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

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

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');

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

'do_stuff(\\1, $foo)'

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

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

戈查斯

  • 使用 is 而不是正则表达式上的修饰符,因此您需要从“pattern”参数中删除该标志。所以像这样的模式会变成.preg_replace_callback/e/blah(.*)blah/mei/blah(.*)blah/mi
  • 修饰符在参数上使用了内部的变体,因此一些替换用于删除它;在大多数情况下,您可能希望从新回调中删除 对 的调用。/eaddslashes()stripslashes()stripslashes

答案 2

支持评估的preg_replace垫片

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

/**
 * 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_replacepreg_replace_eval/e

优点和缺点

  • 实际上只是用Stack Overflow的几个样本进行了测试。
  • 仅支持简单情况(函数调用,不支持变量查找)。
  • 包含一些其他限制和咨询通知。
  • 将产生错位和难以理解的表达式失败错误。
  • 但是,仍然是一个可用的临时解决方案,并且不会使适当的过渡到 .preg_replace_callback
  • 许可证评论只是为了阻止人们过度使用或传播得太远。

替换代码生成器

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

要使用此函数,请将任何中断的调用编辑并运行一。这将打印出相应的块以用于其位置。preg_replacepreg_replace_eval_replacementpreg_replace_callback

/**
 * 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 "<pre><code>
    #----------------------------------------------------
    # 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
    )
    #----------------------------------------------------
    </code></pre>\n";
}

请记住,仅仅复制和粘贴不是编程。您必须根据实际的输入/输出变量名称或使用上下文调整生成的代码。

  • 具体而言,如果在 中使用了上一个调用,则必须进行分配。$OUTPUT =preg_replaceif
  • 不过,最好保留临时变量或多行代码块结构。

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

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

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


推荐