Функция preg_replace_callback_array()

В PHP 7 появилась функция preg_replace_callback_array(). Я её использовал в последнем проекте. Хочется показать на простых примерах, как она работает. Для этого разберём и другие функции. Для понимания статьи нужно знать, как работают регулярные выражения.

Давайте напишем небольшой интерпретатор некоторых BB-кодов.

Замена по одному выражению

Начнём с простой замены: [b]<b> (здесь и далее с учётом пробелов и переносов, то есть варианты типа [ b ] тоже сработают). Для удобства основные символы выделены жёлтым цветом.

$subject = 'To [b]be[/b] or not to be?';
$pattern = '/\[\s*b\s*]/';
$replacement = '<b>';
echo preg_replace($pattern, $replacement, $subject) . PHP_EOL;
// 'To [b]be[/b] or not to be?'
// 'To <b>be[/b] or not to be?'

Элементарная функция preg_replace() заменяет каждое вхождение подстроки, соответствующей шаблону, на другую подстроку.

Замена по нескольким выражениям

Можно указать несколько шаблонов и их замен: [b]<b>, [/b]</b>. Шаблоны и их замены теперь находятся в массиве (для работы с открывающими и закрывающими тегами можно было бы просто написать (\/?), но мы так делать не будем, чтобы продемонстрировать возможности этого семейства функций).

$subject = 'To [b]be[/b] or not to be?';
$pattern[0] = '/\[\s*b\s*]/';
$replacement[0] = '<b>';
$pattern[1] = '/\[\s*\/\s*b\s*\]/';
$replacement[1] = '</b>';
echo preg_replace($pattern, $replacement, $subject) . PHP_EOL;
// 'To [b]be[/b] or not to be?'
// 'To <b>be</b> or not to be?'

Можно усложнить выражение, чтобы учитывать разные теги: [b]<b>, [/b]</b>, [i]<i>, [/i]</i>.

$subject = 'To [b]be[/b] or [i]not[/i] to be?';
$pattern[0] = '/\[\s*(b|i)\s*]/';
$replacement[0] = "<$1>";
$pattern[1] = '/\[\s*\/\s*(b|i)\s*\]/';
$replacement[1] = "</$1>";
echo preg_replace($pattern, $replacement, $subject) . PHP_EOL;
// 'To [b]be[/b] or [i]not[/i] to be?'
// 'To <b>be</b> or <i>not</i> to be?'

Знак $1 означает первую найденную часть подстроки, обозначенную круглыми скобками.

Замена с обработкой

Но пока ещё нельзя было как-то модифицировать найденные выражения (например, менять регистр), можно было только подставить найденную часть подстроки как есть. Функция preg_replace_callback(), один из аргументов которой — функция обратного вызова (callback), позволяет это сделать. Хоть пока и только с одним регулярным выражением. Функция обратного вызова срабатывает для каждого найденного шаблона.

Получаются следующие замены: [b], [B]<b>; [i], [I]<i>.

$subject = 'To [B]be[/B] or [i]not[/I] to be?';
$pattern = '/\[\s*(b|B|i|I)\s*]/';
echo preg_replace_callback(
    $pattern,
    function ($matches)
    {
        return '<' . strtolower($matches[1]) . '>';
    },
    $subject
) . PHP_EOL;
// 'To [B]be[/B] or [i]not[/I] to be?'
// 'To <b>be[/B] or <i>not[/I] to be?'

Замена с обработкой по нескольким выражениям

Функция preg_replace_callback_array() позволяет делать то же самое, что и preg_replace_callback(), но с несколькими регулярными выражениями. Замены: [b], [B]<b>; [/b], [/B]</b>; [i], [I]<i>; [/i], [/I]</I>.

$subject = 'To [B]be[/B] or [i]not[/i] to be?';
$pattern[0] = '/\[\s*(b|B|i|I)\s*]/';
$pattern[1] = '/\[\s*\/\s*(b|B|i|I)\s*\]/';
echo preg_replace_callback_array(
    [
        $pattern[0] => function ($matches)
        {
            return '<' . strtolower($matches[1]) . '>';
        },
        $pattern[1] => function ($matches)
        {
            return '</' . strtolower($matches[1]) . '>';
        }
    ],
    $subject
) . PHP_EOL;
// 'To [B]be[/B] or [i]not[/i] to be?'
// 'To <b>be</b> or <i>not</i> to be?'

Детали работы этой и других функций (максимальное количество замен для шаблонов и подсчёт количества произведённых замен) читайте в официальной документации.

Замена может быть сколь угодно сложной. Например, в моём проекте ссылки на статьи формируются по неким правилам. Это может быть просто адрес статьи: {{ url }}. Или адрес раздела и адрес статьи (должен существовать внешний ключ): {{ section_id -> name }}/{{ url }}. Можно взять только часть поля (к примеру, год из метки времени 2018-08-28 — это первые четыре символа): {{ date_and_time | 0...3 }}. Во втором случае без внешних данных не обойтись, поэтому используем лямбда-функцию как замыкание (closure), захватив некую переменную по ссылке.

...
$foo = preg_replace_callback_array(
    [
        $pattern[0] => function ($matches)
        {
            // Замена.
        },
        $pattern[1] => function ($matches) use (&data)
        {
            // Замена с использованием внешних данных.
        }
        ...
    ],
    ...
);
28 августа · php
Оставить комментарий (отменить)