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

83

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

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

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

Какой правильный код для этого?

Кейси
источник
1
Е модификатор устарел , как из PHP 5.5.0
Хамза
8
@HamZaDzCyberDeV Я знаю. Это одна из причин, по которой я хочу заменить его на preg_replace_callback
Кейси
2
Есть справочная страница для preg_replace_callback. И \\2станет $matches[2]в обратном вызове. Или в какой части конкретно вы запутались?
Марио
@mario ahh $ Match [2] - это все, что мне нужно. Я до сих пор не понимаю, как это работает, но это так. Если вы вставите это в ответ, я отмечу, что это решает проблему.
Кейси
3
Пожалуйста, не используйте create_function, это просто еще одна обертка eval. Вам следует использовать подходящую анонимную функцию, если вы по какой-то причине не застряли в PHP 5.2.
IMSoP

Ответы:

76

В регулярном выражении вы можете «захватывать» части совпадающей строки с помощью (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); }

Попался

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

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 был использован флаг.

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

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

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

Марио
источник
0

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

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

pattern('(^|_)([a-z])')->replace($word)->by()->group(2)->callback('strtoupper');
Данон
источник