【问题标题】:Replace preg_replace() e modifier with preg_replace_callback用 preg_replace_callback 替换 preg_replace() e 修饰符
【发布时间】:2013-03-05 10:55:30
【问题描述】:

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

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

使用带有匿名函数的 preg_replace_callback。我不明白 \\2 在做什么。或者就此而言, preg_replace_callback 究竟是如何工作的。

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

【问题讨论】:

  • 从 PHP 5.5.0 起,e 修饰符为 deprecated
  • @HamZaDzCyber​​DeV 我知道。这就是我想用 preg_replace_callback 替换它的原因之一
  • preg_replace_callback 有一个手册页。而\\2 将在所述回调中变为$matches[2]。或者您具体对哪一部分感到困惑?
  • @mario ahh $matches[2] 是我所需要的。我仍然不明白它是如何工作的,但确实如此。如果您将其放在答案中,我会将其标记为解决问题。
  • 请不要使用create_function,它只是eval 的另一个包装。你应该使用适当的匿名函数,除非你因为某种原因被困在 PHP 5.2 中。

标签: php regex preg-replace preg-replace-callback


【解决方案1】:

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

/e 修饰符接受一个替换字符串,并用适当的反向引用替换后跟一个数字(例如 \1)的反斜杠 - 但是因为您在字符串中,所以您需要转义反斜杠,所以你得到'\\1'。然后它(有效地)运行 eval 来运行生成的字符串,就好像它是 PHP 代码一样(这就是它被弃用的原因,因为它很容易以不安全的方式使用 eval)。

preg_replace_callback 函数取而代之的是一个回调函数并将一个包含匹配的反向引用的数组传递给它。因此,在您应该编写 '\\1' 的地方,您改为访问该参数的元素 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]); }

请注意,$m$matches 不是魔术名称,它们只是我在声明回调函数时给出的参数名称。此外,您不必传递匿名函数,它可以是字符串形式的函数名称,或 array($object, $method)as with any callback in PHP 形式的内容,例如

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关键字导入需要访问的变量as discussed in the PHP manual。例如如果旧的论点是

'do_stuff(\\1, $foo)'

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

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

陷阱

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

【讨论】:

    【解决方案2】:

    您不应使用标志e(或一般的eval)。

    你也可以使用T-Regx library

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

    【讨论】:

      【解决方案3】:

      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_replace 调用编辑到 preg_replace_eval_replacement 并运行它一次。这将打印出相应的 preg_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";
      }
      

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2014-02-16
        • 1970-01-01
        • 2015-07-31
        相关资源
        最近更新 更多