1. Головна
  2. Колонка автора
  3. Регулярні вирази у PHP

Регулярні вирази у PHP

Регулярні вирази є дуже сильним інструментом для здійснення маніпуляцій з підрядками в тексті. Однак, вони також є дуже складними у вивченні та застосуванні.
Регулярні вирази у PHP
Існує кілька різних діалектів регулярних виразів, серед яких один із найпоширеніших і найрозвиненіших є синтаксис Perl-сумісних регулярних виразів (PCRE - Perl Compatible Regular Expressions).

Простими словами, регулярний вираз - це шаблон, який застосовується до заданого тексту зліва направо. Можна використовувати звичайні символи, які зберігають значення у шаблоні та означають збіг з відповідними символами. Наприклад, регулярний вираз, що містить текст "комп", відповідає рядку, який містить зазначений підрядок, наприклад "комп'ютер".

Позначення меж регулярного висловлювання можна записати так:
"/комп/"
Прямий слєш (/) спочатку і кінці набору символів служить межею регулярного висловлювання, тобто регулярний вираз діятиме доти, доки не зустрінеться другий символ прямого слєша.

Допустимо використовувати інструкції модифікатори шаблону, які діють на весь регулярний вираз. Наприклад, модифікатор "i" здійснюватиме пошук за регулярним виразом без урахування регістру. Для українських символів у кодуванні UTF8 для правильної обробки необхідно додавати модифікатор "u" (PCRE_UTF8). Наприклад:
"/комп/ui"
Регулярний вираз із прикладу буде відповідати як рядку "комп'ютер", так і "КОМП'ЮТЕР".

Для прив'язки регулярного висловлювання на початок слова використовується символ "^" (caret - знак вставки):
"/^світ/"
Даний вираз буде відповідати рядку "світильник", і не відповідатиме слову "всесвіт".

Знак долара "$" означає кінець рядка:
"/^світильник$/"
Даний регулярний вираз відповідає виключно рядку "світильник", де після слова немає іншого тексту.

Наступний регулярний вираз відповідає порожньому рядку:
"/^$/"
Дуже часто у пошуковому рядку міститься символ початку та кінця регулярного виразу, у нашому випадку символ косої риски "/". У цьому випадку необхідно екранувати цей символ за допомогою символу зворотного слеша (\):
"/^світильник\/стельовий$/"
У цьому прикладі регулярний вираз буде відповідати рядку "світильник/стельовий".

Як роздільник може виступати будь-який інший символ, наприклад "|":
"|^світильник\/стельовий$|ui"
Змінювати роздільники необхідно виходячи із завдання пошуку, наприклад, якщо символ косої риси "/" часто зустрічається в пошуковому рядку, то його можна змінити.

Слід бути дуже уважними, використовуючи деякі символи для роздільників, оскільки вони можуть виконувати свою роль у шаблоні. Використання символу вертикальної межі "|" у регулярному вираженні може бути використане для завдання альтернативних масок:
"/^abc|def$/"
Даному регулярному виразу відповідає будь-який рядок, що містить підрядки "abc" або "def". Вертикальну риску більшості випадків застосовують під час перевірки, наприклад, розширень файлів або зон доменних імен.

Підрядки в регулярних виразах можна групувати за допомогою дужок "()":
"/^колір (червоний|синій|зелений)$/"
Цей регулярний вираз буде відповідати рядку виду "колір червоний", але замість "червоний" може бути як "синій", так і "зелений".

Для використання дужок як частини пошукового рядка їх слід екранувати. Наприклад, відповідати рядку "колір (червоний)" буде такий регулярний вираз:
"/^колір \(червоний\)$/"
Крім угруповання символів, дужки мають ще одне призначення. Всі вирази, знайдені у дужках, зберігаються інтерпретатором, і до них можна звернутися під час заміни чи пошуку за номером дужки.

Щоб задати клас символів, необхідно використовувати квадратні дужки "[]". Вони обмежують пошук тими символами, які в них є:
"/[abc]/"
Даному регулярному виразу відповідатиме підрядок, який містить хоча б один символ з "abc".

Для створення регулярного виразу, який відповідає всім літерам англійського алфавіту, можна перерахувати всі літери в регулярному виразі, а можна записати коротше наступним чином:
"/[a-z]/i"
Будь-які два символи, що поділяються дефісом, задають відповідність діапазону символів між ними. У даному регулярному вираженні описані символи нижнього регістру, але модифікатор "i" здійснює пошук регістрозалежний.

Аналогічним чином задаються регулярні вирази, що відповідають цифрам:
"/[0-9]/"
При використанні екранування зворотним слешем деякі символи виконують спеціальну інтерпретацію:

\d - будь-яка десяткова цифра ([0-9]);

\D - будь-який символ, крім десяткової цифри;

\s - будь-який пробільний символ ([\r\n\t\f]);

\S - будь-який непробільний символ;

\w - будь-який символ, що утворює "слово" ([a-zA-Z0-9_]);

\W - будь-який символ, що не утворює "слово";

\t - символ табуляції;

\n - символ переводу рядка;

\\ - символ зворотного слешу (\);

\. - Символ крапки (.).

Символ крапки "." позначає будь-який символ у регулярному виразі крім символів розриву рядка "\r" або "\n", тому для пошуку крапки слід екранувати цей символ.

Регулярний вираз для числа можна записати так:
"/[\d]/"
Щоб виключити клас символів з пошуку, необхідно в квадратних дужках поставити першим символ "^", який діє вже не як покажчик межі рядка, а як заперечення:
"/[^0-9]/"
Цей регулярний вираз відповідає будь-якому символу, що не міститься в діапазоні "0-9".

Список спеціальних символів (метасимволи):
\^$.[]|()?*+{}
Вираз у квадратних дужках часто застосовується разом із так званими квантифікаторами, які є символами "?", "+" і "*". Квантифікатори слідують відразу за символом і змінюють кількість входжень конкретного символу в рядок:

? - символ або входить у рядок один раз, або взагалі до неї не входить;

* - будь-яке число входжень символу в рядок, у тому числі 0;

+ - одне чи більше число входжень символу рядок.

Наприклад, якщо необхідно знайти підрядок, що містить одну або більше цифр, слід скористатися виразом:
"/[\d]+/"
Символ "*" використовується для будь-якого числа входжень рядка в підрядок, тобто регулярний вираз відповідає або порожній рядку, або рядку, що містить необмежену кількість цифр.
"/^[\d]*$/"
У регулярних виразах також застосовуються фігурні дужки ({}), які призначені для вказівки числа або діапазону чисел повторення елемента:

"ab{2}" - відповідає рядку "abb";

"ab{2,}" - відповідає рядку, в якому за "b" слід не менше двох "b";

"ab{2,4}" - відповідає рядку, в якому за "b" слідує від 2 до 4 символів "b".

Вираз "{0,}" повністю аналогічний "*", а "{1,}" - "+". Вираз "{0,1}" можна записати коротше, використовуючи "?".

Для об'єднання символів у послідовність їх необхідно помістити в круглі дужки. Наприклад, наступний регулярний вираз відповідає рядку, в якому за "a" слідує від 2 до 4 послідовностей "bc";
"a(bc){2,4}/"
Існує модифікатор "U", який інвертує жадібність. Наприклад, вираз "<.*>" відповідає рядку, що містить кілька тегів HTML-розмітки, повністю. Щоб виділити окремі теги, можна застосувати жадібність: "<.*?>" або "<.*>/U".

Жадібність квантифікаторів може виявитися значною проблемою. Наприклад, часто очікують, що вираз "<.*>" знайде у тексті теги HTML. Однак якщо в тексті є більше одного HTML-тега, то цьому виразу цілком відповідає рядок, що містить безліч тегів.

Функції для роботи з регулярними виразами

Після прочитання теоретичних основ настав час переходити до практичних. Для роботи з регулярними виразами є кілька функцій.

Першою розглянемо функцію Preg_match, яка здійснює пошук у рядку за регулярним виразом і має наступний синтаксис:
preg_match(
string $pattern,
string $subject,
array &$matches = null,
int $flags = 0,
int $offset = 0
): int|false
Функція Preg_match шукає у заданому тексті Subject збіг із шаблоном Pattern. Якщо заданий необов'язковий параметр Matches, результати пошуку поміщаються в масив. Елемент $matches[0] міститиме частину рядка, що відповідає входженню всього шаблону, $matches[i] - частину рядка, що відповідає першим круглим дужкам, $matches[2] - другим і т.д.

Необов'язковий параметр Flags може приймати єдине значення PREG_OFFSET_CAPTURE, при вказівці якого змінюється формат масиву, що повертається $matches - кожне входження повертається у вигляді масиву, в нульовому елементі якого міститься знайдени підрядок, а в першому - зміщення. Пошук здійснюється ліворуч, з початку рядка.

Функція Preg_match повертає кількість знайдених відповідностей, яка може приймати лише 2 значення - 0 (збіги не знайдені) та 1, оскільки ця функція припиняє свою роботу після першого знайденого збігу.

Для пошуку всіх збігів слід скористатися функцією Preg_match_all, яка має наступний синтаксис:
preg_match_all(
string $pattern,
string $subject,
array &$matches = null,
int $flags = 0,
int $offset = 0
): int|false
Функція Preg_match_all шукає у рядку Subject всі збіги з шаблоном Pattern і поміщає результат у масив Matches у порядку, що визначається комбінацією прапорів Flags. Так само як і в попередній функції можна задати зміщення Offset, починаючи з якого буде здійснюватися пошук в рядку Subject. Після знаходження першої відповідності наступні пошуки здійснюватимуться не з початку рядка, а від кінця останнього знайденого входження.

Перейдемо до функції, яка, крім пошуку, здійснює і заміну за регулярним виразом — Preg_replace:
preg_replace(
string|array $pattern,
string|array $replacement,
string|array $subject,
int $limit = -1,
int &$count = null
): string|array|null
Функція Preg_replace виконує пошук збігів у рядку Subject із шаблоном Pattern та замінює їх на Replacement.

Функція Preg_split розбиває рядок за регулярним виразом.
preg_split(
string $pattern,
string $subject,
int $limit = -1,
int $flags = 0
): array|false
Функція повертає масив, що складається з підрядків заданого рядка Subject, який розбитий за межами, що відповідають шаблону Pattern.

У більшості випадків використання вище описаних функцій цілком достатньо для вирішення багатьох завдань.

Також існують додаткові конструкції шаблонів:

(?#Коментар) - коментар у тілі шаблону. Іноді дуже корисно розмістити у тілі регулярного вираження конкретний коментар для кращого розуміння роботи.

(?: Шаблон) - угруповання як і "()", але без зворотного посилання. Це угруповання дуже корисне для завдання шаблону, але без створення зворотного посилання.

(?=шаблон) - "заглядання" вперед. Ця конструкція може знадобитися для пошуку за шаблоном з наперед вказаним виразом, наприклад, вираз "/\w+(?=\t)/" відповідає слову, за яким йде символ табуляції, але символ "\t" не включається до результату.

А тепер опишемо найчастіше вживані приклади використання регулярних виразів:

Перевірка правильності введення електронної пошти E-mail:
preg_match("/^[a-z0-9_\-\.]+@[a-z0-9_^\.]+\.[a-z]{1,6}$/ui", $email)
До символу собачки шаблон шукає букви та цифри, знак тире, нижнього підкреслення та крапки одне або більше число входжень починаючи з початку рядка:
^[a-z0-9_\-\.]+
Далі йде друга частина поштової адреси, починаючи з собачки, маючи той же набір символів, що і перша частина:
@[a-z0-9_^\.]+
Після цього перевіряємо доменну зону, яка складається виключно з рядка букв певної кількості символів до кінця рядка:
\.[a-z]{1,6}$
Також за допомогою регулярного виразу ми можемо вибрати всі E-mail з тексту:
$text = 'Тут текст та поштова адреса test@gmail.com і ще одна адреса admin@mail.ru';
preg_match_all("/[a-z0-9_\-\.]+@[a-z0-9_^\.]+\.[a-z]{1,6}/ui", $text, $matches, PREG_PATTERN_ORDER);
foreach ($matches[0] as $key => $val) {
$email = filter_var($val, FILTER_VALIDATE_EMAIL);
if ($email) $output[] = $email;
}
На відміну від перевірки правильності введення E-mail, при вибірці ми прибрали в шаблоні символ початку (^) і кінця ($) рядка. Результат цього прикладу:
Array
(
[0] => test@gmail.com
[1] => admin@mail.ru
)
Перевірка правильності введення імені:
preg_match('#^[а-яґїієa-z\-\_\'.\d\s]+$#ui', $name);
Перевірка правильності введення числа:
preg_match('/([0-9]+)/ui', $id)
Коректність введення дати:
$date = "2017.05.25";
preg_match('/^[0-9]{4}.[0-9]{2}.[0-9]{2}$/ui', $date);
Коректність введення часу:
preg_match("/^[0-9]{2}+:[0-9]{2}+:[0-9]{2}$/ui", $time))
Видалити всі визначення стилів Style:
preg_replace('/style=\"[^\"]*\"/', '', $string);
Оскільки стилі можуть знаходитися всередині практично будь-якого тега, у прикладі видаляється виключно визначення стилю без тега.

Видалити всі визначення малих елементів документа Span:
preg_replace('#<span[^>]*?>#is', '', $string);
preg_replace('#<\/span>#is', '', $table);
Так само можна видалити будь-який тег, наприклад, для заголовка H1:
preg_replace('#<h1[^>]*?>#is', '', $table);
preg_replace('#<\/h1>#is', '', $table);
Очищення таблиць за допомогою регулярних виразів PHP можна зробити так:
// Видаляємо всі атрибути Table:
$table = preg_replace('#<table.*>#siU', '<table>', $table););
// Видаляємо всі атрибути TR:
$table = preg_replace('#<tr.*>#siU', '<tr>', $table);
// Видаляємо всі атрибути TD (крім colspan або rowspan):
$table = preg_replace('#<td[^>]+((colspan|rowspan)=[^\s>]+?)(|.*)>#siU', '<td \\1>', $ table);
Перевірити правильність імені файлу можна за допомогою наступного регулярного виразу:
preg_match("/(^[a-zA-Z0-9]+([a-zA-Z\_0-9\.-]*))$/", $filename)
Вирізати всі зображення у тексті:
preg_replace("/<img (.*?)>/", '', $content)
Знайти усі посилання:
preg_match_all('#<a [^>]*href="(.*)"[^>]*>#Ui', $content , $url);
Уявіть ситуацію, коли користувач не використовує символ пропуску після крапки або коми. В даному випадку виходить дуже велике слово, яке не завжди може поміститися в поле, що провокує горизонтальну прокрутку. Щоб цього не сталося, можна скористатися наступним регулярним виразом, який після крапки або коми додаватиме символ пробілу:
preg_replace('/(\.|\,)([^\s])/ui', '$1 $2', $content)
Знайти всі хештеги (#tag) можна так:
preg_match_all("/\#(\w+[^\s]*)/ui", $text, $matches, PREG_PATTERN_ORDER);
Або вручну додати необхідні символи та їх кількість, дозволені для складання хештегів:
preg_match_all("/\#([a-zа-яґїіє0-9\-\_]{1,50})/ui", $text, $matches, PREG_PATTERN_ORDER);
Якщо необхідно додати до src зображення назву домену (при парсингу сторінки, на якій шлях src вказаний відносний, а потрібно зробити повний):
preg_replace('/src="(http:\/\/[^\/"]+\/?)?([^"]*)"/', "src=\"http://mysite.com/\\2\"", $text);
Якщо час у базі даних зберигіється у форматі «12:00:00», а потрібно вивести у «12:00», можна виконати так:
preg_replace("#(:\d+):\d+#", '$1', $row['time'])
Замінити подвійні пробіли одинарними можна так:
preg_replace('/ {2,}/ui', ' ', $content)
preg_replace("/[ ]+/", " ", $content)
Редакція «КовельPost» може не поділяти думку блогерів або дописувачів. За зміст публікацій і їх достовірність відповідальність несуть автори.

Коментарі

  • Андрій
    Дякуємо за доступну та легку для сприйняття інформацію.
    Відповісти
  • Гість
    Дуже допомогла інформація про хештеги, саме те, що було потрібно.
    Відповісти
  • Антон
    Спасибі, знайшов, що потрібно!
    Відповісти
  • Володимир
    Дякую що ви є!
    Відповісти
  • Женя
    Велике дякую за кишеньковий довідник, з Вашою допомогою вдалося заощадити безліч часу:)
    Відповісти

Підписуйтесь на наш канал у Telegram! 🚀

@kovelpost