PHP поиск: методы и примеры кода для обработки запросов act search

Раздел: Веб-разработка -> Реализация поиска

Реализация поиска в PHP

Как организовать безопасный и производительный поиск по базе данных с учётом кириллицы?

Наиболее эффективное решение основано на использовании полнотекстового индекса MySQL (FULLTEXT) в связке с подготовленными запросами (prepared statements). Такой подход обеспечивает хорошую скорость поиска даже на больших объёмах данных, корректно обрабатывает кириллицу и исключает риск SQL-инъекций.

Шаги реализации:

  1. Создать таблицу с полнотекстовым индексом на нужных полях (например, title, content). Для кириллицы указать правильную collation, например utf8mb4_0900_ai_ci.
  2. В файле index.php обработать GET-параметр act=search, проверить его наличие и значение, отфильтровать пустую строку.
  3. Использовать PDO или mysqli с подготовленным запросом: SELECT * FROM articles WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE).
  4. Учесть минимальную длину слова (задаётся через ft_min_word_len) и список стоп-слов (stopwords). Для кириллицы может потребоваться настройка через innodb_ft_min_token_size.

// index.php?act=search&q=фреймворк PHP
$query = trim($_GET['q'] ?? '');
if (mb_strlen($query) < 2) {
    // возвращать пустой результат или сообщение
}
$stmt = $pdo->prepare("SELECT id, title, content, 
    MATCH(title, content) AGAINST(:q IN BOOLEAN MODE) AS relevance 
    FROM articles 
    WHERE MATCH(title, content) AGAINST(:q IN BOOLEAN MODE) 
    ORDER BY relevance DESC");
$stmt->execute([':q' => '+' . implode(' +*', explode(' ', $query))]);
$results = $stmt->fetchAll();
  

Search tags php tag (поиск по тегам в php)

Типичные ошибки и их решения:

  • Пустые результаты при корректном запросе - вероятно, слова короче ft_min_word_len (по умолчанию 4). Для кириллицы уменьшить до 2 через innodb_ft_min_token_size=2 в my.cnf.
  • Игнорирование стоп-слов - список можно отключить параметром innodb_ft_enable_stopword=OFF или очистить таблицу stopwords.
  • SQL-инъекция при формировании MATCH без prepared statement - всегда использовать параметризованные запросы.
  • Неправильная collation - использовать utf8mb4_0900_ai_ci для корректного сравнения символов.

Цель варианта: организация поиска по веб-приложению с минимальной задержкой и встроенной поддержкой ранжирования релевантности. Подходит для сайтов с объёмом контента от тысяч до нескольких миллионов записей.

Как реализовать простой поиск с использованием оператора LIKE без полнотекстового индекса?

Когда нет доступа к созданию индексов или объём данных невелик (до 10-20 тысяч записей), допустим вариант с LIKE. Для кириллицы важно правильно обрабатывать регистр через COLLATE utf8mb4_general_ci.


$q = '%' . $query . '%';
$stmt = $pdo->prepare("SELECT * FROM articles WHERE title COLLATE utf8mb4_general_ci LIKE :q OR content COLLATE utf8mb4_general_ci LIKE :q");
$stmt->execute([':q' => $q]);
$results = $stmt->fetchAll();
  

Search topic php (поиск по теме в php)

Проблема: очень медленный поиск на больших таблицах (полный перебор). Решение - добавить ограничение по количеству записей (LIMIT 50) и комбинировать с ORDER BY. Также возможна фильтрация через REGEXP, но она ещё медленнее.

Цель: быстрая разработка без администрирования БД, для небольших проектов или прототипов.

Как искать похожие слова с учётом опечаток через Soundex или Levenshtein?

Для исправления опечаток в русскоязычных текстах можно применить расширение soundex_rus или встроенную функцию LEVENSHTEIN (доступна как пользовательская функция MySQL). Однако это ресурсоёмко и подходит только для небольших наборов (например, поиск по названиям товаров).


// пример с levenshtein в PHP (не в SQL)
function searchByLevenshtein($query, $titles) {
    $results = [];
    foreach ($titles as $title) {
        $dist = levenshtein(mb_strtolower($query), mb_strtolower($title));
        if ($dist <= 2) {
            $results[] = ['title' => $title, 'distance' => $dist];
        }
    }
    usort($results, fn($a, $b) => $a['distance'] <=> $b['distance']);
    return $results;
}
  

Search type php id type (тип поиска по id в php)

Ошибка: не учитываются падежи и окончания. Решение - предварительно привести слова к нормальной форме (стемминг, например, phpMorphy).

Цель: исправление опечаток в узкой предметной области (например, каталог лекарств или фамилий).

Как подключить внешний поисковый движок (Elasticsearch / Sphinx) к index.php?

Для больших объёмов (миллионы записей) и сложных требований (морфология, автодополнение, фасеты) применяют специализированные решения. В index.php отправляется HTTP-запрос к поисковому серверу, а результат формируется из ответа.


// пример с Elasticsearch PHP client
$client = Elasticsearch\ClientBuilder::create()->build();
$params = [
    'index' => 'articles',
    'body'  => [
        'query' => [
            'multi_match' => [
                'query'  => $query,
                'fields' => ['title^3', 'content']
            ]
        ]
    ]
];
$response = $client->search($params);
$results = array_map(fn($hit) => $hit['_source'], $response['hits']['hits']);
  

Search php view (вид поиска в php)

Проблема: зависимость от внешнего сервиса, дополнительная инфраструктура. Решение - использовать Docker для развёртывания Elasticsearch в dev-среде, а в production обеспечить отказоустойчивость.

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

Как добавить пагинацию и постраничный вывод результатов поиска?

После выполнения запроса результаты разбиваются на страницы. Параметры page и per_page передаются через GET. Важно сохранить original query для подстановки в ссылки.


$perPage = 10;
$page = max(1, (int)($_GET['page'] ?? 1));
$offset = ($page - 1) * $perPage;
$stmt = $pdo->prepare("SELECT SQL_CALC_FOUND_ROWS * FROM articles 
    WHERE MATCH(title, content) AGAINST(:q IN BOOLEAN MODE) 
    LIMIT :limit OFFSET :offset");
$stmt->bindValue(':q', $query, PDO::PARAM_STR);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll();
$total = $pdo->query("SELECT FOUND_ROWS()")->fetchColumn();
$pages = ceil($total / $perPage);
  

Ошибка: потеря параметра q при переходе по страницам. Решение - передавать все параметры в ссылках через http_build_query.

Цель: удобная навигация по большому числу результатов.

- Search php cid (поиск по cid в php)
- Search php keyword (поиск по ключевому слову в php)
- Search php search name (поиск по имени в php)

Расширенные примеры реализации поиска в PHP

Многоуровневое ранжирование с FULLTEXT BOOLEAN MODE

Пример демонстрирует использование булевого режима для точной настройки веса слов: слова с префиксом '+' обязательны, с '-' исключаются, '*' - подстановка. Результат сортируется по релевантности.

Пример

// index.php?act=search&q=+PHP -JavaScript *разработка
$rawQuery = $_GET['q'] ?? '';
$terms = preg_split('/\s+/', $rawQuery);
$booleanQuery = '';
foreach ($terms as $term) {
    if ($term[0] === '+') {
        $booleanQuery .= '+' . substr($term, 1) . ' ';
    } elseif ($term[0] === '-') {
        $booleanQuery .= '-' . substr($term, 1) . ' ';
    } else {
        $booleanQuery .= $term . '* ';
    }
}
$stmt = $pdo->prepare("SELECT 
    title, 
    MATCH(title, content) AGAINST(:q IN BOOLEAN MODE) AS relevance 
    FROM articles 
    WHERE MATCH(title, content) AGAINST(:q IN BOOLEAN MODE) 
    HAVING relevance > 0 
    ORDER BY relevance DESC 
    LIMIT 20");
$stmt->execute([':q' => $booleanQuery]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
Результат (массив):
[
    ['title' => 'PHP для начинающих', 'relevance' => 8.53],
    ['title' => 'Сравнение PHP и Node.js', 'relevance' => 5.21],
    ...
]

Поиск с автодополнением (suggest) через Elasticsearch Completion Suggester

Позволяет выводить подсказки по мере ввода запроса. В PHP отправляется запрос к Elasticsearch с типом suggest.

Пример

$client = Elasticsearch\ClientBuilder::create()->build();
$params = [
    'index' => 'articles',
    'body'  => [
        'suggest' => [
            'title-suggest' => [
                'prefix' => $query,
                'completion' => [
                    'field' => 'title_suggest',
                    'fuzzy' => ['fuzziness' => 'auto']
                ]
            ]
        ]
    ]
];
$response = $client->search($params);
$suggestions = array_map(
    fn($opt) => $opt['text'],
    $response['suggest']['title-suggest'][0]['options']??[]
);
Результат:
[
    "PHP фреймворки",
    "PHPstorm настройка",
    "PHPUnit тестирование"
]

Поиск только среди заголовков с учётом стемминга (phpMorphy)

Для русского языка стемминг повышает точность. Библиотека phpMorphy приводит слова к нормальной форме. Затем поиск выполняется по этим формам.

Пример

require_once '/path/to/phpMorphy/src/common.php';
$morphy = new phpMorphy\Morphy('ru');
$words = explode(' ', $query);
$normalized = [];
foreach ($words as $word) {
    $base = $morphy->getBaseForm(mb_strtolower($word));
    $normalized[] = $base ?: $word;
}
$queryNormal = implode(' ', $normalized);
// далее полнотекстовый поиск по нормализованной строке

Замечание: стемминг может быть избыточным для коротких запросов. Лучше применять только при длине более 3 символов.

Поиск с подсветкой найденных фрагментов (snippet)

Выделение совпадений в результатах улучшает UX. Используется функция PHP str_ireplace или регулярное выражение с границами слов.

Пример

function highlight($text, $query) {
    $words = explode(' ', $query);
    $pattern = '/(' . implode('|', array_map('preg_quote', $words)) . ')/ui';
    return preg_replace($pattern, '$0', htmlspecialchars($text));
}
foreach ($results as &$row) {
    $row['content'] = highlight($row['content'], $query);
}
Исходный текст: "PHP - скриптовый язык программирования"
Результат: "PHP - скриптовый язык программирования"

Поиск по связанным тегам (многие ко многим) с помощью JOIN

Если есть таблицы articles, tags, article_tags, поиск может включать теги.

Пример

$stmt = $pdo->prepare("SELECT DISTINCT a.id, a.title 
    FROM articles a
    JOIN article_tags at ON a.id = at.article_id
    JOIN tags t ON at.tag_id = t.id
    WHERE t.name LIKE :tag_query
    OR a.title LIKE :title_query");
$q = '%' . $query . '%';
$stmt->execute([':tag_query' => $q, ':title_query' => $q]);
$results = $stmt->fetchAll();

Действие поиска в PHP - comments

En
Index php act search (php)