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

08.06.2018 в 12:15
21846
+1653

Регулярные выражения - это механизм для поиска и замены в строках. Но не простого, типа поиска подстроки в строке, а более сложного - с определенными условиями.

Например:

  • вам нужно удалить логи веб-сервера за 2019 год. Для этого нужно найти в папке все файлы, у которых имя соответствует маске apache-XX.XX.2019.log;
  • найти в тексте все ссылки, домены которых находятся в доменной зоне .ru (http://адрес-сайта.ru/путь-до-странички), игнорируя при этом .com и другие.

Регулярные выражения отлично подходят для решения подобных задач - с их помощью можно делать поиск и замену по маске. Надеюсь, вам стало понятно, какие проблемы с их помощью можно решать - там, где есть сложные условия для строк.

Итак, давайте начнём с одного простого правила: чтобы написать шаблон для регулярного выражения, его нужно окружить с двух сторон одинаковым символом. Как правило, используется символ слэша - /.

Чтобы найти в строке какое-то совпадение, достаточно просто записать это совпадение внутри двух слэшей.

В PHP есть очень много функций для работы с регулярками - как для поиска, так и для замены. Самая простая функция для поиска по регуляркам - preg_match().

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

preg_match('/шаблон для поиска/', 'строка, в которой ищем совпадения по шаблону', $matches);

$matches - это переменная, в которую будут записаны найденные совпадения. Это будет массив таких значений. Не найдено ничего - он будет пустой. Два совпадения - два элемента.

Давайте приведём пример. Найдём, слово "век" в строке "человек".

<?php

$pattern = '/век/';
$string = 'человек';

preg_match($pattern, $string, $matches);

var_dump($matches);

Результат:

array (size=1)
  0 => string 'век' (length=6)

Для того, чтобы быстрее составлять регулярки, есть онлайн-сервисы. Они позволяют не переписывать шаблон и запускать каждый раз код, а делать всё это прямо в браузере. Я неоднократно пользовался сервисом https://regex101.com/ и вам его рекомендую.

Заходите, вводите строку, в которой ищете, вводите шаблон, на выходе получаете совпадения. Согласитесь - это очень удобно. Результат изменений сразу перед глазами.

Я ввёл в шаблон "/ку/" и строку "кукушка". В результате получил 2 совпадения.

Помимо этого у сервиса есть вкладка "Code generator", на которой вы сразу сможете получить готовый код на PHP для своего примера:

Символы

Давайте теперь перейдём к более интересным примерам.

Предположим, нам нужно найти в строке все совпадения по шаблону: "к, где - любой символ. В регулярных выражениях для обозначения любого символа используется символ точки ".".

Давайте найдём такие совпадения для строки "кукушки не кушают шоколадки". Паттерн примет следующий вид: "/к./". То есть буква "к" и любой символ.

Видим, что нашлось немало совпадений. Давайте добавим теперь после точки ещё одну букву "ш".

Теперь совпадения только два.

Для того, чтобы найти в тексте точку, её нужно экранировать - поставить перед ней обратный слеш. Посмотрите пример:

Нужно найти несколько подряд идущих точек? Да не вопрос! Заэкранируйте их все:

Разумеется, в регулярках символ точки - не единственный, который делает некоторую магию, мы разберем ещё несколько из них в рамках этой статьи. Нужно помнить простое правило - если мы хотим найти такой специальный символ в тексте, не применяя его "магии", мы должны просто его экранировать, как в примере с точкой.

Квантификаторы

А теперь, допустим мы хотим найти совпадения по маске "ку***" - "ку" и три любых символа. Да пожалуйста:

Однако, для таких случаев принято использовать квантификаторы - это такие конструкции в регулярных выражениях, которые позволяют задать число символов, стоящих перед ними.

Для этого используются 2 числа в фигурных скобках. {ОТ, ДО} - от скольки повторений и до скольки.
Например, мы хотим найти в тексте все комбинации точек, идущих друг за другом, в количестве от одной до трёх. Тогда наш паттерн примет следующий вид: ".{1,3}" - символ точки, повторяющийся от одного до трех раз.

Если что-то должно повториться точное число раз, например, 3, то пишется просто {3}.

Есть также квантификаторы, которые используются чаще других и для них сделаны специальные символы:

  • ? (знак вопроса) - предшествующий символ либо есть, либо его может не быть. Аналог - {0, 1}.
    • (знак звёздочки) - предшествующий символ повторяется от 0 (его нет) до бесконечности раз. Аналог - {0, бесконечность}.
    • (знак плюса) - предшествующий символ повторяется от 1 до бесконечности раз. Аналог - {1, бесконечность}.

Немного примеров:
Символ точки, повторяющийся от одного и более раз

Восклицательный знак, перед которым либо есть вопросительный знак, либо нет

Модификаторы

Глобальный поиск

А не обратили ли вы внимание на букву g после закрывающего слеша в паттерне?

Эта g - модификатор, который говорит о том, что не стоит останавливаться при нахождении первого совпадения. Давайте попробуем его убрать и посмотрим, что получится.

Видите? Теперь только одно совпадение осталось.

В PHP при этом для поиска как бы с модификатором g используется функция preg_match_all(), а без этого модификатора - preg_match().

Жадность

Есть ещё один довольно популярный модификатор, который позволяет сделать поиск либо жадным, либо нежадным. Жадный поиск захватывает максимально возможную подстроку. Давайте рассмотрим вот такой пример:

Паттерн "к.+к" - буква к, затем любые символы в количестве от одного до бесконечности, и снова буква к.

Видите, какую строку захватило? А если бы нам хотелось остановиться на первой букве "к"? Тогда нам просто нужно было бы сделать поиск нежадным. Для этого используется модификатор "U"

В PHP он при этом указывается после закрывающего слеша паттерна:

$pattern = '/к.+к/U';

Вообще, все остальные модификаторы как и "U" указываются после слеша, это только для модификатора g пришлось сделать две разные функции.

Итак, давайте сформулируем основные тезисы по жадности и нежадности:

  • жадный поиск захватывает максимально возможную подстроку;
  • нежадный - минимально возможную;
  • по умолчанию включен жадный поиск;
  • нежадный включается с помощью модификатора U, идущего после ограничителей регулярки.

Классы символов

Очень часто в регулярках возникает потребность в перечислении символов, которые подходят под условия. Например, нужно найти символ двоеточия, после которого идут три цифры. Для таких случаев используют классы символов. Это символы, помещённые в квадратные скобки. Примеры таких классов:

  • [a,b,c] - буква a, b или c;
  • [0-9] - цифры от 0 до 9;
  • [a-zA-Z-] - английские буквы от a до Z и знак минус (если его добавляют в класс, то всегда в самый конец).

Когда вы используете такой класс в шаблоне, это соответствует одному из символов из этого шаблона. Не нескольким, а одному из них! Чтобы было несколько, нужно использовать квантификаторы.

Пример: двоеточие, после которого идут три цифры от 0 до 3

Если нужно сделать отрицание, то внутри таких кавычек перед остальными ставится символ "^".

  • [^0-3] - всё, кроме чисел от 0 до 3;
  • [^a-zA-Z] - что угодно, кроме английских букв.
  • [^a-zA-z0-3]+ - последовательность чего угодно, кроме английских букв и цифр от 0 до 3

Давайте теперь применим наши уже имеющиеся знания для проверки того, что нам передали корректный российский номер телефона. Пусть у нас будет следующее простое правило: номер должен начинаться с +7, а далее должны идти 10 цифр от 0 до 9.

Изи, изи!

Якоря в регулярных выражениях

Якорь - это такой специальный символ, который обозначает позицию чего-либо. К примеру, нам нужно найти конец строки и проверить, что в конце стоит точка. Для этого есть якорь "$". Использование выглядит следующим образом:

Паттерн ".$" - точка в конце строки. Всё просто.

Также нам часто приходится говорить о начале строки. Для этого есть якорь "^".

Несмотря на то, что в строке есть два слова "кукушки", под шаблон попало только первое, так как оно находится в начале строки.

Давайте вернёмся к примеру с телефонами. В прошлый раз мы использовали шаблон "/+7[0-9]{10}/". Однако, если строка содержит больше цифр в конце, то она просто отбросит лишнее.

По сути, этот телефон некорректный. Однако, с помощью якорей мы можем сделать так, что в строке будет только телефон, без лишней лабуды. Некорректный телефон не попадает в совпадение:

А корректный попадает:

Многострочный режим

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

Это происходит потому, что всё это поле считается одной строкой. Чтобы для каждой строки были свои якоря ^ и $ нужно включить многострочный режим с помощью модификатора "m".

"ИЛИ" в регулярках

Для случаев, когда нам нужно использовать что-то на выбор, либо одно, либо другое, нужно использовать конструкцию вида:

/(вариант1|вариант2)/

Если совпадёт хотя бы один из вариантов - совпадение найдено.

Пример:

Маски

Маски это очень крутая вещь в регулярках, которая используется повсеместно. Благодаря им можно не только проверить строку на соответствие шаблону, но и выделить из неё некоторые подстроки и использовать их отдельно!

Пример задачи: нужно из строки Меняем автора статьи 123 c "Иван" на "Пётр" извлечь идентификатор статьи и имена авторов.

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

Нагляднее всего это можно увидеть на примере:

Видите, справа у нас появились дополнительные совпадения? В PHP мы можем сделать из этого отдельные переменные!

Делается это проще простого:

$pattern = '/Меняем автора статьи ([0-9]+) c "(.+)" на "(.+)"./';
$str = 'Меняем автора статьи 123 c "Иван" на "Пётр".';

preg_match($pattern, $str, $matches);

var_dump($matches);

Результат:

array (size=4)
  0 => string 'Меняем автора статьи 123 c "Иван" на "Пётр".' (length=72)
  1 => string '123' (length=3)
  2 => string 'Иван' (length=8)
  3 => string 'Пётр' (length=8)

Первым в $matches всегда будет полное совпадение строки шаблону. А далее - значения, попавшие в маски.

Соответственно, нам остаётся лишь определить переменные под всё это дело:

$pattern = '/Меняем автора статьи ([0-9]+) c "(.+)" на "(.+)"./';
$str = 'Меняем автора статьи 123 c "Иван" на "Пётр".';

preg_match($pattern, $str, $matches);

$articleId = $matches[1];
$oldAuthor = $matches[2];
$newAuthor = $matches[3];

Вот и всё!

Однако, если ничего не совпало, в $matches будет пустой массив, и в реальном коде это всегда нужно учитывать и добавлять дополнительные проверки.

А ещё маске можно дать имя прямо в шаблоне, вот так:

(?P<имя_маски>содержимое)

В коде будет выглядеть вот так:

$pattern = '/Меняем автора статьи (?P<articleId>[0-9]+) c "(.+)" на "(.+)"./';
$str = 'Меняем автора статьи 123 c "Иван" на "Пётр".';

preg_match($pattern, $str, $matches);

var_dump($matches);

Теперь $matches примет вид:

array (size=5)
  0 => string 'Меняем автора статьи 123 c "Иван" на "Пётр".' (length=72)
  'articleId' => string '123' (length=3)
  1 => string '123' (length=3)
  2 => string 'Иван' (length=8)
  3 => string 'Пётр' (length=8)

И мы можем получить id статьи вот так:

$articleId = $matches['articleId'];

Ещё один реальный пример

Практически на любом современном сайте все адреса вида: https://php.zone/post/892

Эти адреса нужно обрабатывать прямо в коде, и понимать, что показать пользователю. Это называется "роутинг" - специальные правила, по которым нужно преобразовать адрес, по которому пришел пользователь, в конкретное действие на стороне сервера. То есть получив адрес вида /post/892 ваш код должен понять, что:

  1. это post, то есть статья
  2. идентификатор этой статьи - 892

Дальше он ищет статью в базе данных с таким идентификатором и возвращает её пользователю.

Вопрос: как это сделать? Да проще простого! Создать список шаблонов для всех страниц сайта. И когда прилетает запрос на /post/[0-9]+ - то искать статью с каким-то идентификатором. Да, да, вы уже догадались, сделать это можно с помощью регулярок. И это будет вашим домашним заданием.

Это далеко не вся инфа о регулярках, но этого достаточно для 95% решения реальных задач. Забивать голову остальным и упарываться не советую :)

Домашка

У вас есть переменная

$url = '/post/892';

С помощью регулярных выражений выдерните из этой переменной 2 значения и положите их в переменные:

  • $controller - сюда положите строку 'post'
  • $id - сюда положите число 892
loader
08.06.2018 в 12:15
21846
+1653
Комментарии
Этот урок набрал набрал достаточно большое количество комментариев и дальнейшее его комментирование отключено. Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку, посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали. Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
Логические задачи с собеседований