Zastąp preg_replace () e modyfikator preg_replace_callback

83

Jestem okropny z wyrażeniami regularnymi. Próbuję to zastąpić:

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

z preg_replace_callback z funkcją anonimową. Nie rozumiem, co robi \\ 2. Albo o to, jak dokładnie działa preg_replace_callback.

Jaki byłby prawidłowy kod, aby to osiągnąć?

Casey
źródło
1
E modyfikator przestarzałe w PHP 5.5.0
hamza
8
@HamZaDzCyberDeV Wiem. To jeden z powodów, dla których chcę go zastąpić preg_replace_callback
Casey
2
Jest strona podręcznika dla preg_replace_callback. I \\2zostanie $matches[2]w wymienionym oddzwonieniu. A może konkretnie co do której części jesteś zdezorientowany?
mario
@mario ahh Mecze $ [2] były wszystkim, czego potrzebowałem. Nadal nie rozumiem, jak to działa, ale tak jest. Jeśli umieścisz to w odpowiedzi, zaznaczę, że jest to rozwiązanie problemu.
Casey
3
Proszę, nie używaj create_function, to tylko kolejne opakowanie eval. Powinieneś użyć odpowiedniej funkcji anonimowej, chyba że z jakiegoś powodu utkniesz w PHP 5.2.
IMSoP

Odpowiedzi:

76

W wyrażeniu regularnym można „przechwycić” części dopasowanego ciągu za pomocą (brackets); w tym przypadku przechwytujesz (^|_)i ([a-z])części dopasowania. Są one numerowane począwszy od 1, więc masz odniesienia wsteczne 1 i 2. Dopasowanie 0 to cały dopasowany ciąg.

/eModyfikator pobiera ciąg zastępczy, a substytuty BACKSLASH następuje numer (np \1) z odpowiednim odniesieniem wstecznego - ale dlatego, że jesteś w środku łańcucha, trzeba uciec backslash, więc masz '\\1'. Następnie (skutecznie) evaluruchamia wynikowy ciąg tak, jakby był to kod PHP (dlatego jest przestarzały, ponieważ jest łatwy w użyciu evalw niezabezpieczony sposób).

preg_replace_callbackFunkcja zamiast wykonuje funkcję wywołania zwrotnego i przekazuje mu tablicę zawierającą dopasowane nawiązań wstecznych. Więc tam, gdzie byś napisał '\\1', zamiast tego uzyskujesz dostęp do elementu 1 tego parametru - np. Jeśli masz anonimową funkcję formularza function($matches) { ... }, pierwsze odniesienie wsteczne znajduje się $matches[1]wewnątrz tej funkcji.

Więc /eargument

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

może stać się oddzwonieniem do

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

Albo w twoim przypadku

'strtoupper("\\2")'

może się stać

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

Zauważ, że $mi $matchesnie są to magiczne nazwy, to tylko nazwa parametru, który podałem podczas deklarowania moich funkcji zwrotnych. Nie musisz też przekazywać funkcji anonimowej, może to być nazwa funkcji w postaci łańcucha lub coś w formie array($object, $method), jak w przypadku każdego wywołania zwrotnego w PHP , np.

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

Podobnie jak w przypadku każdej funkcji, domyślnie nie można uzyskać dostępu do zmiennych poza wywołaniem zwrotnym (z otaczającego zakresu). Korzystając z funkcji anonimowej, możesz użyć usesłowa kluczowego, aby zaimportować zmienne, do których chcesz uzyskać dostęp, zgodnie z opisem w podręczniku PHP . np. jeśli stary argument był

'do_stuff(\\1, $foo)'

wtedy może wyglądać nowe wywołanie zwrotne

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

Gotchas

  • Zastosowanie preg_replace_callbackto zamiast z /emodyfikatora na regex, więc trzeba usunąć tę flagę z „wzorcem” argument. Taki wzór /blah(.*)blah/meistałby się /blah(.*)blah/mi.
  • /eModyfikator stosowany wariant addslashes()wewnętrznie na argumentach, więc niektóre zamienniki stosowane stripslashes(), aby go usunąć; w większości przypadków prawdopodobnie będziesz chciał usunąć to połączenie stripslashesz nowego wywołania zwrotnego.
IMSoP
źródło
1

preg_replace shim z obsługą eval

To jest bardzo niewskazane. Ale jeśli nie jesteś programistą lub naprawdę wolisz okropny kod, możesz użyć preg_replacefunkcji zastępczej, aby tymczasowo utrzymać /eflagę w działaniu .

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

W istocie wystarczy dołączyć tę funkcję do swojej bazy kodu i edytować preg_replacepreg_replace_evaltam, gdzie /ezostała użyta flaga.

Plusy i minusy :

  • Naprawdę właśnie przetestowano z kilkoma próbkami ze Stack Overflow.
  • Obsługuje tylko proste przypadki (wywołania funkcji, a nie wyszukiwania zmiennych).
  • Zawiera kilka dodatkowych informacji o ograniczeniach i poradach.
  • Przy błędach ekspresji wystąpią przemieszczone i mniej zrozumiałe błędy.
  • Jednak nadal jest użytecznym rozwiązaniem tymczasowym i nie komplikuje prawidłowego przejścia na preg_replace_callback.
  • A komentarz do licencji ma po prostu zniechęcić ludzi do nadużywania lub rozpowszechniania tego zbyt daleko.

Generator kodów zastępczych

Teraz jest to trochę zbędne. Ale może pomóc tym użytkownikom, którzy wciąż są przytłoczeni ręczną restrukturyzacją kodu do preg_replace_callback. Chociaż jest to bardziej czasochłonne, generator kodu ma mniej problemów z rozszerzeniem /ezastępującego ciągu znaków na wyrażenie. To bardzo zwyczajne nawrócenie, ale prawdopodobnie wystarcza w przypadku większości rozpowszechnionych przykładów.

Aby użyć tej funkcji, edytuj każde przerwane preg_replacewywołanie preg_replace_eval_replacementi uruchom je raz . Spowoduje to wydrukowanie odpowiedniego preg_replace_callbackbloku, który ma zostać użyty w jego miejsce.

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

Pamiętaj, że samo kopiowanie i wklejanie nie jest programowaniem. Będziesz musiał dostosować wygenerowany kod z powrotem do rzeczywistych nazw zmiennych wejścia / wyjścia lub kontekstu użycia.

  • W szczególności $OUTPUT =przypisanie musiałoby przejść, gdyby poprzednie preg_replacewywołanie zostało użyte w pliku if.
  • Najlepiej jednak zachować zmienne tymczasowe lub strukturę wielowierszowego bloku kodu.

A wyrażenie zastępcze może wymagać większej poprawy czytelności lub przeróbek.

  • Na przykład stripslashes()często staje się zbędne w wyrażeniach dosłownych.
  • Wyszukiwanie w zakresie zmiennych wymaga odwołania uselub globalodniesienia do / w wywołaniu zwrotnym.
  • Nierównomiernie ujęte w cudzysłowy "-$1-$2"odniesienia do przechwytywania zostaną podzielone składniowo przez zwykłą transformację do "-$m[1]-$m[2].

Kod wyjściowy jest jedynie punktem wyjścia. I tak, byłoby to bardziej przydatne jako narzędzie online. To podejście do przepisywania kodu (edycja, uruchamianie, edycja, edycja) jest nieco niepraktyczne. Jednak może być bardziej przystępny dla tych, którzy są przyzwyczajeni do kodowania skoncentrowanego na zadaniach (więcej kroków, więcej odkryć). Ta alternatywa może więc ograniczyć kilka dodatkowych pytań.

mario
źródło
0

Nie powinieneś używać flagi e(lub evalogólnie).

Możesz także skorzystać z biblioteki T-Regx

pattern('(^|_)([a-z])')->replace($word)->by()->group(2)->callback('strtoupper');
Danon
źródło