التعبيرات المنتظمة في PHP — دليل شامل
Что это такое и зачем? (аналогия из реальной жизни)
Представьте, что вы — библиотекарь в огромной библиотеке, где хранятся миллионы книг. К вам приходит читатель и говорит: «Найдите мне все книги, где на 15-й странице есть номер телефона, начинающийся на +7, и имя автора заканчивается на "ов"». Без специальных инструментов вам пришлось бы открывать каждую книгу вручную и проверять — это заняло бы годы.
Регулярные выражения (Regular Expressions, или сокращённо regex) — это как волшебная лупа, которая умеет искать в тексте не просто конкретные слова, а шаблоны (паттерны). Вы говорите лупе: «Найди всё, что похоже на телефон», — и она мгновенно подсвечивает все номера в тексте.
В программировании регулярные выражения используются повсеместно:
- Проверка, правильно ли пользователь ввёл email или номер телефона
- Поиск всех ссылок на сайте
- Замена «плохих» слов на звёздочки в комментариях
- Извлечение данных из логов сервера
- Валидация форм (не дать отправить пустое поле или поле с недопустимыми символами)
💡 Важно: Регулярные выражения — это не изобретение PHP. Это универсальный язык поиска, который существует во многих языках программирования (Python, JavaScript, Java, C# и даже в текстовых редакторах вроде VS Code).
Теория: как это работает изнутри
Давайте заглянем «под капот» регулярных выражений. Представьте, что регулярное выражение — это инструкция для робота-поисковика, который движется по тексту символ за символом.
Основные строительные блоки
1. Литералы (обычные символы)
Самый простой случай. Если вы напишете шаблон cat, то найдёте в тексте именно слово «cat». Робот просто ищет точное совпадение.
2. Метасимволы (специальные команды)
Это символы, которые не ищут сами себя, а дают команды. Вот главные из них:
| Символ | Значение | Пример |
|---|---|---|
. (точка) |
Любой один символ (кроме перевода строки) | c.t найдёт «cat», «cot», «c3t» |
* |
Предыдущий символ повторяется 0 или более раз | ca*t найдёт «ct», «cat», «caat», «caaat» |
+ |
Предыдущий символ повторяется 1 или более раз | ca+t найдёт «cat», «caat», но не «ct» |
? |
Предыдущий символ может быть, а может не быть (0 или 1 раз) | colou?r найдёт и «color», и «colour» |
^ |
Начало строки | ^Привет найдёт только «Привет» в начале строки |
$ |
Конец строки | конец$ найдёт только «конец» в конце строки |
d |
Любая цифра (0-9) | ddd найдёт любые три цифры подряд |
w |
Любая буква, цифра или знак подчёркивания | w+ найдёт целое слово |
s |
Любой пробельный символ (пробел, табуляция, перевод строки) | s+ найдёт один или несколько пробелов |
3. Квантификаторы (указатели количества)
Они говорят, сколько раз должен повториться предыдущий символ или группа:
{n}— ровно n раз (например,d{3}— три цифры){n,}— n или более раз{n,m}— от n до m раз
4. Классы символов (группировка вариантов)
Квадратные скобки [] позволяют указать набор допустимых символов:
[abc]— один из символов a, b или c[a-z]— любая строчная буква латинского алфавита[0-9]— любая цифра (то же самое, чтоd)[^abc]— любой символ, кроме a, b или c (знак ^ внутри скобок означает отрицание)
5. Группы и альтернативы
Круглые скобки () выполняют две функции:
- Группируют часть шаблона для применения квантификатора (например,
(ab)+найдёт «ab», «abab», «ababab») - Запоминают найденное для последующего использования
Вертикальная черта | означает «или»: кот|собака найдёт либо «кот», либо «собака».
Как PHP обрабатывает регулярные выражения
В PHP есть два набора функций для работы с регулярными выражениями:
- POSIX-стиль (устаревший, не используйте!) — функции
ereg(),eregi() - PCRE-стиль (Perl Compatible Regular Expressions) — современный стандарт. Все функции начинаются с
preg_:preg_match(),preg_match_all(),preg_replace(),preg_split()
⚠️ Важное замечание: В PHP регулярное выражение обязательно заключается в разделители. Чаще всего используют косую черту/, но можно и другие символы:#,~,!. Пример:/шаблон/или#шаблон#.
Синтаксис в PHP: все формы и варианты
Давайте разберём все основные функции PHP для работы с регулярными выражениями. Я покажу их в виде удобной таблицы-шпаргалки.
Основные функции PCRE
| Функция | Что делает | Возвращает |
|---|---|---|
preg_match($pattern, $subject, &$matches) |
Проверяет, есть ли в строке хотя бы одно совпадение с шаблоном | 1 — если есть совпадение, 0 — если нет, false — при ошибке |
preg_match_all($pattern, $subject, &$matches) |
Находит все совпадения с шаблоном в строке | Количество найденных совпадений (может быть 0) |
preg_replace($pattern, $replacement, $subject) |
Заменяет все найденные совпадения на указанный текст | Изменённую строку |
preg_split($pattern, $subject) |
Разбивает строку на массив по указанному шаблону (как explode, но мощнее) | Массив строк |
preg_grep($pattern, $array) |
Фильтрует массив, оставляя только элементы, совпадающие с шаблоном | Отфильтрованный массив |
Модификаторы (флаги) — меняют поведение поиска
Модификаторы ставятся после закрывающего разделителя. Например: /шаблон/i — регистронезависимый поиск.
| Модификатор | Описание |
|---|---|
i |
Регистронезависимый поиск (не различает заглавные и строчные буквы) |
m |
Многострочный режим. Символы ^ и $ начинают работать для каждой строки, а не для всего текста |
s |
Точка . начинает совпадать с любым символом, включая перевод строки |
u |
Включает поддержку UTF-8 (обязательно для работы с кириллицей!) |
x |
Разрешает пробелы и комментарии внутри шаблона (для читаемости) |
U |
Ленивый режим (квантификаторы ищут минимальное совпадение, а не максимальное) |
🔥 Совет: При работе с русским языком ВСЕГДА добавляйте модификаторu. Иначе регулярка будет воспринимать кириллицу как набор странных символов. Пример:/[а-яё]+/u— найдёт русское слово.
Примеры кода — от простого к сложному
Теперь самое интересное — практика. Каждый пример я разберу построчно.
Пример 1: Проверка, есть ли в строке цифры
<?php
// Создаём строку для проверки
$text = "Мой номер телефона: 123-456-789";
// Шаблон: d — любая цифра, + — одна или более подряд
$pattern = '/d+/';
// Выполняем поиск
$result = preg_match($pattern, $text);
// preg_match возвращает 1 (истина) или 0 (ложь)
if ($result) {
echo "✅ В тексте есть цифры!";
} else {
echo "❌ Цифр не найдено";
}
// Выведет: ✅ В тексте есть цифры!
?>
Пример 2: Извлечение всех email-адресов из текста
<?php
$text = "Свяжитесь с нами: support@example.com или admin@test.org.
Также пишите на info@company.net";
// Шаблон для email:
// [w.-]+ — имя пользователя (буквы, цифры, точки, дефисы)
// @ — собачка
// [w.-]+ — домен
// . — точка (экранируем, так как точка — метасимвол)
// [a-z]{2,} — доменная зона (ru, com, org и т.д.)
$pattern = '/[w.-]+@[w.-]+.w{2,}/';
// Ищем ВСЕ совпадения
$count = preg_match_all($pattern, $text, $matches);
echo "Найдено email-адресов: $count
";
print_r($matches[0]);
/*
Выведет:
Array
(
[0] => support@example.com
[1] => admin@test.org
[2] => info@company.net
)
*/
?>
Пример 3: Замена всех ссылок на кликабельные HTML-ссылки
<?php
$text = "Посетите наш сайт https://example.com и страницу http://test.org/about";
// Шаблон для URL:
// https?:// — протокол (s может не быть)
// [^s]+ — любые непробельные символы (сам адрес)
$pattern = '/https?://[^s]+/';
// Заменяем найденные URL на HTML-ссылку
// $0 — это специальная переменная, которая содержит всё найденное совпадение
$replacement = '$0';
$newText = preg_replace($pattern, $replacement, $text);
echo $newText;
// Выведет: Посетите наш сайт https://example.com и страницу http://test.org/about
?>
Пример 4: Валидация номера телефона в формате +7 (XXX) XXX-XX-XX
<?php
function validatePhone($phone) {
// Шаблон для российского номера:
// ^ — начало строки
// +7 — символ + и цифра 7
// s? — необязательный пробел
// ( — открывающая скобка (экранируем)
// d{3} — ровно 3 цифры
// ) — закрывающая скобка
// s? — необязательный пробел
// d{3} — 3 цифры
// - — дефис
// d{2} — 2 цифры
// - — дефис
// d{2} — 2 цифры
// $ — конец строки
$pattern = '/^+7s?(d{3})s?d{3}-d{2}-d{2}$/';
if (preg_match($pattern, $phone)) {
return "✅ Номер корректен";
} else {
return "❌ Неверный формат номера";
}
}
echo validatePhone("+7 (123) 456-78-90"); // ✅
echo "
";
echo validatePhone("8-123-456-78-90"); // ❌ (нет +7 и скобок)
?>
Пример 5: Разбор лога сервера на части
<?php
$logLine = "192.168.1.1 - - [10/Oct/2023:13:55:36 +0000] "GET /index.php HTTP/1.1" 200 2326";
// Шаблон для извлечения IP, даты, метода и статуса
// Используем группы (скобки) для захвата отдельных частей
$pattern = '/^(S+) .* [(.*?)] "(w+) .*" (d+) /';
preg_match($pattern, $logLine, $matches);
echo "IP-адрес: " . $matches[1] . "
"; // 192.168.1.1
echo "Дата: " . $matches[2] . "
"; // 10/Oct/2023:13:55:36 +0000
echo "Метод: " . $matches[3] . "
"; // GET
echo "Статус: " . $matches[4] . "
"; // 200
// Объяснение шаблона:
// ^(S+) — захватываем первые непробельные символы (IP)
// .* — пропускаем всё до...
// [(.*?)] — захватываем содержимое квадратных скобок (дата)
// "(w+) — захватываем слово после кавычки (метод HTTP)
// .*" — пропускаем остаток до кавычки
// (d+) — захватываем число (код статуса)
?>
Пример 6: Фильтрация массива — оставить только email
<?php
$data = [
"user@example.com",
"not_an_email",
"admin@site.org",
"12345",
"test@test.com"
];
// Шаблон для проверки, что строка — это email
$pattern = '/^[w.-]+@[w.-]+.w{2,}$/';
// preg_grep возвращает только те элементы, которые совпадают с шаблоном
$validEmails = preg_grep($pattern, $data);
print_r($validEmails);
/*
Array
(
[0] => user@example.com
[2] => admin@site.org
[4] => test@test.com
)
*/
?>
Пример 7: Сложная замена — цензура с сохранением первой буквы
<?php
$text = "Этот дурак написал плохой отзыв, а этот болван вообще ничего не понял.";
// Список плохих слов
$badWords = ['дурак', 'болван', 'плохой'];
// Для каждого плохого слова создаём шаблон и замену
foreach ($badWords as $word) {
// Шаблон: слово целиком (границы слова )
// Захватываем первую букву в группу
$pattern = '/(' . $word[0] . ')' . substr($word, 1) . '/u';
// Замена: первая буква ($1) + столько звёздочек, сколько букв во второй части
$replacement = '$1' . str_repeat('*', mb_strlen($word) - 1);
$text = preg_replace($pattern, $replacement, $text);
}
echo $text;
// Выведет: Этот д**** написал п***** отзыв, а этот б***** вообще ничего не понял.
?>
Пример 8: Разделение строки с сохранением разделителей
<?php
$sentence = "Привет! Как дела? Я изучаю PHP. А ты?";
// Разбиваем по знакам препинания, но сохраняем их
// Используем PREG_SPLIT_DELIM_CAPTURE — он сохраняет то, что в скобках
$parts = preg_split('/([!?.])/u', $sentence, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
print_r($parts);
/*
Array
(
[0] => Привет
[1] => !
[2] => Как дела
[3] => ?
[4] => Я изучаю PHP
[5] => .
[6] => А ты
[7] => ?
)
*/
?>
Сравнение с Python: чем похоже и чем отличается
Если вы уже знакомы с Python или планируете его изучать, вот ключевые различия в работе с регулярными выражениями:
| Характеристика | PHP (PCRE) | Python (re) |
|---|---|---|
| Импорт модуля | Не требуется (встроено) | import re |
| Разделители шаблона | Обязательны: /шаблон/ |
Не нужны: "шаблон" |
| Поиск первого совпадения | preg_match() |
re.search() |
| Поиск всех совпадений | preg_match_all() |
re.findall() |
| Замена | preg_replace() |
re.sub() |
| Разделение строки | preg_split() |
re.split() |
| Регистронезависимость | Модификатор i: /шаблон/i |
Флаг re.IGNORECASE: re.search(..., flags=re.I) |
| Работа с Unicode | Модификатор u |
По умолчанию поддерживает Unicode |
| Именованные группы | (?P<name>шаблон) |
(?P<name>шаблон) — синтаксис одинаковый |
| Производительность | Высокая (PCRE — один из самых быстрых движков) | Хорошая, но чуть медленнее PCRE на сложных шаблонах |
💡 Главное отличие для новичка: В PHP вы пишетеpreg_match('/шаблон/i', $text), а в Python —re.search('шаблон', text, re.I). Суть одна, синтаксис разный.
Типичные ошибки и подводные камни
Даже опытные программисты иногда спотыкаются на регулярках. Вот список самых частых ошибок:
Ошибка 1: Забыли про разделители
// ❌ Неправильно — нет разделителей
preg_match('d+', $text);
// ✅ Правильно
preg_match('/d+/', $text);
Ошибка 2: Не экранировали спецсимволы
// ❌ Неправильно — точка означает "любой символ"
preg_match('/example.com/', $text); // Найдёт "exampleXcom"
// ✅ Правильно — экранируем точку
preg_match('/example.com/', $text);
Список символов, которые нужно экранировать (ставить перед ними обратную косую черту): . + * ? [ ^ ] $ ( ) { } = ! < > | : -
Ошибка 3: Жадность квантификаторов
$text = "<b>Жирный</b> и <b>тоже жирный</b>";
// ❌ Жадный поиск — захватит всё от первого < до последнего >
preg_match('/<b>.*</b>/', $text, $m);
echo $m[0]; // <b>Жирный</b> и <b>тоже жирный</b>
// ✅ Ленивый поиск — остановится на первом закрывающем теге
preg_match('/<b>.*?</b>/', $text, $m);
echo $m[0]; // <b>Жирный</b>
Ошибка 4: Проблемы с кириллицей
// ❌ Неправильно — без модификатора u
preg_match('/[а-я]+/', 'привет'); // Может не сработать
// ✅ Правильно — добавляем u
preg_match('/[а-яё]+/u', 'привет');
Ошибка 5: Путаница между preg_match и preg_match_all
$text = "a b c";
// preg_match найдёт ТОЛЬКО первое совпадение
preg_match('/w/', $text, $m);
print_r($m[0]); // 'a'
// preg_match_all найдёт ВСЕ совпадения
preg_match_all('/w/', $text, $m);
print_r($m[0]); // ['a', 'b', 'c']
Ошибка 6: Забыли про якоря ^ и $ при валидации
// ❌ Найдёт "123" даже в строке "abc123def"
preg_match('/d+/', 'abc123def'); // true
// ✅ Найдёт только если вся строка состоит из цифр
preg_match('/^d+$/', 'abc123def'); // false
preg_match('/^d+$/', '123'); // true
Лучшие практики и советы профессионалов
За годы работы с регулярными выражениями я выработал несколько правил, которые спасают от головной боли:
1. Начинайте с простого и тестируйте
Не пытайтесь написать идеальное регулярное выражение с первого раза. Начните с простого шаблона, проверьте его на тестовых данных, потом постепенно усложняйте. Используйте онлайн-тестеры вроде regex101.com — они подсвечивают совпадения и объясняют, что делает каждый символ.
2. Используйте именованные группы для сложных шаблонов
// Вместо безликих $matches[1], $matches[2]...
$pattern = '/(?P<year>d{4})-(?P<month>d{2})-(?P<day>d{2})/';
preg_match($pattern, '2023-10-15', $matches);
echo $matches['year']; // 2023
echo $matches['month']; // 10
echo $matches['day']; // 15
3. Не изобретайте велосипед для сложных валидаций
Для проверки email, URL, IP-адресов есть готовые функции PHP:
filter_var($email, FILTER_VALIDATE_EMAIL)— проверка emailfilter_var($url, FILTER_VALIDATE_URL)— проверка URLfilter_var($ip, FILTER_VALIDATE_IP)— проверка IP
Регулярные выражения для этих задач могут быть очень сложными и содержать ошибки. Доверьтесь встроенным функциям.
4. Комментируйте сложные регулярки
// Используем модификатор x для пробелов и комментариев
$pattern = '/^
+7 # Код страны
s? # Необязательный пробел
(d{3}) # Код города в скобках
s? # Необязательный пробел
d{3}-d{2}-d{2} # Основной номер
$/x';
preg_match($pattern, '+7 (123) 456-78-90', $matches);
5. Ограничивайте длину строки перед обработкой
Регулярные выражения могут работать медленно на очень длинных строках (особенно с жадными квантификаторами). Если вы ожидаете строки длиннее 10 000 символов, подумайте об ограничении:
$text = substr($text, 0, 5000); // Берём только первые 5000 символов
6. Используйте PREG_OFFSET_CAPTURE для получения позиции совпадения
$text = "Найти слово в этом предложении";
preg_match('/слово/', $text, $matches, PREG_OFFSET_CAPTURE);
echo "Найдено на позиции: " . $matches[0][1]; // Выведет: 10
Вопросы для самопроверки
Проверьте, как хорошо вы усвоили материал. Ответы спрятаны под спойлером — попробуйте ответить сначала сами!
- Что такое разделители в PHP-регулярках и зачем они нужны?
- Какая функция найдёт ВСЕ совпадения с шаблоном в строке?
- Как сделать поиск регистронезависимым?
- Что означает шаблон
/^[A-Z]d{3}$/? - Чем отличается
.*от.*?? - Какой модификатор обязателен при работе с кириллицей?
- Что вернёт
preg_match('/d+/', 'abc')? - Как в шаблоне обозначить группу, которая запоминает найденное?
- Какая функция заменит все совпадения на другой текст?
- Почему для валидации email лучше использовать
filter_var(), а не регулярку?