Реализация поиска на PHP: от простого LIKE до Elasticsearch
Реализация поиска на PHP: подходы и примеры
Организация поискового функционала на PHP требует выбора подходящего механизма в зависимости от объёма данных, требований к скорости и гибкости. Рассматриваются несколько вариантов с примерами кода и типичными сложностями.
Основное эффективное решение: полнотекстовый поиск MySQL с пагинацией
Для большинства сайтов оптимальным является использование полнотекстового индекса MySQL (FULLTEXT). Это решение обеспечивает высокую производительность, поддержку русского языка (при правильной настройке) и ранжирование результатов.
-- Создание таблицы с полнотекстовым индексом
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
content TEXT,
FULLTEXT (title, content)
) ENGINE=InnoDB;Search tags php tag (поиск по тегам в php)
// Страница поиска index.php
$search = $_GET['q'] ?? '';
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 10;
$offset = ($page - 1) * $perPage;
// Подготовленный запрос для безопасности
$stmt = $pdo->prepare('SELECT id, title,
MATCH(title, content) AGAINST(:query IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE)
ORDER BY relevance DESC
LIMIT :limit OFFSET :offset');
$stmt->bindValue(':query', $search, PDO::PARAM_STR);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll();
// Подсчёт общего количества для пагинации
$countStmt = $pdo->prepare('SELECT COUNT(*) FROM articles
WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE)');
$countStmt->execute([':query' => $search]);
$total = $countStmt->fetchColumn();
$totalPages = ceil($total / $perPage);Search topic php (поиск по теме в php)
Пояснение: IN BOOLEAN MODE позволяет использовать операторы + и - для обязательных и запрещённых слов. Пагинация реализована через LIMIT и OFFSET. Подсчёт общего числа результатов выполняется отдельным запросом для корректного отображения страниц.
Типичные проблемы и их решения:
- SQL-инъекция: всегда используются подготовленные выражения (PDO::prepare).
- Пустой запрос: если строка поиска пуста, не выполнять запрос, выводить сообщение.
- Низкая производительность на больших таблицах: необходимо проверить, что FULLTEXT индекс действительно создан и совпадает с полями запроса. На InnoDB минимальная длина слова по умолчанию 3 символа (можно изменить через параметр innodb_ft_min_token_size).
- Стоп-слова: MySQL по умолчанию исключает распространённые слова (предлоги, союзы). При необходимости стоп-слова отключаются настройкой ft_stopword_file.
Вариант 1: Как сделать поиск по частичному совпадению без дополнительных индексов?
Используется оператор LIKE с подстановочными символами. Подходит для небольших таблиц (до нескольких тысяч записей).
$stmt = $pdo->prepare('SELECT * FROM articles
WHERE title LIKE :pattern OR content LIKE :pattern');
$pattern = '%' . $search . '%';
$stmt->execute([':pattern' => $pattern]);
$results = $stmt->fetchAll();Search type php id type (тип поиска по id в php)
Цели: простота реализации, не требует настройки индексов. Случаи использования: поиск по коротким спискам, административные панели.
Проблемы: полное сканирование таблицы, медленный на объёме свыше 10-20 тысяч записей; не работает с русской морфологией (слова ищутся точно по подстроке).
Вариант 2: Когда требуется гибкость регулярных выражений?
MySQL поддерживает REGEXP (REGEXP_LIKE с MySQL 8.0). Позволяет искать сложные паттерны.
$stmt = $pdo->prepare('SELECT * FROM articles
WHERE title REGEXP :pattern OR content REGEXP :pattern');
// Экранирование специальных символов регулярного выражения
$pattern = preg_quote($search, '/');
$stmt->execute([':pattern' => $pattern]);
$results = $stmt->fetchAll();Search php view (вид поиска в php)
Цели: поиск по шаблону, например, номера телефонов или email. Случаи использования: поиск в структурированных данных, где нужно точное совпадение формата.
Проблемы: REGEXP не использует индексы, ещё медленнее LIKE; требуется экранирование пользовательского ввода во избежание ошибок; не рекомендуется для полнотекстового поиска.
Как обеспечить быстрый полнотекстовый поиск на больших объёмах?
Используется внешний движок Elasticsearch. Подключается к PHP через официальную клиентскую библиотеку elasticsearch/elasticsearch.
require 'vendor/autoload.php';
use Elastic\Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()->setHosts(['localhost:9200'])->build();
$params = [
'index' => 'articles',
'body' => [
'query' => [
'multi_match' => [
'query' => $search,
'fields' => ['title', 'content']
]
]
]
];
$response = $client->search($params);
$results = $response['hits']['hits'];
Цели: производительность на миллионах записей, поддержка морфологии, фасетов, автодополнения. Случаи использования: интернет-магазины, каталоги, новостные порталы.
Проблемы: требуется отдельный сервер Elasticsearch, больше ресурсов; сложность настройки и синхронизации данных; необходимость обучения.
Подробные примеры и результаты работы
Полнотекстовый поиск в BOOLEAN MODE с операторами
// Поиск статей, содержащих обязательно 'php' и желательно 'search', но не 'oop'
$query = '+php search -oop';
$stmt = $pdo->prepare('SELECT id, title,
MATCH(title,content) AGAINST(:query IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title,content) AGAINST(:query IN BOOLEAN MODE)
ORDER BY relevance DESC');
$stmt->execute([':query' => $query]);
$rows = $stmt->fetchAll();
// Пример результатов
// id=1, title="PHP search example", relevance=2.5
// id=2, title="Search in PHP without OOP", relevance=1.8 (слово -oop не исключило, т.к. оператор действует на кластер)
// id=3, title="OOP in Java", не попадёт (нет php)
Array ( [0] => Array ( [id] => 1 [title] => PHP search example [relevance] => 2.5 ) [1] => Array ( [id] => 2 [title] => Search in PHP without OOP [relevance] => 1.8 ) )
Пояснение: Оператор + означает обязательное присутствие слова, - запрет. Слова без знака влияют на релевантность. В BOOLEAN MODE можно использовать также * для усечения, "фраза" для точной фразы.
Подсветка найденных совпадений
function highlightText($text, $search) {
$words = explode(' ', $search);
foreach ($words as $word) {
$word = preg_quote($word, '/');
$text = preg_replace("/".$word."/iu", '$0', $text);
}
return $text;
}
if (!empty($results)) :
foreach ($results as $row) :
echo '' . highlightText($row['title'], $search) . '
';
echo '' . highlightText(substr($row['content'], 0, 200), $search) . '...
';
endforeach;
endif;
Вывод HTML с обёрнутыми словами в <span class="highlight"></span> (визуально выделяются цветом при наличии CSS).
Пояснение: Функция highlightText экранирует специальные символы регулярного выражения, затем заменяет вхождения на теги с классом. Используется модификатор /iu для корректной работы с UTF-8 и регистронезависимости.
Интеграция Elasticsearch с автодополнением
// Индексация документа
$params = [
'index' => 'articles',
'id' => $articleId,
'body' => [
'title' => $title,
'content' => $content,
'suggest' => [
'input' => [$title, ...explode(' ', $title)],
'weight' => 10
]
]
];
$response = $client->index($params);
// Автодополнение по мере ввода
$suggestParams = [
'index' => 'articles',
'body' => [
'suggest' => [
'title-suggest' => [
'prefix' => substr($search, 0, 10),
'completion' => [
'field' => 'suggest'
]
]
]
]
];
$response = $client->search($suggestParams);
$suggestions = array_map(function($hit) {
return $hit['_source']['title'];
}, $response['suggest']['title-suggest'][0]['options']);
print_r($suggestions);
Array ( [0] => PHP search tutorial [1] => PHP framework selection )
Пояснение: Поле suggest типа completion позволяет реализовать автодополнение. При поиске по префиксу возвращаются подходящие названия. На стороне клиента можно вызывать этот эндпоинт через AJAX.