86

正規表現が苦手です。私はこれを置き換えようとしています:

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

無名関数で preg_replace_callback を使用します。\\2 が何をしているのかわかりません。または、正確に preg_replace_callback がどのように機能するかについても説明します。

これを達成するための正しいコードは何でしょうか?

4

3 に答える 3

79

(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。これらは、コールバック関数を宣言するときに指定したパラメーター名にすぎません。また、匿名関数を渡す必要はありません。関数名を文字列にすることも、 PHPのコールバックと同様に の形式array($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); }

落とし穴

  • 正規表現の修飾子の代わりにpreg_replace_callbackisを使用するため、「パターン」引数からそのフラグを削除する必要があります。したがって、 のようなパターンは になります。/e/blah(.*)blah/mei/blah(.*)blah/mi
  • /e修飾子は、引数で内部的にバリアントを使用しaddslashes()ていたため、いくつかの置換を使用stripslashes()してそれを削除しました。ほとんどの場合、stripslashes新しいコールバックから への呼び出しを削除する必要があります。
于 2013-03-16T20:37:11.847 に答える
2

preg_replace shim と eval サポート

これは非常にお勧めできません。しかし、あなたがプログラマーでない場合、またはひどいコードを本当に好む場合は、代替関数を使用して一時的にフラグ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_replace するだけです。preg_replace_eval/e

長所と短所:

  • Stack Overflow のいくつかのサンプルで実際にテストしたところです。
  • 簡単なケースのみをサポートします (変数の検索ではなく、関数呼び出し)。
  • さらにいくつかの制限事項と注意事項が含まれています。
  • 式の失敗に対して、位置がずれて理解しにくいエラーが発生します。
  • ただし、まだ使用可能な一時的な解決策であり、への適切な移行を複雑にすることはありませんpreg_replace_callback
  • そして、ライセンスのコメントは、人々がこれを過度に使用したり広めたりするのを思いとどまらせることを目的としています.

置換コードジェネレーター

これはやや冗長です。しかし、手動でコードをpreg_replace_callback. これには実質的に時間がかかりますが、コード ジェネレーターが/e置換文字列を式に展開する際の問題は少なくなります。これは非常に目立たない変換ですが、最も一般的な例ではおそらく十分です。

この関数を使用するには、壊れたpreg_replace呼び出しを編集して1 回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";
}

単なるコピー&ペーストはプログラミングではないことに注意してください。生成されたコードを実際の入力/出力変数名または使用コンテキストに戻す必要があります。

  • 具体的には、前の呼び出しが で使用された$OUTPUT =場合、割り当てを行わなければなりません。preg_replaceif
  • ただし、一時変数または複数行のコード ブロック構造を保持することをお勧めします。

また、置換式では、読みやすさの改善や手直しが必要になる場合があります。

  • たとえばstripslashes()、リテラル式ではしばしば冗長になります。
  • 変数スコープのルックアップには、コールバックの/内のuseまたは参照が必要です。global
  • 不均等に引用符で囲まれた"-$1-$2"キャプチャ参照は、 への単純な変換によって構文的に壊れてしまい"-$m[1]-$m[2]ます。

コード出力は単なる開始点です。はい、これはオンラインツールとしてもっと便利だったでしょう. このコード書き換えアプローチ (編集、実行、編集、編集) はやや非現実的です。ただし、タスク中心のコーディング (より多くのステップ、より多くの発見) に慣れている人にとっては、より親しみやすいものになる可能性があります。したがって、この代替手段により、さらにいくつかの重複する質問が抑制される可能性があります。

于 2019-10-14T10:56:05.417 に答える