Главная PHP | Документация учебники | PHP справочники | Borland C++ Builder |

Безопасный и удобный поиск.

Главное, с чем сталкиваешься при написании скрипта для поиска - то, что все кажется простым, но объем кода быстро нарастает.

Обработка строки


Первым делом надо порезать ручками строку.

$ search = substr($ search, 0, 64);

64 символов пользователю будет достаточно для поиска. Теперь каленым железом выжжем все "ненормальные" символы.

$ search = preg_replace("/[^(w)|(x7F-xFF)|(s)]/", " ", $ search);

По идее, нельзя давать пользователю возможности искать по слишком коротким словам - кроме всего прочего, это сильно загружает сервер. Итак, разрешим искать только по словам, которые длиннее двух букв (если ограничение больше, надо заменить "{1,2}" на "{1, кол-во символов}").

$ good = trim(preg_replace("/s([^s]{1,2})s/", " ", ereg_replace("[ ]+", "  "," $ search ")));

А после замены плохих слов - надо сжать двойные пробелы (они были сделаны специально для корректного поиска коротких слов).

$ good = ereg_eplace("[ ]+", " ", $ good);

Логика


Допустим, мы хотим предоставить пользователю возможность выбирать логику поиска - искать все слова или только одно из нескольких. Если вы хотите сделать как в Яндексе [2] [1] - два амперсанта означают "И" (слово1&&слово2&&слово3) или как-то еще, то я не советчик. Шаманство со строками на небольшом сайте imho не оправдывает затраченного времени. Поэтому форму для поиска рисуем так:


А в поисковом скрипте лишний раз проверяем, что пользователь ввел:

if ($ logic!="AND" && $ logic!="OR")
  $ logic = "OR";

Как будет использоваться логика — ниже.

Релевантность


Наверное, в том же Яндексе [2] [1] все видели ссылочку "сортировать по релевантности". Это оно и есть. Сортировка результатов по количеству совпадений слов.

Отчасти, кстати, такая сортировка снимает проблему обработки логики поиска. Но с БД MySQL делать такую сортировку очень сложно. Надо сперва выбрать, где есть все слова, потом записи, где разные слова (исключив предыдущие). Если у вас постраничный вывод - то вообще дело труба!

Статистика поиска


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

$ query = "SELECT id FROM table WHERE field LIKE '%". str_replace(" ", "%' OR field LIKE '%", $ good). "%'";

Для статистики по отдельным словам можно сделать следующее:

$ word = explode(" ", $search);
while (list($ k, $ v) = each($ word)) {
  if (strlen($ v)>2)
    $ stat[]="$ v:". mysql_num_rows(mysql_query("SELECT id FROM table WHERE field LIKE '%$ v%'"));
  else
    $ stat[]="$ v: <font color=#cc0000>короткое</font>";
  };
$ word_stats = "Статистика слов: ". implode("", $ stat). "<br>";
unset($ stat);

Постраничный вывод результатов


Ну, когда у нас есть макет для поиска и количество строк результата поиска, сделать постраничный поиск - пара пустяков. Проверяем переменную $ page (не меньше 0, не больше $ results_amount/$ rows_in_page).

В запрос, который подсчитывает количество строк (смотри выше), пишем нужные нам поля и поля для сортировки. А потом дописываем

if ($ page==0)
  $ request .= "LIMIT $ rows_in_page";
else
  $ request .= "LIMIT ". $ page*$ rows_in_page. ",". $ rows_in_page;

(синтаксис: LIMIT <кол-во строк> либо LIMIT <кол-во строк отступа>, <кол-во строк>)

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

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

if ($ page>0)
  print ("<a href=search.php?search=". rawurlencode($ good). "&page=". ($ page-1). ">предыдущая страница</a>");

if ($ page<$ results_amount/$ rows_in_page)
  print ("<a href=search.php?search=". rawurlencode($ good). "&page=". ($ page+1). ">следующая страница</a>");

Подсветка


Чтобы подсвечивать светом или жирным шрифтом искомые слова в тексте, надо сделать всего лишь следующее:


$ highlight = "(". str_replace(" ", "|", $ good). ")";

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

В коде, который выводит текст пишем:


$ row["text"] = ereg_replace($ highlight, "<font color=#cc0000>1</font>", $ row["text"]);

После написания выпуска я кинулся, было, писать и себе "подсветку". Не тут-то было! У меня в тексте встречаются теги HTML, поэтому пришлось много подумать... Получилась вот такая вещь (строка со словами для подсветки есть):

$ text = eregi_replace(">([^<]*)$ words", ">1<font color=#cc0000>2</font>3<", $ text);

Приходится смотреть, нет в теге ли это слово. Однако тут встает проблема ресурсоемкости такой замены (мой K6-266 над текстом в 5 килобайт думал целых семь секунд). Печально.

Итог


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



Документация, учебники по PHP


Наши партнёры и спонсоры:

Главная PHP | Документация учебники | PHP справочники | Perl | Borland C++ Builder | JavaScript |
php@fud.ru