Проверка данных регулярными выражениями в php

Утверждения

Утверждения — это проверки касательно символов, идущих до или после текущей позиции сопоставления, ничего при этом не поглощая (никакие символы исходного текста не ставятся в соответствие утверждениям). Наиболее простые варианты утверждений, такие как \b, \B, \A, \Z, \z, ^ и $ рассмотрены в escape-последовательности. Более сложные утверждения записываются как подмаски. Утверждения бывают двух видов: те, которые анализируют текст, следующий после текущей позиции (look ahead), и идущий перед ней (look behind).

Сопоставление подмаски, содержащий утверждение, происходит обычным образом, за исключением того, что текущая позиция не изменяется. Утверждения касательно последующего текста начинаются с (?= для положительных утверждений и с (?! для отрицающих утверждений. Например,

\w+(?=;)


совпадает со словом, за которым следует символ ‘;’, но при этом сама точка с запятой в совпадение не включается. А

foo(?!bar)

соответствует любому появлению «foo», после которого не идёт «bar». Заметим, что похожий шаблон

(?!foo)bar

не будет искать вхождение «bar», которому предшествует любая строка за исключением «foo». Но, тем не менее, он будет соответствовать любым вхождениям подстроки «bar», поскольку условие (?!foo) всегда , если следующие три символа — «bar». Для получения желаемого результата необходимо воспользоваться второй категорией утверждений.

Утверждения касательно предшествующего текста начинаются с (?<= для положительных утверждений и (?<! для отрицающих. Например,

(?<!foo)bar

найдёт вхождения «bar», которым не предшествует «foo». Сами утверждения ‘назад’ ограничены так, чтобы все подстроки, которым они соответствуют, имели фиксированную длину. Но, в случае, если используются несколько альтернатив, они не обязаны иметь одинаковую длину. Таким образом шаблон

(?<=bullock|donkey)

корректен, но

(?<!dogs?|cats?)

вызовет ошибку во время компиляции. Ветки, которые соответствуют строкам разной длины, разрешены только на верхнем уровне утверждений касательно предшествующего текста. Это расширение относительно Perl 5.005, который требует чтобы все ветки соответствовали строкам одинаковой длины. Такое утверждение как

(?<=ab(c|de))

не корректно, поскольку верхний уровень маски может соответствовать строкам разной длины, но его можно преобразовать к корректному шаблону, используя альтернативы на верхнем уровне:

(?<=abc|abde)

Утверждения касательно предшествующего текста реализованы так, что для каждой альтернативы текущая позиция временно переносится назад, на фиксированную ширину, после чего выполняется поиск соответствия условию. В случае, если перед текущей позицией недостаточно символов, поиск соответствия терпит неудачу. Утверждения назад в сочетании с однократными подмасками могут быть особенно удобны для поиска в конце строки; соответствующий пример приведен в конце раздела «Однократные подмаски».

Несколько утверждений (разных типов) могут присутствовать в утверждении, например:

(?<=\d{3})(?<!999)foo

совпадает с подстрокой «foo», которой предшествуют три цифры, отличные от «999». Следует понимать, что каждое из утверждений проверяется относительно одной и той же позиции в обрабатываемом тексте. Вначале выполняется проверка того, что предшествующие три символа — это цифры, затем проверяется, чтобы эти же цифры не являлись числом 999. Приведенный выше шаблон не соответствует подстроке «foo», которой предшествуют шесть символов, первые три из которых — цифры, а последние три не образуют «999». Например, он не соответствует строке «123abcfoo», в то время как шаблон

(?<=\d{3}…)(?<!999)foo соответствует.

В этом случае анализируются предшествующие шесть символов на предмет того, чтобы первые три из них были цифрами, а последние три не образовали «999».

Утверждения могут быть вложенными, причем в произвольных сочетаниях:

(?<=(?<!foo)bar)baz

соответствует подстроке «baz», которой предшествует «bar», перед которой, в свою очередь, нет «foo», а

(?<=\d{3}…(?<!999))foo

— совершенно другой шаблон, соответствующий подстроке «foo», которой предшествуют три цифры и три произвольных символа, отличных от «999».

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

В утверждениях обрабатывается не более, чем 200 захватывающих подмасок.

Problems

There are several serious issues with the modifier:

Security issues

As evaluates arbitrary PHP code it can easily be exploited if user input is not carefully validated or sanitized.

For example the above example can be used to execute arbitrary PHP code by passing the string . The evaluted code in this case would be and as such execute any PHP code passed in the GET variable.

Alternative

A both more secure and cleaner approach is the use of :

preg_replace_callback(
    '(<h()>(.*?)</h\1>)',
    function ($m) {
        return "<h$m>" . strtoupper($m2) . "</h$m>";
    },
    $code
);

When passing the above exploit code () into this function the result will just be and no code will be executed.

But we don’t remove eval() either, do we?

The use of the modifier is not comparable to the use of . *Any* use of can be replaced with a safer and cleaner use of . on the other hand has *some* valid use cases which can not be implemented in any other (sane) way.

Additionally it is much easier to create a vunerability with than it is with . Firstly many people don’t understand PCRE well enough to distinguish whether a use of is safe or not. But more importantly the fact that addslashes() is applied to all substituted backreferences creates a false feeling of security (as seen above it can be easily circumvented.)

Overescaping of quotes

The application of on all substituted backreferences not only isn’t helping security, but additionally also results in unexpected behavior when the input contains quotes:

always escapes both quote types, but only one of them needs escaping (e.g. in double quoted strings only should be escaped and in single quoted strings only ‘ should be escaped). This will result in one of the quote types to be overescaped. E.g. if is passed into the above function the result would be (note the additional backslashes).

This behavior makes unusable in many cases (or people just use it anyways, without knowing that it is broken).

Use as obfuscation in exploit scripts

Various exploit scripts use the modifier to obfuscate their intention. Some examples from a quick SO search:

This obfuscation hides scripts from -like searches (as is usually considered a “good” function). Additionally — as you can see in the second link — it is possible to obfuscate the use of the modifier itself, making it even harder to find.

Obviously the use by exploit scripts is not bad per se (most exploit scripts also use and we better keep that), so don’t put too much weight on this argument 😉

Выражайтесь!

PHP-приложения применяются для обработки все больших объемов информации. Если необходимо верифицировать входную информацию или разложить данные на части, пользуйтесь регулярными выражениями.

Похожие темы

Оригинал статьи: «Mastering regular expressions in PHP, Part 1: Perl may be regex king, but PHP can slice and dice input quickly, too» (EN).

Познакомьтесь со статьей на PHP.net (EN).

PHP.net — ведущий ресурс для PHP-разработчиков (EN).


Познакомьтесь с «Рекомендательным списком литературы по PHP» (EN)

Повысьте свою квалификацию в области разработок на PHP с помощью раздела Ресурсы с PHP-проектами на developerWorks (EN). Интересные интервью и обсуждения для разработчиков ПО представлены в разделе Подкасты на сайте developerWorks (EN).

Работаете с базами данных и PHP? Обратите внимание на Zend Core для IBM — интегрированную, не требующую настройки, легко устанавливаемую среду для разработки и исполнения ПО на PHP, поддерживающую IBM DB2 V9 .

В разделе Open source на developerWorks можно найти исчерпывающую практическую информацию, инструментальные средства и новости проектов, которые помогут вам в разработке технологий с открытым исходным кодом и их использовании с другими продуктами IBM (EN).

Внесите инновации в свой следующий проект по разработке ПО с открытым исходным кодом с помощью пробной версии программного обеспечения IBM, которые можно скачать или заказать на DVD-диске (EN).

Загрузите демо-версии продукта IBM и воспользуйтесь средствами разработки приложений и связующим ПО DB2, Lotus, Rational, Tivoliи WebSphere (EN).

Практические примеры сложных регулярных выражений

Теперь, когда вы знаете теорию и основной синтаксис регулярных выражений в PHP, пришло время создать и проанализировать некоторые более сложные примеры.

1) Проверка имени пользователя с помощью регулярного выражения Начнем с проверки имени пользователя. Если у вас есть форма регистрации, вам понадобится проверять на правильность имена пользователей. Предположим, вы не хотите, чтобы в имени были какие-либо специальные символы, кроме «» и, конечно, имя должно содержать буквы и возможно цифры. Кроме того, вам может понадобиться контролировать длину имени пользователя, например от 4 до 20 символов.

Сначала нам нужно определить доступные символы. Это можно реализовать с помощью следующего кода:

После этого нам нужно ограничить количество символов следующим кодом:

{4,20}

Теперь собираем это регулярное выражение вместе:

^{4,20}$

В случае Perl-совместимого регулярного выражения заключите его символами ‘‘. Итоговый PHP-код выглядит так:

<?php
$pattern  = '/^{4,20}$/';
$username = "demo_user-123";
if (preg_match($pattern, $username)) {
 echo "Проверка пройдена успешно!";
} else {
 echo "Проверка не пройдена!";
}
?>

2) Проверка шестнадцатеричного кода цвета регулярным выражением Шестнадцатеричный код цвета выглядит так: , также допустимо использование краткой формы, например . В обоих случаях код цвета начинается с и затем идут ровно 3 или 6 цифр или букв от a до f.

Итак, проверяем начало кода:

^#

Затем проверяем диапазон допустимых символов:

После этого проверяем допустимую длину кода (она может быть либо 3, либо 6). Полный код регулярного выражения выйдет следующим:

^#(({3}$)|({6}$))

Здесь мы используем логический оператор, чтобы сначала проверить код вида , а затем код вида . Итоговый PHP-код проверки регулярным выражением выглядит так:

<?php
$pattern = '/^#(({3}$)|({6}$))/';
$color   = "#1AA";
if (preg_match($pattern, $color)) {
 echo "Проверка пройдена успешно!";
} else {
 echo "Проверка не пройдена!";
}
?>

3) Проверка электронной почты клиента с использованием регулярного выражения Теперь давайте посмотрим, как мы можем проверить адрес электронной почты с помощью регулярных выражений. Сначала внимательно рассмотрите следующие примеры адресов почты:

john.doe@test.com
john@demo.ua
john_123.doe@test.info

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

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

^+

Доменное имя всегда имеет, скажем, имя и tld (top-level domain) – т.е, доменную зону. Доменная зона – это , , и тому подобное. Это означает, что шаблон регулярного выражения для домена будет выглядеть так:

+\.{2,5}$

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

^+@+\.{2,5}$

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

<?php
$pattern = '/^+@+\.{2,5}$/';
$email   = "john_123.doe@test.info";
if (preg_match($pattern, $email)) {
 echo "Проверка пройдена успешно!";
} else {
 echo "Проверка не пройдена!";
}
?>

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

Регулярные выражения: никакой магии

Перевод

Код этого поста, как и сам пост, выложен на github. До недавнего времени регулярные выражения казались мне какой-то магией. Я никак не мог понять, как можно определить, соответствует ли строка заданному регулярному выражению. А теперь я это понял! Ниже представлена реализация простого движка регулярных выражений менее чем в 200 строках кода.

Спецификация

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

  • — соответствие любому символу
  • — соответствие или
  • — соответствие одному или более предыдущего паттерна
  • — соответствие 0 или более предыдущего паттерна
  • и — для группировки

Хотя набор опций невелик, с его помощью можно создать интересные regex-ы, например, позволяющий найти субтитры к Star Wars без субтитров к Star Trek, или для нахождения множества всех строк чётной длины.

План атаки

Мы будем анализировать регулярные выражения в три этапа:

  1. Парсинг (синтаксический анализ) регулярного выражения в синтаксическое дерево
  2. Преобразование синтаксического дерева в конечный автомат
  3. Анализ конечного автомата для нашей строки

Для анализа регулярных выражений (подробнее об этом ниже) мы будем использовать конечный автомат под названием NFA. На высоком уровне NFA будет представлять наш regex. При получении входных данных мы будем перемещаться в NFA от состояния к состоянию. Если мы придём в точку, из которой невозможно совершить допустимого перехода, то регулярное выражение не соответствует строке.

Рекурсивные шаблоны

Рассмотрим задачу поиска соответствия со строкой, находящихся внутри неограниченного количества круглых скобок. Без использования рекурсии лучшее, что можно сделать — написать шаблон, который будет решать задачу для некоторой ограниченной глубины вложенности, так как обработать неограниченную глубину не предоставляется возможным. В Perl 5.6 предоставлены некоторые экспериментальные возможности, которые в том числе позвояляют реализовать рекурсию в шаблонах. Специально обозначение (?R) используется для указания рекурсивной подмаски. Таким образом, приведем PCRE шаблон, решающий поставленную задачу (подразумевается, что используется модификатор PCRE_EXTENDED, незначащие пробелы игнорируются):

\( ( (?>+) | (?R) )* \)

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

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

(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(),

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

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

(ab(cd)ef),

захваченным значением будет ‘ef’, которое является последним значением, принимаемым на верхнем уровне. В случае, если добавить дополнительные скобки

\( ( ( (?>+) | (?R) )* ) \), захваченным значением будет «ab(cd)ef». В случае, если в шаблоне встречается более, чем 15 захватывающих скобок, PCRE требуется больше памяти для обработки рекурсии, чем обычно. Память выделяется при помощи функции pcre_malloc, и освобождается соответственно функцией pcre_free. Если память не может быть выделена, сохраняются данные только для первых 15 захватывающих скобок, так как нет способа выдать ошибку out-of-memory изнутри рекурсии.

(?1), (?2) и так далее могут быть использованы для рекурсивных подмасок. Также возможно использовать именованные подмаски: (?P>name) или (?&name).

Если синтаксис ссылки на рекурсивную подмаску (как по имени, так и по числовому индексу) используется вне скобок, к которым он относится, он отрабатывает аналогично подпрограмме в языке программирования. Возьмем более ранний пример, указывающий что шаблон (sens|respons)e and \1ibility соответствует «sense and sensibility» и «response and responsibility», но не «sense and responsibility». Если вместо этого использовать шаблон (sens|respons)e and (?1)ibility, он совпадет с «sense and responsibility» так же, как и с другими двумя строками. Однако, такие ссылки должны быть указаны после подмаски, на которую они ссылаются.

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

Программирование на языке PHP и регулярные выражения

В PHP есть функции для поиска совпадений в тексте, замены каждого совпадения на другой текст (похоже на операцию «найти и заменить») и поиска совпадений среди элементов списка. Вот эти функции:

Чтобы показать, как работают эти функции, давайте создадим небольшое PHP-приложение, которое будет просматривать список слов на соответствие определенному шаблону. Слова и регулярные выражения будут вводиться из обычной web-формы, а результаты отображаться в браузере посредством функции . Эта программка пригодится, если возникнет желание проверить или отладить регулярное выражение.

PHP-код показан в листинге 2. Все входные данные берутся из обычной HTML-формы. (Для краткости эту форму и PHP-код, отслеживающий ошибки, опустим.)

Листинг 2. Сравнение текста с шаблоном
<?php
	//
	// деление списка, разделенного запятыми на отдельные слова
	//   третий параметр, -1, разрешает неограниченное число совпадений
	//   четвертый параметр, PREG_SPLIT_NO_EMPTY, игнорирует пустые совпадения
	//
	$words = preg_split( '/,/',  $_REQUEST, -1, PREG_SPLIT_NO_EMPTY );

	//
	// удаление начальных и конечных пробелов каждого элемента
	//
	foreach ( $words as $key => $value ) { 
		$words = trim( $value ); 
	}

	//
	// нахождение слов, соответствующих условиям регулярного выражения
	//
	$matches = preg_grep( "/${_REQUEST}/", $words );

	print_r( $_REQUEST ); 
	echo( '<br /><br />' );
	
	print_r( $words ); 
	echo( '<br /><br />' );
	
	print_r( $matches );
	
	exit;
?>

Вначале с помощью функции строка из слов, разделенных запятыми, преобразуется в отдельные элементы. Данная функция разбивает строку в тех местах, которые соответствуют условиям регулярного выражения. В данном случае регулярное выражение представляет собой просто «,» (запятая — разделитель списка слов, указанных через запятую). Слэш в начале и в конце просто показывает начало и конец regex.

Третий и четвертый аргументы функции необязательны, но полезны. Добавьте в третий аргумент число n целого типа, если необходимо вернуть только первые n совпадений, или , если необходимо вернуть все совпадения. Если в качестве четвертого аргумента задать идентификатор , то функция не будет возвращать пустые результаты.


Затем каждый элемент списка слов, разделенных запятыми, корректируется (убираются начальные и конечные пробелы) с помощью функции и сравнивается с заданным регулярным выражением. Функция существенно упрощает процесс обработки списка: просто укажите в качестве первого аргумента шаблон, а в качестве второго — массив слов для сравнения. Функция возвращает массив совпадений.

Например, если в качестве шаблона задать регулярное выражение и список слов разной длины, то можно получить результат, показанный в листинге 3.

Листинг 3. Результат работы простого регулярного выражения
^{2,9}$

Array (  => martin  => 1happy  => hermanmunster ) 

Array (  => martin )

Кстати, с помощью дополнительного маркера можно инвертировать операцию и найти элементы, которые не совпадают с шаблоном (аналогично оператору в командной строке). Заменяя 22 строку на и используя входные данные из листинга 3, мы получим .

Как правильно писать регулярные выражения ¶

Прежде, чем садиться и писать регулярно выраженного кракена, подумайте, что именно вы хотите сделать. Регулярное выражение должно начинаться с мысли «Я хочу найти/заменить/удалить то-то и то-то». Затем вам нужен исходный текст, который содержит как ПРАВИЛЬНЫЕ, так и НЕправильные данные. Затем вы открываете https://regex101.com/, вставляете текст и начинаете писать регулярное выражение. Этот замечательный инструмент укажет и покажет все ошибки, а также подсветит результаты поиска.

Для примера возьмём валидацию ip-адреса. Первая мысль должна быть: «Я хочу валидировать ip-адрес. А что такое ip-адрес? Из чего он состоит?». Затем нужен список валидных и невалидных адресов:

Валидный адрес должен содержать четыре числа (байта) от 0 до 255. Если он содержит число больше 255, это уже ошибка. Если бы мы делали валидацию на каком-либо языке программирования, то можно было бы разбить выражение на четыре части и проверить каждое число отдельно. Но регулярные выражения не поддерживают проверки больше или меньше, поэтому придётся делать по-другому.

Для начала упростим задачу: будем валидировать не весь ip-адрес, а только один байт. А байт это всегда есть либо одно-, либо дву-, либо трёхзначное число. Для одно- и двузначного числа шаблон очень простой — любая цифра. А вот для трёхзначного числа первая цифра либо единица, либо двойка. Если первая цифра единица, то вторая и третья могут быть от нуля до девяти. Если же первая цифра двойка, то вторая может быть только от нуля до пяти. Если первая цифра двойка и вторая пятёрка, то третья может быть только от ноля до пяти. Давайте формализуем:

Теперь, зная все диапазоны байта, можно объединить их в одно выражение через вертикальную палочку | (ИЛИ):

Обратите внимание, что я использовал границу слова \b, чтобы искать полные байты. Пробуем регулярку в деле:

Как видим, все байты стали зелёненькими. Это значит, что мы на верном пути.

Осталось дело за малым: сделать так, чтобы искать четыре байта, а не один. Нужно учесть, что байты разделены тремя точками. То есть мы ищем три байта с точкой на конце и один без точки:

Результат выглядит так:

Подсветились только валидные ip-адреса, значит регулярное выражение работает корректно.

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

Разбор строк

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

Но что произойдет, если строку нужно разобрать на составные части, используя одно или несколько сложных правил? Например, в США номера телефонов обычно выглядят следующим образом: «(305) 555-1212,» «305-555-1212,» или «305.555.1212.» Если убрать пунктуацию, то количество символов сократится до 10 цифр, что легко можно определить с помощью регулярного выражения . Однако код и префикс (каждый из которых состоит из трех цифр) телефонного номера США не могут начинаться с нуля или единицы (так как нуль и единица используются как префиксы для междугородных звонков). Вместо того чтобы разбивать числовую последовательность на отдельные цифры и создавать сложный код, для верификации можно использовать регулярное выражение.

Фрагмент кода позволяющий решить эту задачу, показан в листинге 4.

Листинг 4. Проверка американского телефонного номера
<?php   
	$punctuation = preg_quote( "().-" );
	$number = preg_replace( "//", '', $_REQUEST );

	$valid = "/{2}{2}{4}/";	
	if ( preg_match( $valid, $number ) == 1 ) {
		echo(  "${_REQUEST} is valid<br />" );
	}
		
	exit;
?>

Давайте пройдем по этому коду:

  • Как показано в , в регулярных выражениях используется ограниченный набор специальных символов, например, квадратные скобки () для наименования последовательности. Если надо найти такой символ в тексте, необходимо «выделить» специальный символ в регулярном выражении, поставив перед ним обратный слэш (). Когда символ выделен, можно задать его посик, как и любого другого символа. Если нужно найти символ точки, например, в полном составном имени хоста, то напишите . При желании строку можно подать в функцию которая выполняет автоматическую изоляцию всех специальных символов регулярных выражений, как показано в строке 1. Если поставить после первой строки, то вы должны увидеть .
  • В строке 2 из телефонного номера убираются все знаки пунктуации. Функция заменяет все символы из — операторы из набора — пустой строкой, эффективно устраняя такие символы. Возвращаемая новая строка присваивается переменной .
  • В строке 4 определен шаблон верифицируемого телефонного номера США.
  • Строка 5 реализует сопоставление, сравнивая телефонный номер, который теперь состоит только из цифр, с шаблоном. Функция возвращает 1, если есть совпадение. Если совпадения нет, функция возвращает нулевое значение. Если во время обработки возникла ошибка, то функция возвращает значение False (ложно). Таким образом, чтобы проверить удачное завершение, необходимо посмотреть, было ли возвращено значение 1. В противном случае проверьте итоговое значение функции (если используется PHP версии 5.2.0 или выше). Если оно не равно нулю, то, возможно, был превышен лимит вычислений, например, разрешенная глубина рекурсии регулярного выражения. Обсуждение констант и ограничений, применяемых в регулярных выражениях PHP, представлено на странице, посвященной функциям регулярных выражений PCRE (см. раздел ).

С этим читают