Reemplace el modificador preg_replace () e con preg_replace_callback

83

Soy terrible con las expresiones regulares. Estoy tratando de reemplazar esto:

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

con preg_replace_callback con una función anónima. No entiendo qué está haciendo \\ 2. O para el caso exactamente cómo funciona preg_replace_callback.

¿Cuál sería el código correcto para lograr esto?

Casey
fuente
1
El modificador e está obsoleto a partir de PHP 5.5.0
HamZa
8
@HamZaDzCyberDeV Lo sé. Esa es una de las razones por las que quiero reemplazarlo con preg_replace_callback
Casey
2
Hay una página de manual para preg_replace_callback. Y \\2se convertirá $matches[2]en dicha devolución de llamada. ¿O qué parte te confunde específicamente?
mario
@mario ahh Los $ partidos [2] eran todo lo que necesitaba. Todavía no entiendo cómo funciona, pero funciona. Si pones eso en una respuesta, lo marcaré como solución al problema.
Casey
3
No lo use create_function, es solo otro envoltorio más eval. Debe usar una función anónima adecuada, a menos que esté atascado en PHP 5.2 por alguna razón.
IMSoP

Respuestas:

75

En una expresión regular, puede "capturar" partes de la cadena coincidente con (brackets); en este caso, está capturando las partes (^|_)y ([a-z])del partido. Estos están numerados comenzando en 1, por lo que tiene referencias inversas 1 y 2. Coincidir 0 es la cadena coincidente completa.

El /emodificador toma una cadena de reemplazo y sustituye la barra invertida seguida de un número (p \1. Ej. ) Con la referencia inversa adecuada, pero debido a que está dentro de una cadena, necesita escapar de la barra invertida, para obtener '\\1'. Luego (efectivamente) se ejecuta evalpara ejecutar la cadena resultante como si fuera código PHP (razón por la cual está en desuso, porque es fácil de usar evalde una manera insegura).

En su preg_replace_callbacklugar, la función toma una función de devolución de llamada y le pasa una matriz que contiene las referencias anteriores coincidentes. Entonces, donde habría escrito '\\1', en su lugar accede al elemento 1 de ese parámetro, por ejemplo, si tiene una función anónima del formulario function($matches) { ... }, la primera referencia inversa está $matches[1]dentro de esa función.

Entonces un /eargumento de

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

podría convertirse en una devolución de llamada de

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

O en tu caso

'strtoupper("\\2")'

podría convertirse

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

Tenga en cuenta que $my $matchesno son nombres mágicos, son solo el nombre de parámetro que di al declarar mis funciones de devolución de llamada. Además, no tiene que pasar una función anónima, podría ser el nombre de una función como una cadena, o algo de la forma array($object, $method), como con cualquier devolución de llamada en PHP , por ejemplo

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

Al igual que con cualquier función, no puede acceder a variables fuera de su devolución de llamada (desde el alcance circundante) de forma predeterminada. Cuando usa una función anónima, puede usar la usepalabra clave para importar las variables a las que necesita acceder, como se explica en el manual de PHP . por ejemplo, si el viejo argumento era

'do_stuff(\\1, $foo)'

entonces la nueva devolución de llamada podría verse así

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

Gotchas

  • El uso de preg_replace_callbackes lugar de la /emodificador de la expresión regular, por lo que necesita para eliminar esa bandera de su argumento "patrón". Entonces un patrón como /blah(.*)blah/meise convertiría /blah(.*)blah/mi.
  • El /emodificador usó una variante de addslashes()internamente en los argumentos, por lo que algunos reemplazos usaron stripslashes()para eliminarlo; en la mayoría de los casos, probablemente desee eliminar la llamada a stripslashesde su nueva devolución de llamada.
IMSoP
fuente
1

preg_replace shim con soporte eval

Esto es muy desaconsejable. Pero si no es un programador, o realmente prefiere un código terrible, puede usar una preg_replacefunción sustituta para mantener su /ebandera funcionando temporalmente .

/**
 * 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 esencia, sólo incluye esa función en su código base y editar preg_replace a preg_replace_evaldondequiera que el /ese usó la bandera.

Pros y contras :

  • Realmente solo lo probé con algunas muestras de Stack Overflow.
  • Solo admite los casos fáciles (llamadas a funciones, no búsquedas de variables).
  • Contiene algunas restricciones más y avisos de advertencia.
  • Producirá errores dislocados y menos comprensibles por fallas de expresión.
  • Sin embargo, sigue siendo una solución temporal utilizable y no complica una transición adecuada a preg_replace_callback.
  • Y el comentario sobre la licencia solo tiene como objetivo disuadir a las personas de usarlo en exceso o difundirlo demasiado.

Generador de código de reemplazo

Ahora bien, esto es algo redundante. Pero podría ayudar a aquellos usuarios que todavía están abrumados con la reestructuración manual de su código a preg_replace_callback. Si bien esto lleva más tiempo, un generador de código tiene menos problemas para expandir la /ecadena de reemplazo en una expresión. Es una conversión sin complicaciones, pero probablemente sea suficiente para los ejemplos más frecuentes.

Para usar esta función, edite cualquier preg_replacellamada rota preg_replace_eval_replacementy ejecútela una vez . Esto imprimirá el preg_replace_callbackbloque correspondiente que se utilizará en su lugar.

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

Tenga en cuenta que simplemente copiar y pegar no es programación. Tendrá que adaptar el código generado a sus nombres de variables de entrada / salida reales o al contexto de uso.

  • Específicamente, la $OUTPUT =asignación debería desaparecer si la preg_replacellamada anterior se utilizó en un if.
  • Sin embargo, es mejor mantener las variables temporales o la estructura del bloque de código de varias líneas.

Y la expresión de reemplazo puede exigir más mejoras de legibilidad o reelaboración.

  • Por ejemplo, a stripslashes()menudo se vuelve redundante en expresiones literales.
  • Las búsquedas de alcance variable requieren una referencia useo globalpara / dentro de la devolución de llamada.
  • Las "-$1-$2"referencias de captura entre comillas desiguales terminarán rotas sintácticamente por la transformación simple en "-$m[1]-$m[2].

La salida del código es simplemente un punto de partida. Y sí, esto hubiera sido más útil como herramienta en línea. Este enfoque de reescritura de código (editar, ejecutar, editar, editar) es algo poco práctico. Sin embargo, podría ser más accesible para aquellos que están acostumbrados a la codificación centrada en tareas (más pasos, más descubrimientos). Entonces, esta alternativa podría frenar algunas preguntas más duplicadas.

mario
fuente
0

No deberías usar flag e(o evalen general).

También puede usar la biblioteca T-Regx

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