87 votes

Remplacer le modificateur preg_replace() e par preg_replace_callback

Je suis nul avec les expressions régulières. J'essaie de remplacer ça :

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

avec preg_replace_callback avec une fonction anonyme. Je ne comprends pas ce que le \\2 fait. Ou pour cette question, exactement comment preg_replace_callback fonctionne.

Quel serait le code correct pour y parvenir ?

80voto

IMSoP Points 16089

Dans une expression régulière, vous pouvez "capturer" des parties de la chaîne recherchée avec (brackets) ; dans ce cas, vous capturez la (^|_) y ([a-z]) parties du match. Elles sont numérotées en commençant par 1, ce qui vous permet d'avoir les références arrière 1 et 2. La correspondance 0 est la chaîne entière de la correspondance.

En /e prend une chaîne de remplacement, et la remplace par une barre oblique inversée suivie d'un nombre (par ex. \1 ) avec la référence arrière appropriée - mais parce que vous êtes à l'intérieur d'une chaîne de caractères, vous devez échapper la barre oblique inverse, donc vous obtenez '\\1' . Il exécute ensuite (effectivement) eval pour exécuter la chaîne de caractères résultante comme s'il s'agissait d'un code PHP (c'est la raison pour laquelle il est déprécié, car il est facile à utiliser eval d'une manière non sécurisée).

En preg_replace_callback prend plutôt une fonction de rappel et lui transmet un tableau contenant les rétro-références correspondantes. Ainsi, là où vous auriez écrit '\\1' vous accédez à l'élément 1 de ce paramètre - par exemple, si vous avez une fonction anonyme de la forme function($matches) { ... } la première référence arrière est $matches[1] à l'intérieur de cette fonction.

Donc un /e argument de

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

pourrait devenir un callback de

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

Ou dans votre cas

'strtoupper("\\2")'

pourrait devenir

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

Notez que $m y $matches ne sont pas des noms magiques, ce sont juste les noms de paramètres que j'ai donnés lors de la déclaration de mes fonctions de rappel. De plus, vous n'êtes pas obligé de passer une fonction anonyme, il peut s'agir d'un nom de fonction sous forme de chaîne de caractères, ou quelque chose du type array($object, $method) , comme avec n'importe quel callback en PHP par exemple

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

Comme avec toute fonction, vous ne pouvez pas accéder aux variables en dehors de votre callback (de la portée environnante) par défaut. Lorsque vous utilisez une fonction anonyme, vous pouvez utiliser la fonction use pour importer les variables auxquelles vous devez accéder, comme indiqué dans le manuel PHP . par exemple, si l'ancien argument était

'do_stuff(\\1, $foo)'

alors le nouveau callback pourrait ressembler à

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

Gotchas

  • Utilisation de preg_replace_callback es au lieu de el /e sur la regex, vous devez donc supprimer ce drapeau de votre argument "pattern". Ainsi, un motif comme /blah(.*)blah/mei deviendrait /blah(.*)blah/mi .
  • En /e a utilisé une variante de addslashes() en interne sur les arguments, donc certains remplacements utilisaient stripslashes() pour le supprimer ; dans la plupart des cas, vous voulez probablement supprimer l'appel à stripslashes de votre nouveau rappel.

2voto

mario Points 76989

preg_replace la cale avec le support d'évaluation

C'est très déconseillé. Mais si vous n'êtes pas un programmeur, ou si vous préférez vraiment un code terrible, vous pouvez utiliser un substitut preg_replace pour conserver votre /e le drapeau fonctionne temporairement .

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

En fait, il suffit d'inclure cette fonction dans votre base de données, et d'éditer preg_replace à preg_replace_eval partout où le /e a été utilisé.

Avantages et inconvénients :

  • Vraiment juste testé avec quelques échantillons de Stack Overflow.
  • Ne prend en charge que les cas simples (appels de fonction, pas de recherche de variable).
  • Contient quelques restrictions et avis supplémentaires.
  • Produira des erreurs disloquées et moins compréhensibles pour les échecs d'expression.
  • Cependant, il s'agit toujours d'une solution temporaire utilisable et qui ne complique pas une transition correcte vers le système de gestion des déchets. preg_replace_callback .
  • Et le commentaire sur la licence a pour but de dissuader les gens d'en faire un usage abusif ou de la diffuser trop loin.

Générateur de codes de remplacement

Maintenant, c'est un peu redondant. Mais cela pourrait aider les utilisateurs qui sont toujours dépassés par la restructuration manuelle de leur code pour preg_replace_callback . Bien que cela prenne effectivement plus de temps, un générateur de code a moins de difficultés à étendre le champ d'action de la /e chaîne de remplacement dans une expression. Il s'agit d'une conversion très banale, mais qui est probablement suffisante pour les exemples les plus courants.

Pour utiliser cette fonction, modifiez n'importe quel preg_replace l'appel en preg_replace_eval_replacement et l'exécuter une fois . Cela permettra imprimer le selon preg_replace_callback pour être utilisé à sa place.

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

Gardez à l'esprit que le simple copier-coller est pas la programmation. Vous devrez adapter le code généré à vos noms de variables d'entrée/sortie réels, ou à votre contexte d'utilisation.

  • Plus précisément, le $OUTPUT = L'affectation devra être supprimée si le précédent preg_replace a été utilisé dans un if .
  • Il est toutefois préférable de conserver les variables temporaires ou la structure de bloc de code multiligne.

Et l'expression de remplacement peut exiger davantage d'améliorations de la lisibilité ou de remaniements.

  • Par exemple stripslashes() devient souvent redondant dans les expressions littérales.
  • Les recherches à portée variable nécessitent un use o global pour/au sein du callback.
  • Citations irrégulières "-$1-$2" les références de capture se retrouveront syntaxiquement brisées par la transformation simple en "-$m[1]-$m[2] .

La sortie du code n'est qu'un point de départ. Et oui, cela aurait été plus utile en tant qu'outil en ligne. Cette approche de réécriture du code (éditer, exécuter, éditer, éditer) n'est pas très pratique. Pourtant, elle pourrait être plus accessible à ceux qui sont habitués à un codage centré sur les tâches (plus d'étapes, plus de découvertes). Cette alternative pourrait donc freiner un peu plus les questions dupliquées.

0voto

Danon Points 1064

Vous ne devriez pas utiliser le drapeau e (ou eval en général).

Vous pouvez également utiliser Bibliothèque T-Regx

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X