©
Copyright Алекс aka TrigGer
& x-graphic studio.
© Все права защищены.
Перепечатка
материалов разрешается, только после письменного разрешения автора (e-mail).
При перепечатке любого материала с журнала видимая ссылка на источник www.kovrik.hut.ru
и все имена, ссылки авторов обязательны.
*** WEB Star *** #2 Programming
Безопасный и удобный поиск на PHP.
Автор:
Дмитрий Лебедев
Источник: Клуб PHP-разработчиков
Главное, с чем сталкиваешься при написании скрипта для поиска - то, что все
кажется простым, но объем кода быстро нарастает.
Обработка строки
Первым делом надо порезать ручками строку.
$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 тысяч раз лазил в базу) в) увидеть сообщение об ошибке в результате попадания
в строку спецсимволов языка запросов. Во-вторых, некоторое удобство для пользователя
- постраничный вывод и подсветка.