Замените 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); в этом случае вы захватываете (^|_) и ([az]) части матча. Они нумеруются начиная с 1, так что у вас есть обратные ссылки 1 и 2. Матч 0 - вся строка со строкой.

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

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

Итак, аргумент a /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]); }

Обратите внимание, что $matches $m и $matches не являются волшебными именами, это просто имя параметра, которое я дал при объявлении моих функций обратного вызова. Кроме того, вам не нужно передавать анонимную функцию, это может быть имя функции как строка или что-то вроде array($object, $method) формы 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 в регулярном выражении, поэтому вам нужно удалить этот флаг из аргумента "шаблон". Таким образом, шаблон, подобный /blah(.*)blah/mei, станет /blah(.*)blah/mi.
  • Модификатор /e использовал аргумент addslashes() внутри аргументов, поэтому для некоторых замен использовались stripslashes() для его удаления; в большинстве случаев вы, вероятно, хотите удалить вызов stripslashes из вашего нового обратного вызова.

Ответ 2

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

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

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

Ответ 3

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

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

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

  • Действительно только что протестировано с несколькими образцами из Кару.
  • Поддерживает только простые случаи (вызовы функций, а не поиск переменных).
  • Содержит несколько дополнительных ограничений и уведомлений.
  • Приводит к смещенным и менее понятным ошибкам при выражении ошибок.
  • Тем не менее, это временное решение, которое можно использовать, и оно не затрудняет надлежащий переход на 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].

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