Регулярные выражения PHP: от основ до продвинутых техник

Раздел: PHP -> Регулярные выражения

Регулярные выражения в PHP: обзор и эффективное решение

Регулярные выражения (regex) в PHP реализованы через библиотеку PCRE (Perl Compatible Regular Expressions). Они позволяют выполнять поиск, замену, разбиение строк и проверку шаблонов. В качестве наиболее эффективного и универсального подхода рекомендуется использовать функцию preg_match_all() с модификаторами - она подходит для большинства задач извлечения данных и сложного поиска.

Основное эффективное решение: preg_match_all с модификаторами

Как найти все вхождения подстроки по шаблону в строке?

$str = 'Код: 123, код: 456, и ещё код: 789';
$pattern = '/код: (\d+)/iu';
if (preg_match_all($pattern, $str, $matches)) {
    print_r($matches[1]);
} else {
    echo 'Совпадений нет'; }
Array
(
    [0] => 123
    [1] => 456
    [2] => 789
)

Пояснение: Модификатор i делает поиск регистронезависимым, u включает поддержку UTF-8. Шаблон (\d+) захватывает последовательность цифр. Функция возвращает все совпадения в массив $matches, где индекс 1 соответствует первой захваченной группе.

Типичная ошибка: забыть экранировать слэши в шаблоне. Если разделитель - слэш, то внутри шаблона его нужно экранировать (\/) или использовать другой разделитель (например, #).

Проблема: несоответствие кодировки. Без модификатора u русские буквы могут обрабатываться некорректно. Решение - всегда добавлять u для строк в UTF-8.

Вариант 1: preg_match - первое совпадение

Как проверить, содержит ли строка заданный шаблон, и получить первое вхождение?

$email = 'user@example.com';
if (preg_match('/^[\w.-]+@[\w.-]+\.\w{2,}$/', $email)) {
    echo 'Email корректен';
} else {
    echo 'Неверный формат'; }

Функция preg_match() возвращает 1 при совпадении, 0 при отсутствии, false при ошибке. Полезна для валидации.

Типичная ошибка: использование preg_match для поиска всех совпадений - для этого нужна preg_match_all. Также часто забывают проверять возвращаемое значение на false при ошибках регулярки.

Вариант 2: preg_replace - замена по шаблону

Как заменить все вхождения одного шаблона на другой текст?

$text = 'Цена: 100 руб, 200 руб, 300 руб';
$result = preg_replace('/(\d+)\s*руб/u', '$1 USD', $text);
echo $result;
Цена: 100 USD, 200 USD, 300 USD

В шаблоне $1 обозначает первую захваченную группу. Для обратных ссылок можно также использовать \1. Функция может принимать массивы для выполнения нескольких замен.

Ошибка: неправильное экранирование символа доллара в строке замены. Если нужно вставить буквально $1, используйте \$1 (экранированный слэш).

Вариант 3: preg_split - разбиение строки по шаблону

Как разбить текст на части, используя сложный разделитель (например, несколько пробелов или знаки препинания)?

$sentence = 'Привет, мир! Как дела?';
$words = preg_split('/[\s,?!.]+/u', $sentence, -1, PREG_SPLIT_NO_EMPTY);
print_r($words);
Array
(
    [0] => Привет
    [1] => мир
    [2] => Как
    [3] => дела
)

Флаг PREG_SPLIT_NO_EMPTY исключает пустые элементы. Полезна для парсинга логов, CSV, URL.

Проблема: разбиение по строке, а не по регулярке (тогда лучше explode). preg_split может работать медленнее на простых разделителях.

Вариант 4: preg_grep - фильтрация массива по регулярке

Как отфильтровать массив, оставив только элементы, соответствующие шаблону?

$files = ['img1.jpg', 'doc.pdf', 'image2.jpg', 'readme.txt'];
$jpegs = preg_grep('/\.jpg$/i', $files);
print_r($jpegs);
Array
(
    [0] => img1.jpg
    [2] => image2.jpg
)

Функция возвращает массив с совпадающими элементами (ключи сохраняются). Третий аргумент PREG_GREP_INVERT инвертирует поиск. Используется для обработки списков.

Ошибка: передача не строкового массива - preg_grep работает только со строковыми элементами.

Вариант 5: preg_filter - замена с фильтрацией массива

Как выполнить замену во всех элементах массива, соответствующих шаблону, и вернуть только изменённые?

$data = ['test', 'data123', '123456', 'end'];
$filtered = preg_filter('/^(\d+)$/', 'число: $1', $data);
print_r($filtered);
Array
(
    [2] => число: 123456
)

Отличие от preg_replace: preg_filter возвращает только те элементы, в которых была произведена замена. Удобно для выборочной обработки.

Нюанс: если замена не применяется ни к одному элементу, возвращается пустой массив, а не null.

Вариант 6: preg_replace_callback - замена с использованием пользовательской функции

Как выполнить сложную замену, где нужно обработать каждое совпадение каким-либо алгоритмом?

$str = 'Дата: 2024-03-15, следующая: 2025-01-10';
$result = preg_replace_callback(
    '/(\d{4})-(\d{2})-(\d{2})/',
    function ($m) {
        return $m[3] . '.' . $m[2] . '.' . $m[1];
    },
    $str
);
echo $result;
Дата: 15.03.2024, следующая: 10.01.2025

В callback передаётся массив совпадений, можно выполнить любую логику. Полезно для переформатирования, преобразования данных.

Ошибка: забыть возвращать строку из callback - приведёт к ошибке или некорректному результату.

Производительность: использование анонимной функции с замыканием может быть медленнее, чем простой preg_replace, но даёт гибкость.

Общие рекомендации и частые проблемы

При работе с регулярными выражениями в PHP полезно помнить следующие моменты:

  • Всегда используйте разделители (например, /, #, ~). Если внутри шаблона много слэшей, выбирайте # или ~.
  • При работе с UTF-8 добавляйте модификатор u.
  • Избегайте backtrack limit - слишком сложные регулярки с большим количеством вложенных квантификаторов могут вызывать ошибку PREG_BACKTRACK_LIMIT_ERROR. В таких случаях упрощайте шаблон или увеличивайте лимит через ini_set('pcre.backtrack_limit', ...).
  • Проверяйте результат функций на false, особенно при работе с некорректными шаблонами (ошибка компиляции).
  • Используйте preg_last_error() для диагностики ошибок.

Расширенные примеры регулярных выражений в PHP

Пример 1. Именованные группы и извлечение данных из URL

Пример
$url = 'https://example.com/page/42?ref=social';
$pattern = '#^(?P<scheme>https?)://(?P<host>[^/]+)(?P<path>/[^?]*)?(?:\?(?P<query>.*))?$#';
preg_match($pattern, $url, $matches);
echo 'Схема: ' . $matches['scheme'] . '\n';
echo 'Хост: ' . $matches['host'] . '\n';
echo 'Путь: ' . ($matches['path'] ?? '') . '\n';
echo 'Query: ' . ($matches['query'] ?? '');
Схема: https
Хост: example.com
Путь: /page/42
Query: ref=social

Именованные группы (?P<name>...) делают код читаемее. Обращение к ним по ключам вместо числовых индексов.

Пример 2. Lookahead и Lookbehind: поиск, но не включение в результат

Пример
$text = 'Стоимость: 500 руб, скидка 10%';
// Найти числа, после которых идёт "руб" (позитивный lookahead)
preg_match_all('/\d+(?=\s*руб)/u', $text, $prices);
print_r($prices[0]);
// Найти числа, перед которыми идёт "скидка " (позитивный lookbehind)
preg_match_all('/(?<=скидка\s)\d+/u', $text, $discounts);
print_r($discounts[0]);
Array
(
    [0] => 500
)
Array
(
    [0] => 10
)

Используйте lookahead (?=...) и lookbehind (?<=...) для сопоставления шаблона без захвата. Отрицательные варианты - (?!...) и (?<!...).

Пример 3. Рекурсивные шаблоны: парсинг вложенных скобок

Пример
$text = 'a(b(c)d)e';
$pattern = '/\((?:[^()]|(?R))*\)/';
preg_match_all($pattern, $text, $inner);
print_r($inner[0]);
Array
(
    [0] => (b(c)d)
)

(?R) - рекурсия на весь шаблон, позволяет обрабатывать вложенные структуры. Ограничение: может быть ресурсоёмко.

Пример 4. Модификаторы: s, m, x, u, i

Пример
$multi = "Первая строка\nвторая строка\nТретья";
// Модификатор m (multiline) - ^ и $ работают по каждой строке
preg_match_all('/^\w+/m', $multi, $words_m);
print_r($words_m[0]);
// Модификатор s (dotall) - точка включает \n
preg_match('/Первая.+/s', $multi, $line);
echo $line[0];
// Модификатор x - игнорирует пробелы в шаблоне (для читаемости)
$pattern = '/ ^ 
             \w+   # слово
             \s*   # пробелы
           /x';
preg_match($pattern, $multi, $m);
print_r($m);
Array
(
    [0] => Первая
    [1] => вторая
    [2] => Третья
)
Первая строка
вторая строка
Третья
Array
(
    [0] => Первая
)

Комбинируйте модификаторы: /uis, /mix и т.д. Внимание: модификатор x изменяет синтаксис - нужны комментарии #.

Пример 5. Работа с UTF-8 и юникодными свойствами

Пример
$text_ru = 'Привет, мир! 123.';
// Найти все буквы (включая русские) с помощью \p{L}
preg_match_all('/\p{L}+/u', $text_ru, $letters);
print_r($letters[0]);
// Найти все цифры
preg_match_all('/\p{N}+/u', $text_ru, $digits);
print_r($digits[0]);
// Удалить всё, кроме букв
$only_letters = preg_replace('/\P{L}+/u', ' ', $text_ru);
echo trim($only_letters);
Array
(
    [0] => Привет
    [1] => мир
)
Array
(
    [0] => 123
)
Привет мир

\p{...} обозначает категорию Unicode (L - буквы, N - цифры, Z - пробелы и т.д.). \P{...} - отрицание.

Пример 6. Обратные ссылки и модификаторы insensivity

Пример
$string = 'abacaba';
// Поиск повторяющихся подряд букв (захват группы и обратная ссылка)
preg_match_all('/(\w)\1/', $string, $doubles);
print_r($doubles[0]);
// Поиск палиндрома (более сложная рекурсия)
$palindrome = 'level';
$pattern = '/^((.)(?1)\2|.?)$/';
if (preg_match($pattern, $palindrome)) {
    echo "Да, это палиндром";
}
Array
(
    [0] => aa
    [1] => bb
)
Да, это палиндром

Обратная ссылка \1 ссылается на первую захваченную группу. Рекурсия (?1) вызывает первую группу заново. Палиндромный шаблон сложен для понимания, но демонстрирует мощь PCRE.

Пример 7. preg_replace_callback с анонимной функцией и статической переменной

Пример
$text = 'id:5, id:23, id:7';
$result = preg_replace_callback(
    '/id:(\d+)/',
    function ($m) {
        static $counter = 0;
        $counter++;
        return 'new_id:' . ($m[1] + $counter * 10);
    },
    $text
);
echo $result;
new_id:15, new_id:33, new_id:27

Статическая переменная сохраняет состояние между вызовами callback. Полезно для нумерации или преобразования с контекстом.

Пример 8. Использование preg_filter для выборочной замены и сохранения порядка

Пример
$data = ['apple', 'banana', 'cherry', 'avocado'];
// Оставить только те фрукты, которые начинаются на 'a', и удвоить их
$filtered = preg_filter('/^a(.+)/', 'a$1a', $data);
print_r($filtered);
// Исходный массив не изменился, а $filtered содержит только изменённые элементы
print_r($data);
Array
(
    [0] => aapplea
    [3] => aavocadoa
)
Array
(
    [0] => apple
    [1] => banana
    [2] => cherry
    [3] => avocado
)

preg_filter возвращает только те ключи, которые были изменены. Остальные элементы исходного массива игнорируются.

Пример 9. Обработка ошибок PCRE с помощью preg_last_error

Пример
// Создадим шаблон с превышением backtrack limit (очень сложный)
$bad = str_repeat('(a?)', 100);
$subject = str_repeat('a', 100);
$result = preg_replace($bad, '', $subject);
if ($result === null) {
    $err = preg_last_error();
    switch ($err) {
        case PREG_BACKTRACK_LIMIT_ERROR:
            echo 'Ошибка: превышен лимит backtrack';
            break;
        case PREG_INTERNAL_ERROR:
            echo 'Внутренняя ошибка PCRE';
            break;
        default:
            echo 'Неизвестная ошибка';
    }
}
Ошибка: превышен лимит backtrack

preg_last_error() возвращает код ошибки после неудачного выполнения. Применяйте для отладки сложных регулярных выражений.

Пример 10. Использование PREG_OFFSET_CAPTURE для получения позиции совпадения

Пример
$str = 'The quick brown fox jumps over the lazy dog.';
preg_match('/\b\w{5}\b/', $str, $matches, PREG_OFFSET_CAPTURE);
print_r($matches[0]);
echo "Найдено: '" . $matches[0][0] . "' на позиции " . $matches[0][1];
Array
(
    [0] => quick
    [1] => 4
)
Найдено: 'quick' на позиции 4

Флаг PREG_OFFSET_CAPTURE добавляет к каждому элементу массива смещение в байтах. Удобно для замен с учётом контекста.

Регулярные выражения в PHP - comments

En
регулярные выражения php (php)