Ersetzen Sie den Modifikator preg_replace () e durch preg_replace_callback

83

Ich bin schrecklich mit regulären Ausdrücken. Ich versuche dies zu ersetzen:

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

mit preg_replace_callback mit einer anonymen Funktion. Ich verstehe nicht, was die \\ 2 macht. Oder genau so, wie preg_replace_callback funktioniert.

Was wäre der richtige Code, um dies zu erreichen?

Casey
quelle
1
Der e- Modifikator ist ab PHP 5.5.0
HamZa
8
@ HamZaDzCyberDeV Ich weiß. Das ist einer der Gründe, warum ich es durch preg_replace_callback ersetzen möchte
Casey
2
Es gibt eine Handbuchseite für preg_replace_callback. Und \\2wird $matches[2]in besagtem Rückruf. Oder über welchen Teil sind Sie konkret verwirrt?
Mario
@mario ahh Die $ Matches [2] waren alles was ich brauchte. Ich verstehe immer noch nicht, wie es funktioniert, aber es funktioniert. Wenn Sie das in eine Antwort einfügen, werde ich markieren, dass es das Problem löst.
Casey
3
Bitte nicht benutzen create_function, es ist nur ein weiterer Wrapper eval. Sie sollten eine ordnungsgemäße anonyme Funktion verwenden, es sei denn, Sie stecken aus irgendeinem Grund in PHP 5.2 fest.
IMSoP

Antworten:

75

In einem regulären Ausdruck können Sie Teile der übereinstimmenden Zeichenfolge mit "erfassen" (brackets). In diesem Fall erfassen Sie die (^|_)und ([a-z])Teile des Spiels. Diese sind ab 1 nummeriert, sodass Sie die Rückverweise 1 und 2 haben. Match 0 ist die gesamte übereinstimmende Zeichenfolge.

Der /eModifikator verwendet eine Ersatzzeichenfolge und ersetzt den Backslash gefolgt von einer Zahl (z. B. \1) durch die entsprechende Backreferenz. Da Sie sich jedoch in einer Zeichenfolge befinden, müssen Sie den Backslash umgehen, damit Sie erhalten '\\1'. Es wird dann (effektiv) ausgeführt eval, um die resultierende Zeichenfolge so auszuführen, als wäre es PHP-Code (weshalb sie veraltet ist, weil sie auf evalunsichere Weise einfach zu verwenden ist ).

Die preg_replace_callbackFunktion verwendet stattdessen eine Rückruffunktion und übergibt ihr ein Array mit den übereinstimmenden Rückverweisen. Wo Sie also geschrieben hätten '\\1', greifen Sie stattdessen auf Element 1 dieses Parameters zu - z. B. wenn Sie eine anonyme Funktion des Formulars haben function($matches) { ... }, befindet sich die erste Rückreferenz $matches[1]innerhalb dieser Funktion.

Also ein /eArgument von

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

könnte ein Rückruf von werden

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

Oder in deinem Fall

'strtoupper("\\2")'

könnte werden

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

Beachten Sie, dass $mund $matchessind nicht magische Namen, sie sind nur die Parameternamen gab ich als meine Callback - Funktionen zu deklarieren. Außerdem müssen Sie keine anonyme Funktion übergeben, es kann sich um einen Funktionsnamen als Zeichenfolge oder um etwas in der Form handeln array($object, $method), wie bei jedem Rückruf in PHP , z

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

Wie bei jeder Funktion können Sie standardmäßig nicht auf Variablen außerhalb Ihres Rückrufs (aus dem umgebenden Bereich) zugreifen. Wenn Sie eine anonyme Funktion verwenden, können Sie das useSchlüsselwort verwenden, um die Variablen zu importieren, auf die Sie zugreifen müssen, wie im PHP-Handbuch beschrieben . zB wenn das alte Argument war

'do_stuff(\\1, $foo)'

dann könnte der neue Rückruf so aussehen

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

Fallstricke

  • Die Verwendung preg_replace_callbackist statt dem /eModifikator auf dem regulären Ausdruck, so dass Sie die Flagge von Ihrem „Muster“ Argumente entfernen müssen. So würde ein Muster wie /blah(.*)blah/meiwerden /blah(.*)blah/mi.
  • Der /eModifikator verwendete eine Variante von addslashes()intern für die Argumente, daher wurden einige Ersetzungen verwendet stripslashes(), um sie zu entfernen. In den meisten Fällen möchten Sie den Anruf wahrscheinlich stripslashesaus Ihrem neuen Rückruf entfernen .
IMSoP
quelle
1

preg_replace shim mit eval support

Dies ist sehr nicht ratsam. Aber wenn Sie kein Programmierer sind oder wirklich schrecklichen Code bevorzugen, können Sie eine Ersatzfunktion preg_replaceverwenden, um Ihre /eFlagge vorübergehend funktionsfähig zu halten .

/**
 * 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
    );
}

Im Wesentlichen fügen Sie diese Funktion einfach in Ihre Codebasis ein und bearbeiten preg_replace sie preg_replace_evaldort , wo das /eFlag verwendet wurde.

Vor- und Nachteile :

  • Wirklich nur mit ein paar Proben von Stack Overflow getestet.
  • Unterstützt nur die einfachen Fälle (Funktionsaufrufe, keine variablen Suchvorgänge).
  • Enthält einige weitere Einschränkungen und Hinweise.
  • Ergibt dislozierte und weniger verständliche Fehler bei Ausdrucksfehlern.
  • Es ist jedoch immer noch eine brauchbare temporäre Lösung und erschwert keinen ordnungsgemäßen Übergang zu preg_replace_callback.
  • Und der Lizenzkommentar soll die Leute nur davon abhalten, dies zu überbeanspruchen oder zu weit zu verbreiten.

Ersatzcode-Generator

Das ist jetzt etwas überflüssig. Könnte aber den Benutzern helfen, die immer noch mit der manuellen Umstrukturierung ihres Codes überfordert sind preg_replace_callback. Während dies effektiv zeitaufwändiger ist, hat ein Codegenerator weniger Probleme, die /eErsatzzeichenfolge in einen Ausdruck zu erweitern. Es ist eine sehr unauffällige Konvertierung, die aber wahrscheinlich für die am weitesten verbreiteten Beispiele ausreicht.

Um diese Funktion zu verwenden, bearbeiten Sie einen unterbrochenen preg_replaceAnruf in preg_replace_eval_replacementund führen Sie ihn einmal aus . Dies wird auszudrucken den entsprechenden preg_replace_callbackBlock an seiner Stelle verwendet werden.

/**
 * 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";
}

Denken Sie daran, dass das bloße Kopieren und Einfügen keine Programmierung ist. Sie müssen den generierten Code wieder an die tatsächlichen Namen der Eingabe- / Ausgabevariablen oder den Verwendungskontext anpassen.

  • Insbesondere $OUTPUT =müsste die Zuweisung gehen, wenn der vorherige preg_replaceAnruf in einem verwendet wurde if.
  • Es ist jedoch am besten, temporäre Variablen oder die mehrzeilige Codeblockstruktur beizubehalten.

Und der Ersatzausdruck erfordert möglicherweise mehr Verbesserungen der Lesbarkeit oder Nacharbeit.

  • Beispielsweise stripslashes()wird es in wörtlichen Ausdrücken häufig überflüssig.
  • Für Suchvorgänge mit variablem Bereich ist ein useoder eine globalReferenz für / innerhalb des Rückrufs erforderlich .
  • Ungleichmäßig in Anführungszeichen eingeschlossene "-$1-$2"Erfassungsreferenzen werden durch die einfache Umwandlung in syntaktisch unterbrochen "-$m[1]-$m[2].

Die Code-Ausgabe ist lediglich ein Ausgangspunkt. Und ja, dies wäre als Online-Tool nützlicher gewesen. Dieser Ansatz zum Umschreiben von Code (Bearbeiten, Ausführen, Bearbeiten, Bearbeiten) ist etwas unpraktisch. Könnte jedoch für diejenigen zugänglicher sein, die an aufgabenorientiertes Codieren gewöhnt sind (mehr Schritte, mehr Aufdeckungen). Diese Alternative könnte also einige weitere doppelte Fragen zügeln.

Mario
quelle
0

Sie sollten kein Flag e(oder evalallgemein) verwenden.

Sie können auch die T-Regx-Bibliothek verwenden

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