Функция preg_match

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

Существует также функция preg_match(регулярка, где искать), которая проверяет, есть ли в строке совпадение с регуляркой.

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

Поэтому preg_match выводит либо 1, либо 0 (а не true или false) и используется для ответа на вопрос 'есть искомое в строке или нет', вернет 1 - значит есть (а сколько раз - неясно), вернет 0 - значит нет.

<?php
	echo preg_match('#a+#', 'eee aaa bbb'); //выведет 1
?>

Шаблон поиска такой: буква 'a' один или более раз.

<?php
	echo preg_match('#a+#', 'eee aaa aa bbb'); //все равно выведет 1
?>

Функция все равно вернет один, хотя совпадений на самом деле совпадений два.

<?php
	echo preg_match('#a+#', 'eee bbb'); //выведет 0
?>

Функция ничего не нашла - вернет 0.

Функция preg_match также может вернуть false в случае какой-либо ошибки.

Часто данная функция используется для проверки на соответствие регулярному выражению целой строки. Например, мы хотим узнать - данная строка корректный email или нет:

<?php
	echo preg_match('#^[a-zA-Z-.]+@[a-z]+\.[a-z]{2,3}$#', 'my-mail@mail.ru');
?>

Шаблон поиска такой:

[a-zA-Z-.]+ маленькие или большие латинские буквы, точка или '-' 1 или более раз, @ потом @,

[a-z]+ потом маленькие латинские 1 или более раз,

\. потом точка,

[a-z]{2,3} потом маленькие латинские два или три раза (ru, by, com и т.п.).

В результате мы получим 1 - наша строка будет корректным емэйлом.

А вот так получим ноль:

<?php
	echo preg_match('#^[a-zA-Z-.]+@[a-z]+\.[a-z]{2,3}$#', 'my-#mail@mail.ru');
?>

Обратите внимание на то, что мы вначале регулярки ставим ^, а в конце $ - этим мы говорим, что под шаблон должна попасть вся строка. Если их не поставить - тогда мы скажем не 'вся строка есть email', а 'в строке есть email':

<?php
	echo preg_match('#[a-zA-Z-.]+@[a-z]+\.[a-z]{2,3}#', '#$%my-mail@mail.ru&@$'); 
?>

Функция вернет 1 - она нашла одно совпадение (выделено голубым). Но переданная строка отнюдь не корректный email (она просто содержит его внутри, но по краям - мусор).

Функция preg_match_all

Функция preg_match находит только первое совпадение. Чтобы найти все совпадения - следует использовать preg_match_all(регулярка, где искать, найденное):

<?php
	echo preg_match_all('#a+#', 'eee aaa bbb', $m); //выведет 1
?>

Шаблон поиска такой: буква 'a' один или более раз.

<?php
	echo preg_match_all('#a+#', 'eee aaa aa bbb', $m); //выведет 2
?>

Функция нашла все совпадения и вернула 2.

<?php
	echo preg_match_all('#a+#', 'eee bbb', $m); //выведет 0
?>

Функция ничего не нашла - вернет 0.

Третий параметр

Функция preg_match_all имеет третий параметр: туда можно записать переменную, к которую сложатся все найденные совпадения (в виде массива):

<?php
	echo preg_match_all('#a+#', 'eee aaa aa bbb a',  $matches)); //выведет 3
	var_dump($matches);

	//Выведет массив совпадений:
	[0=>[0=>'aaa', 1=>'aa', 2=>'a']]
?>

Обратите внимание на многомерность этого массива! Почему так - об этом в следующем пункте.

Карманы

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

Чтобы разобраться с этим непростым понятием - смотрите пример:

<?php
	echo preg_match_all('#x(a+)x#', 'xax xaax xaaax', $matches); //выведет 3
	var_dump($matches);
?>

Шаблон поиска такой: буква 'x', затем 'a' один или более раз, затем буква 'x'.

А то, что стоит в ( ), ложится в карман - и занесется в массив $matches:

<?php
	[
		0=>[0=>'xax', 1=>'xaax', 2=>'xaaax'], //это найденные подстроки
		1=>[0=>'a', 1=>'aa', 2=>'aaa'] //это содержимое кармана
	]
?>

То есть карман - это такой способ хранения части того, что мы ищем. Например, мы ищем домены вида domain.ru, но хотим узнать только доменную зону (ru, com и т.п.).

В регулярке придется указать, что нам нужны строки вида 'domain.ru' (иначе мы ничего не найдем - мы не можем просто искать доменные зоны, так как непонятно как их найти, нужно привязаться к доменам), но раз нас интересует только зона - то положим ее в карман. Давайте посмотрим на примере:

<?php
	preg_match_all('#[a-z]+\.([a-z]{2,3})#', 'domain.ru site.com hello.by', $matches);
?>

Шаблон поиска такой: [a-z]+ - маленькие буквы один или более раз (domain, site и т.п.) \. - точка ([a-z]{2,3}) - маленькие буквы 2 или 3 раза (ru, com, by, net и т.п.)

Посмотрим содержимое $matches:

<?php
	var_dump($matches);
	[
		0=>[0=>'domain.ru', 1=>'site.com', 2=>'hello.by'],
		1=>[0=>'ru', 1=>'com', 2=>'by'] //это содержимое кармана
	]
?>

Можно использовать не один карман, а несколько. Положим в первый карман имя домена, а во второй - зону:

<?php
	preg_match_all('#([a-z]+)\.([a-z]{2,3})#', 'domain.ru site.com hello.by', $matches); 
	var_dump($matches);
	[
		0=>[0=>'domain.ru', 1=>'site.com', 2=>'hello.by'], //это найденные строки
		1=>[0=>'domain', 1=>'site', 2=>'hello'], //это 1-ый карман
		2=>[0=>'ru', 1=>'com', 2=>'by'], //это 2-ой карман
	]
?>

Карманы нумеруются по порядку в регулярке: '#([a-z]+)\.([a-z]{2,3})#' - первые круглые скобки - первый карман, вторые - второй карман и так далее.

Изменение поведения preg_match_all

По умолчанию preg_match_all складывает найденные строки в один подмассив, 1-ый карман в другой и так далее. Это поведение можно изменить (иногда это очень нужно) с помощью 4-го параметра функции.

Подробную информацию об этом посмотрите по ссылке http://www.php.su/functions/?preg-match-all.

Карманы внутри preg_replace

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

Если мы что-то положим в карман в регулярке, то в параметре 'на что заменить' мы можем обратиться к этому карману так: $1 – первый карман, $2 второй карман и так далее.

Давайте решим следующую задачу: даны строки вида 'aaa@bbb' - буквы, потом собака, потом буквы. Нужно поменять местами буквы до @ и после. В нашем случае из 'aaa@bbb' сделать 'bbb@aaa':

<?php
	echo preg_replace('#([a-z]+)@([a-z]+)#', '$2@$1', 'a@b aa@bb'); //b@a bb@aa
?>

Шаблон поиска такой: маленькие буквы один или более раз (первый карман), собака, и опять маленькие буквы один или более раз (второй карман).

Шаблон замены такой: мы говорим: замени найденное на $2@$1, где $2 – содержимое второго кармана, а $1 - первого.

Давайте разберем подстроку 'a@b', что с ней происходит: 'a' ложится в первый карман и доступно как $1, 'b' ложится во второй карман и доступно как $2.

При замене $2@$1 мы говорим: $2 (вставится 'b'), собака @, $1 (вставится 'a').

Карман $0 соответствует всему выражению. Давайте заменим подстроки из букв на них самих с '!' по краям:

<?php
	echo preg_replace('#[a-z]+#', '!$0!', 'aaa bbb'); //выведет '!aaa! !bbb!'
?>

!$0! - $0 это найденная строка (сначала 'aaa', потом 'bbb'). Мы говорим: замени 'aaa' на ее саму ($0), но с '!' по краям !$0!. И получаем '!aaa!'. Для 'bbb' аналогично.

Карманы внутри регулярки

Карманы можно использовать и внутри самой регулярки: \1 – первый карман, \2 – второй, и так далее. Разберите следующий пример, чтобы понять, для чего это нужно и как этим пользоваться:

<?php
	//Найдем две одинаковые буквы подряд и заменим на '!':
	echo preg_replace('#([a-z])\1#', '!', 'aaebbc'); //выведет '!e!c'
?>

Как это работает: команда [a-z] ищет букву, при этом найденная буква ложится в карман. Затем в регулярке стоит \1, который говорит, что после первой буквы должно идти содержимое первого кармана (а в нем лежит первая буква).

Таким образом, мы найдем две идущие подряд одинаковые буквы и заменим на '!'.

Несохраняющие скобки

К сожалению, скобки ( ) выполняют две функции - группировка символов и функцию кармана. А что делать, если нам нужно сгруппировать, но в карман не ложить? Для этого придуманы специальные несохраняющие скобки (выделены красным) (?:a+) - они группируют, но не ложат в карман:

<?php
	echo preg_replace('#(?:ab)+([a-z])#', '!$1!', 'ababx abe'); //выведет '!x! !e!'
?>

Шаблон поиска следующий: 'ab' один или более раз, затем одна буква, которую ложим в карман.

Шаблон замены: заменим найденное на первый карман, обернутый '!' справа и слева.

Так как первый карман - это ([a-z]), то в него попадет сначала 'x', а потом 'e'.

А вот если мы вместо несохраняющих скобок возьмем обычные, то первый карман будет (ab):

<?php
	echo preg_replace('#(ab)+([a-z])#', '!$1!', 'ababx abe'); //выведет '!ab! !ab!'
?>

Обратите внимание: + не размножает карманы - 'abab' превратится в '!', а не '!!'.

Но мы-то хотели, чтобы в карман попадало не 'ab', а то, что после него. Это будет уже второй карман:

<?php
	echo preg_replace('#(ab)+([a-z])#', '!$2!', 'ababx abe'); //выведет '!x! !e!'
?>

Получается немного неудобно - $1 (первый карман) нигде не используется. А начинаем сразу с $2. Чтобы не было таких неудобств - и придуманы скобки (?: ).

Хотя, если вас это не смущает, - можно их и не использовать.

Исключение: когда у вас много группировок, а карманов мало - будет неудобно считать все скобки, чтобы понять, что вам нужно $5 и $9, а остальные карманы просто группировка. А если что-то поменяется - то придется все пересчитывать. Тут точно лучше использовать (?: ) для группировки, а ( ) - только для карманов.