Замените модификатор preg_replace() e на preg_replace_callback

Я ужасен с регулярными выражениями. Я пытаюсь заменить это:

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

с preg_replace_callback с анонимной функцией. Я не понимаю, что делает \\2. Или в этом отношении, как именно работает preg_replace_callback.

Какой будет правильный код для достижения этого?

1 ответ

Решение

В регулярном выражении вы можете "захватывать" части совпадающей строки с помощью (brackets); в этом случае вы захватываете (^|_) а также ([a-z]) части матча. Они нумеруются, начиная с 1, поэтому у вас есть обратные ссылки 1 и 2. Совпадение 0 - это вся совпадающая строка.

/e Модификатор принимает строку замены и заменяет обратную косую черту, за которой следует число (например, \1) с соответствующей обратной ссылкой - но поскольку вы внутри строки, вам нужно экранировать обратную косую черту, чтобы вы получили '\\1', Затем он (эффективно) работает eval запустить полученную строку, как если бы это был PHP-код (именно поэтому она устарела, потому что ее легко использовать eval небезопасным способом).

preg_replace_callback Вместо этого функция принимает функцию обратного вызова и передает ей массив, содержащий соответствующие обратные ссылки. Так где бы вы написали '\\1'вместо этого вы получаете доступ к элементу 1 этого параметра - например, если у вас есть анонимная функция вида function($matches) { ... }первая обратная ссылка $matches[1] внутри этой функции.

Так что /e аргумент

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

может стать обратным вызовом

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

Или в вашем случае

'strtoupper("\\2")'

мог стать

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

Обратите внимание, что $m а также $matches это не магические имена, это просто имя параметра, которое я дал при объявлении моих функций обратного вызова. Кроме того, вам не нужно передавать анонимную функцию, это может быть имя функции в виде строки или что-то в форме array($object, $method), как с любым обратным вызовом в PHP, например

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

Как и с любой функцией, вы не можете получить доступ к переменным вне вашего обратного вызова (из окружающей области видимости) по умолчанию. При использовании анонимной функции вы можете использовать use ключевое слово для импорта переменных, к которым вам нужно получить доступ, как описано в руководстве по PHP. например, если старый аргумент был

'do_stuff(\\1, $foo)'

тогда новый обратный вызов может выглядеть

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

Gotchas

  • Использование preg_replace_callback вместо /e модификатор в регулярном выражении, поэтому вам нужно убрать этот флаг из аргумента "pattern". Так что картина как /blah(.*)blah/mei станет /blah(.*)blah/mi,
  • /e модификатор использовал вариант addslashes() внутренне на аргументы, поэтому некоторые замены используются stripslashes() удалить его; в большинстве случаев вы, вероятно, хотите удалить вызов stripslashes от вашего нового обратного вызова.

preg_replace shim с поддержкой eval

Это очень нецелесообразно. Но если вы не программист или действительно предпочитаете ужасный код, вы можете использовать заменуpreg_replace функция, чтобы сохранить ваш /eфлаг работает временно.

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

По сути, вы просто включаете эту функцию в свою кодовую базу и редактируете preg_replaceк preg_replace_eval где бы /e использовался флаг.

Плюсы и минусы:

  • На самом деле только что протестировали с несколькими образцами из stackru.
  • Поддерживает только простые случаи (вызовы функций, а не поиск переменных).
  • Содержит еще несколько ограничений и рекомендательных уведомлений.
  • Будет приводить к вывихнутым и менее понятным ошибкам для ошибок выражения.
  • Тем не менее, это временное решение, которое можно использовать, и оно не усложняет правильный переход к preg_replace_callback.
  • И комментарий к лицензии предназначен только для того, чтобы удержать людей от чрезмерного использования или распространения этого слишком далеко.

Генератор кода замены

Теперь это несколько избыточно. Но может помочь тем пользователям, которые все еще не могут вручную реструктурировать свой код, чтобы preg_replace_callback. Хотя это фактически занимает больше времени, у генератора кода меньше проблем с расширением/eзаменяющую строку в выражение. Это ничем не примечательное преобразование, но его, вероятно, достаточно для наиболее распространенных примеров.

Чтобы использовать эту функцию, отредактируйте любой сломанный preg_replace позвонить в preg_replace_eval_replacementи запустить его один раз. Это распечатает соответствующийpreg_replace_callback блок, который будет использоваться вместо него.

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

Помните, что простое копирование и вставка - это не программирование. Вам придется адаптировать сгенерированный код обратно к вашим фактическим именам переменных ввода / вывода или контексту использования.

  • В частности, $OUTPUT = задание должно быть отменено, если предыдущее preg_replace звонок был использован в if.
  • Однако лучше всего сохранить временные переменные или многострочную структуру блока кода.

И выражение замены может потребовать дополнительных улучшений читабельности или доработки.

  • Например stripslashes() часто становится излишним в буквальных выражениях.
  • Для поиска с переменной областью видимости требуется use или global ссылка для / внутри обратного вызова.
  • Неравномерно заключенный в кавычки "-$1-$2" ссылки захвата будут синтаксически нарушены простым преобразованием в "-$m[1]-$m[2].

Вывод кода - это просто отправная точка. И да, это было бы более полезно в качестве онлайн-инструмента. Такой подход к переписыванию кода (редактирование, запуск, редактирование, редактирование) в некоторой степени непрактичен. Тем не менее, он может быть более доступным для тех, кто привык к программированию, ориентированному на задачи (больше шагов, больше открытий). Таким образом, эта альтернатива может ограничить еще несколько повторяющихся вопросов.

Вы не должны использовать флаг e (или же eval в общем).

Вы также можете использовать библиотеку T-Regx

pattern('(^|_)([a-z])')->replace($word)->by()->group(2)->callback('strtoupper');
Другие вопросы по тегам