Номер телефона

Последнее обновление:

Проблема функций поиска подстрок в языке PHP

Оговоримся сразу: проблема наблюдалась в РНР 5 и только на хостинге. На виртуальном сервере (Denwer, РНР 5) такой проблемы не было вообще.

В РНР существует несколько удобных функций, предназначенных для поиска подстрок в строках, такие, как strpos, substr_count, а также функции, делающие подобный поиск с использованием регулярных выражений: preg_match, preg_match_all.

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

Всем (ну, почти всем) хороши эти функции, но у них есть одна общая проблема: при определенной длине строк они могут работать… перестать(!). Причем, делают это совершенно тихо, диагностических сообщений не выдают. Просто возвращают значение false, т.е. будто бы это не ошибка, а результат поиска отрицателен, т.е., якобы, искомой подстроки нет в строке для поиска. И, что самое печальное: время от времени они все-таки могут работать корректно. Ну, об этом чуть ниже. А пока посмотрим, в чем состоит эта проблема.

Итак, проблема: функции strpos, substr_count, preg_match, preg_match_all и кое-какие другие НЕ работают, если длины обрабатываемых ими строк превышают некоторое критическое значение

Точнее, ИНОГДА работают, а иногда – НЕ работают.

Рассмотрим простой код на PHP:

  • $str = 'qwertyQwErTy QWERTY';
  • $str_to_find = 'ert';
  • echo strpos($str, $str_to_find);

Очевидно, в результате, при запуске этого кода, в браузере появится число 2. Ибо начало расположения букв ert – это позиция под номером 2 в строке ‘qwertyQwErTy QWERTY qwerty' (если считать с 0).

Код

  • $str = 'qwertyQwErTy QWERTY qwerty';
  • $str_to_find = 'ert';
  • echo substr_count($str, $str_to_find);

Тоже в результате будете выведено 2, так как будут найдены именно 2 вхождения.

Но, что будет, если увеличивать длины указанных строк?

В так называемых «мануалах» (т.е. на php.net) ничего не сказано об этом вообще. Поэтому создается иллюзия, что длины строк, теоретически могут быть любыми. Однако, еще раз, это – ИЛЛЮЗИЯ. К большому сожалению.

Что будет, если длина первого аргумента ($str) будет превышать некоторое критическое значение?

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

Этой ошибки можно избежать, если изменить соответствующую настройку, например:

ini_set('pcre.backtrack_limit', '1000000');

Эта настойка может быть сделана в файле php.ini или же можно сделать ее временно, на момент выполнения скрипта РНР путем соответствующей команды. При этом максимальный размер строки $str может составлять до 1000000 байт.

Т.е. это еще поправимо. Но, вот как быть с ситуацией, когда длина строки $str_to_find (т.е. той строки, которая ищется) тоже будет выше, чем некоторый предел?

Что будет, если длина искомой строки превысит некоторый предел?

Увы, тоже будет ошибка, причем – тоже сложно диагностируемая. Точнее, ее даже еще сложнее диагностировать. Например, на данном сайте максимальная длина строки $str_to_find (которая еще не приводит к ошибке) составляет… всего лишь несколько килобайт.

Вот типичный пример. Длина исходной строки, в которой происходил поиск – всего 140 кБ. Граничным значением длины строки, которая отыскивалась в исходной строке, являлось число 6910 байт.

Прямо почти что число Зверя.

Так вот, если длина искомой строки $str_to_find составляла 6909 байт, то все хорошо работало, все указанные выше функции возвращали корректные значения. А вот, начиная с длины строки, равной 6910 байт, почему-то возникают ошибки. Т.е. функции уже не работают, как положено. И выдают false.

Да, они выдают не ошибку, а именно false.

Причем, самое печальное: ИНОГДА эти функции все-таки дают правильный результат даже и при более высокой длине $str_to_find! А иногда – выдают ошибку. От чего это зависит, что к чему – выяснить так и не удалось.

Были выбраны, например, следующие настройки:

Directive Local Value Master Value
pcre.backtrack_limit 1000000 1000000
pcre.recursion_limit 100000 100000

Кстати, подробный перечень настроек РНР на сервере можно узнать, запустив команду phpinfo();

Т.е., казалось бы, значения параметров достаточно велики для столь небольших длин строк (140 кБ и 7 кБ). Несмотря на это, периодически возникали указанные ошибки.

К чему такие ошибки могут привести, наверное, рассказывать не нужно, и так понятно. Главная проблема тут еще и в том, что строки, использующиеся в указанных выше функциях, могут формироваться динамически и их точный размер (длина) может быть неизвестен. И это означает, что вдруг, казалось бы, ни с того, ни с сего будут возникать очень странные и, главное, НЕПОСТОЯННЫЕ ошибки. Отловить которые будет достаточно сложно. Особенно, если программисту предстоит отладить чужой код и когда этого кода очень много, когда там еще абстракция на абстракции, вызывается и подключается множество разных классов и т.д.

Причем, ошибки возникают во ВСЕХ указанных функциях. Вполне возможно, что под угрозой и другие функции.

И самое плохое здесь то, что указанные функции strpos, substr_count, preg_match, preg_match_all могут ИНОГДА (но, не всегда – даже при одних и тех же данных!) возвращать результат false вне зависимости от того, присутствует ли искомая подстрока в строке поиска или нет. Т.е. следует констатировать НЕнадежность работы указанных функций языка РНР в случае, когда длины строк – аргументов этих функций – превышают некоторые критические значения. Т.е. мало того, что функции работают ненадежно, так они еще и не сообщают об ошибке, а просто возвращают значение false. Создавая впечатление, что будто бы искомая строка «не найдена».

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

Что делать?

Т.е. как бороться с этим? На наш взгляд, следует:

  1. В обязательном порядке предварительно тестировать ту функцию, которую Вы собираетесь применять с учетом максимально возможных длин строк,
  2. Предварительно контролировать размер строки перед тем, как передавать ее в соответствующую функцию,
  3. Если требуется найти длинную искомую строку – придется ее делить на части и искать по частям по отдельности.

Пункт 2 напоминает уже работу в языке С. Это там, прежде, чем задавать (динамическую) строку, необходимо выделить ей соответствующую память – например, при помощи функции malloc(). Так вот, похоже, язык PHP является настолько С-подобным, что даже в таких мелочах требует аналогичного контроля, как и в языке С.

Шутка, конечно.

Дело в том, что, как уже отмечалось, на хостинге и на виртуальном сервере в одной и той же версии языка РНР (PHP 3) наблюдались разночтения: на хостинге возникали ошибки, если длина строки превышала определенный предел, а вот на виртуальном сервере (Denwer) такой проблемы не наблюдалось вообще.

А ведь речь-то идет (в случае с $str_to_find) о строке размером всего-то навсего 7 килобайт! Уж, казалось бы, что может быть меньше. Однако, увы.

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

Так что аккуратнее – те, кто будет использовать язык РНР. Удачной Вам работы со строками!


Комментарии:
Всего комментариев:0
Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация
НАПИШИТЕ НАМ
Яндекс.Метрика
Номер телефона
© Copyright Все права защищены 2013-