Замініть модифікатор 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

@mario ahh $ сірники [2] - це все, що мені потрібно. Я досі не розумію, як це працює, але це робить. Якщо ви вкажете це у відповіді, я позначу це як вирішення проблеми.
Кейсі

3
Будь ласка, не використовуйте create_function, це просто чергова обгортка навколо eval. Вам слід використовувати належну анонімну функцію, якщо ви з якихось причин не застрягли в PHP 5.2.
IMSoP 16.03.13

Відповіді:


75

У регулярному виразі ви можете "захопити" частини відповідного рядка за допомогою (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із нового зворотного дзвінка.

1

preg_replace прокладку з підтримкою 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');
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.