Substitua o modificador preg_replace () e por preg_replace_callback

83

Sou péssimo com expressões regulares. Estou tentando substituir isso:

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

com preg_replace_callback com uma função anônima. Não entendo o que o \\ 2 está fazendo. Ou exatamente como funciona preg_replace_callback.

Qual seria o código correto para conseguir isso?

Casey
fonte
1
O modificador e está obsoleto a partir do PHP 5.5.0
HamZa
8
@HamZaDzCyberDeV Eu sei. Essa é uma das razões pelas quais quero substituí-lo por preg_replace_callback
Casey
2
Existe uma página de manual para preg_replace_callback. E \\2se tornará $matches[2]no dito retorno de chamada. Ou sobre qual parte você está confuso especificamente?
mario
@mario ahh O $ Match [2] era tudo que eu precisava. Ainda não entendo como funciona, mas funciona. Se você colocar isso em uma resposta, marcarei como uma solução para o problema.
Casey
3
Por favor, não use create_function, é apenas mais um invólucro eval. Você deve usar uma função anônima adequada, a menos que esteja preso no PHP 5.2 por algum motivo.
IMSoP

Respostas:

75

Em uma expressão regular, você pode "capturar" partes da string combinada com (brackets); neste caso, você está capturando as partes (^|_)e ([a-z])da partida. Eles são numerados começando em 1, então você tem referências anteriores 1 e 2. Match 0 é toda a string combinada.

O /emodificador pega uma string de substituição e substitui a barra invertida seguida por um número (por exemplo \1) com a referência inversa apropriada - mas porque você está dentro de uma string, você precisa escapar da barra invertida, então você consegue '\\1'. Em seguida, ele (efetivamente) executa evalpara executar a string resultante como se fosse um código PHP (por isso está sendo descontinuado, porque é fácil de usar evalde uma maneira insegura).

A preg_replace_callbackfunção, em vez disso, recebe uma função de retorno de chamada e passa a ela uma matriz contendo as referências anteriores correspondentes. Então, onde você teria escrito '\\1', você acessa o elemento 1 desse parâmetro - por exemplo, se você tem uma função anônima do formulário function($matches) { ... }, a primeira referência anterior está $matches[1]dentro dessa função.

Então, um /eargumento de

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

pode se tornar um retorno de chamada de

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

Ou no seu caso

'strtoupper("\\2")'

poderia se tornar

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

Observe que $me $matchesnão são nomes mágicos, eles são apenas o nome do parâmetro que dei ao declarar minhas funções de retorno de chamada. Além disso, você não precisa passar uma função anônima, pode ser um nome de função como uma string, ou algo na forma array($object, $method), como em qualquer retorno de chamada em PHP , por exemplo

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

Como com qualquer função, você não pode acessar variáveis ​​fora de seu retorno de chamada (do escopo circundante) por padrão. Ao usar uma função anônima, você pode usar a usepalavra-chave para importar as variáveis ​​que você precisa acessar, conforme discutido no manual do PHP . por exemplo, se o antigo argumento era

'do_stuff(\\1, $foo)'

então o novo retorno de chamada pode parecer

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

Pegadinhas

  • Uso de preg_replace_callbacké , em vez do /emodificador na regex, então você precisa para remover a bandeira de seu argumento "padrão". Portanto, um padrão como /blah(.*)blah/meise tornaria /blah(.*)blah/mi.
  • O /emodificador usou uma variante de addslashes()internamente nos argumentos, portanto, algumas substituições foram usadas stripslashes()para removê-lo; na maioria dos casos, você provavelmente deseja remover a chamada para stripslashesde seu novo retorno de chamada.
IMSoP
fonte
1

preg_replace shim com eval support

Isso é muito desaconselhável. Mas se você não for um programador, ou realmente preferir um código terrível, você pode usar uma preg_replacefunção substituta para manter seu /esinalizador funcionando temporariamente .

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

Em essência, você acabou de incluir essa função em sua base de código, e edita preg_replace para preg_replace_evalonde quer que o /efoi usado bandeira.

Prós e contras :

  • Realmente apenas testado com algumas amostras do Stack Overflow.
  • Suporta apenas os casos fáceis (chamadas de função, não pesquisas de variáveis).
  • Contém mais algumas restrições e avisos.
  • Irá produzir erros deslocados e menos compreensíveis para falhas de expressão.
  • No entanto, ainda é uma solução temporária utilizável e não complica uma transição adequada para preg_replace_callback.
  • E o comentário da licença serve apenas para impedir as pessoas de usar demais ou espalhar isso muito longe.

Gerador de código de substituição

Agora, isso é um tanto redundante. Mas pode ajudar os usuários que ainda estão sobrecarregados com a reestruturação manual de seu código para preg_replace_callback. Embora isso consuma efetivamente mais tempo, um gerador de código tem menos problemas para expandir a /estring de substituição em uma expressão. É uma conversão nada notável, mas provavelmente é suficiente para os exemplos mais comuns.

Para usar esta função, edite qualquer preg_replacechamada interrompida em preg_replace_eval_replacemente execute-a uma vez . Isso imprimirá o preg_replace_callbackbloco correspondente a ser usado em seu 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";
}

Lembre-se de que apenas copiar e colar não é programação. Você terá que adaptar o código gerado de volta aos nomes reais das variáveis ​​de entrada / saída ou ao contexto de uso.

  • Especificamente, a $OUTPUT =atribuição teria que ser encerrada se a preg_replacechamada anterior fosse usada em um if.
  • É melhor manter as variáveis ​​temporárias ou a estrutura do bloco de código de várias linhas.

E a expressão de substituição pode exigir mais melhorias de legibilidade ou retrabalho.

  • Por exemplo, stripslashes()muitas vezes se torna redundante em expressões literais.
  • As pesquisas de escopo variável requerem uma referência useou globalpara / dentro do retorno de chamada.
  • As "-$1-$2"referências de captura entre aspas desigualmente terminarão quebradas sintaticamente pela transformação simples em "-$m[1]-$m[2].

A saída do código é apenas um ponto de partida. E sim, isso teria sido mais útil como uma ferramenta online. Essa abordagem de reescrita de código (editar, executar, editar, editar) é um pouco impraticável. No entanto, poderia ser mais acessível para aqueles que estão acostumados com a codificação centrada em tarefas (mais etapas, mais descobertas). Portanto, essa alternativa pode conter mais algumas perguntas duplicadas.

mario
fonte
0

Você não deve usar flag e(ou evalem geral).

Você também pode usar a biblioteca T-Regx

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