PHP Manual

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

08. 03. 2020

Регулярні вирази - це інструменти, які дозволяють легко здійснювати пошук, перевірку, порівняння, розбиття, згортання і заміну рядків за маскою (шаблоном). Це дуже потужний і елегантний інструмент для складних маніпуляцій зі струнами.

Маска

На початку нам спочатку потрібно придумати власне регулярний вираз, який ми будемо виконувати. Він вводиться у вигляді текстового рядка, який має купу правил і варіантів конфігурації (це дуже складна техніка).

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

На поведінку і стратегію обробки регулярного виразу можна впливати за допомогою перемикачів, яких існує безліч.

Проста перевірка того, що рядок є дійсним e-mail

Як можна просто перевірити, що рядок jan@barasek.com є дійсною адресою електронної пошти, не розбиваючи його на складні частини і не перебираючи символ за символом?

Відповідь дають регулярні вирази (наведений вище вираз дуже спрощений для цілей прикладу, і реальна реалізація валідації електронної адреси повинна бути дещо складнішою):

$mail = 'jan@barasek.com';
$regex = '/^.+@.+\.(en|en|com)$/';
if (preg_match($regex, $mail)) {
echo 'Електронна пошта дійсна';
} else {
echo 'E-mail не дійсний';
}

Розглянемо вираз /^.+@.+\.(en|en|com)$/ трохи детальніше:

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

При обробці виразу виходите з лівої частини посимвольно. Кожна з них має своє значення, яке наведено у наступній таблиці:

Символ Значення Опис Приклад
Початок рядка ^ Початок рядка Змушує рядок починатися з цієї точки.
$ Кінець рядка або рядка Вказує, що рядок або рядок повинен закінчуватися тут. Кінець рядка підтверджується символом \z. Детальне пояснення. Ім'я файлу повинно бути текстовим (закінчуватися крапкою, а потім рядком "txt"): /\.txt$/.
Будь-який символ . Будь-який символ Ловить точно будь-який символ.
Номер ¦ Виявляє символи 0-9 Виявляє номер телефону, який не містить пробілів і має 9 цифр: /^(\+420)?\d{9}$/.
Пробіли Пробіли, дефіси і табуляції. Дозволяє пропускати пробіли між цифрами в номері телефону в тризначних числах: /^(\d{3}\s?){3}$/.
+ Кілька символів, але хоча б один Повторює попередній підвираз і намагається вловити якомога більше. Підвираз повинен повторюватись хоча б один раз. Перехоплює якомога більше цифр, але хоча б одну: /\d+/.
* Кілька символів, може бути жодного Працює так само, як +, але дозволяє перехопити порожній рядок (значення не обов'язково має бути присутнім). Перехоплює якомога більше цифр, якщо їх немає, то перехоплює порожній рядок: /\d*/.
Дужки (``) Вказує на підвираз. Це може бути використано для того, щоб вкласти кілька різних тегів і потім вимагати, наприклад, повторення над ними, або для того, щоб захопити їх вміст у змінну. Розділимо поштовий індекс на 2 частини відповідно до пробілу, який не є обов'язковим і його може бути навіть більше одного: /^(\d{3})\s*(\d{2})$/
Містить підвираз або інший підвираз. Число, що починається з +420 або +421: /^+(420|421)\s*\d+$/.
Екранування Якщо ми хочемо зловити символ у виразі, який в іншому випадку має особливе значення, нам потрібно екранувати його так само, як, наприклад, рядки в PHP. Ловить пару чисел, розділених крапкою (якби ми не екранували крапку, це було б зрозуміло як "будь-який символ"): /\d+\.\d+/.

Для повноти картини наведу повну форму правила валідації для електронної пошти, реалізовану Nette:

/**
* Визначає, чи є рядок дійсною адресою електронної пошти.
*/
public static function isEmail(string $value): bool
{
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part // RFC 5322 unquoted characters in local-part
$alpha = "a-z\x80-\xFF"; // надмножина IDN
return (bool) preg_match("(^
(\"([ !#-[\\]-~]*|\\\\[ -~])+\"|$atom+(\\.$atom+)*) # quoted or unquoted
@
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
\\z)ix", $value);
}

preg_match() - перевірка за зразком

Основною функцією для перевірки та розбору формату є preg_match(), вона має 2 обов'язкових параметри, а третій може бути використаний для вказівки поля виводу.

Приклад:

$psc = '272 01'; // Kladno
if (preg_match('/^(\d{3})\s*(\d{2})$/', $psc, $parser)) {
echo 'Поштовий індекс дійсний [' . $parser[1] . ','. $parser[2] . ']';
} else {
echo 'Поштовий індекс не дійсний';
}

Код повернеться: Code is valid [272, 01].

Зверніть увагу на одинарні круглі дужки, якими ми розбили вираз на кілька менших частин. Це дає змогу отримати окремі під-вирази як елементи масиву. Потім вся функція повертає значення true або false в залежності від того, чи було успішно перехоплено рядок.

Іноді, однак, орієнтуватися в числовому порядку дужок дуже складно, оскільки їх кількість може змінюватися, або їх може бути просто занадто багато. У цьому випадку є можливість назвати дужки індивідуально і потім звертатися до клавіш за їхніми іменами.

Наприклад:

$phone = '777 123 456';
preg_match('/^(?<оператор>\d{3})\s*(?<число>[0-9 ]+)$/', $phone, $parser);
echo $parser['оператор']; // повернуто 777

preg_replace() - заміна за зразком

Також можлива заміна рядків за допомогою regex, що особливо корисно для різних виправлень формату після користувача.

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

У такому разі я дотримуюся цієї сентенції:

"Будь щедрим у тому, що отримуєш, і суворим у тому, що посилаєш".

Тому ми автоматично підлаштовуємо формат. Спочатку за допомогою синтаксичного розбору розбиваємо рядок на окремі частини, а потім складаємо його назад відповідно до номерів дужок:

function formatPhoneNumber(string $phoneNumber): int
{
return (int) preg_replace(
'/^(\+\d{3})\s*(\d{3})\s*(\d{3})\s*(\d{3})$/',
'$2$3$4',
$phoneNumber
);
}
echo formatPhoneNumber('+420 777 123 456');

Конструювання рядка за регулярним виразом

Регекси також мають великий сенс при створенні нових рядків за складним шаблоном.

Чистий PHP не має такої підтримки, але ми можемо завантажити сторонню бібліотеку ReverseRegex, яка може це зробити.

Наприклад, ми можемо захотіти згенерувати набір паролів на основі регексу [a-z]{10} і нас ніщо не зупинить:

jmceohykoa
aclohnotga
jqegzuklcv
ixdbpbgpkl
kcyrxqqfyw
jcxsjrtrqb
kvaczmawlz
itwrowxfxh
auinmymonl
dujyzuhoag
vaygybwkfm

Застосування полягає в наступному:

use ReverseRegex\Lexer;
use ReverseRegex\Random\SimpleRandom;
use ReverseRegex\Parser;
use ReverseRegex\Generator\Scope;
require 'vendor/autoload.php';
$lexer = new Lexer('[a-z]{10}');
$gen = new SimpleRandom(10007);
$result = '';
$parser = new Parser($lexer, new Scope(), new Scope());
$parser->parse()->getResult()->generate($result, $gen);
echo $result;

Я, наприклад, генерую свої математичні приклади в Nette in Presenter таким чином, і це дуже легко:

public function actionRegex(): void
{
$lexer = new Lexer('\d{1,3}[\+\-\*\/]');
$parser = new Parser($lexer, new Scope(), new Scope());
for ($i = 0; $i <= 10; $i++) {
$result = '';
$gen = new SimpleRandom($i);
$parser->parse()->getResult()->generate($result, $gen);
dump($result);
}
$this->terminate();
}

Важливим для бібліотеки є те, що вона все ще генерує той самий вивід на той самий ввід (навіть якщо може здатися, що для кожного регулярного виразу існує багато можливих рядків, які можна підібрати). Якщо ми хочемо випадковим чином змінювати згенерований вираз, нам також потрібно змінити "насіння", за допомогою якого генерується вихідний рядок. Для цього може бути корисним або обхід посівного інтервалу, або, можливо, функція rand(1, 1e6).

Виявлення та обробка помилок

У PHP відловлювати помилки в регексах - це справжнє пекло, але рішення все ж є.

Детально про це розповідається в статті Досяжні регулярні вирази в PHP Девіда Груделя.

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
5.
Status:
All systems normal.
2024