Создание поискового функционала на PHP
Основные подходы к поиску на PHP
Полнотекстовый поиск через MySQL FULLTEXT
Как реализовать быстрый и релевантный поиск на PHP с использованием базы данных MySQL?
Наиболее эффективное решение для большинства проектов - полнотекстовый индекс в MySQL. Он позволяет выполнять поиск по одному или нескольким столбцам с учётом стоп-слов и ранжирования.
Создание полнотекстового индекса
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
body TEXT,
FULLTEXT (title, body)
) ENGINE=InnoDB;Search php page (поиск на php)
После заполнения таблицы данными выполняется запрос MATCH … AGAINST.
PHP-код для поиска
$searchTerm = $_POST['query'] ?? '';
$stmt = $pdo->prepare("SELECT id, title, MATCH(title, body) AGAINST(:query IN NATURAL LANGUAGE MODE) AS relevance FROM articles WHERE MATCH(title, body) AGAINST(:query IN NATURAL LANGUAGE MODE) ORDER BY relevance DESC LIMIT 20");
$stmt->execute(['query' => $searchTerm]);
$results = $stmt->fetchAll();Результаты сортируются по релевантности. Для безопасности используется подготовленное выражение PDO.
Возможные проблемы:
- Минимальная длина слова (по умолчанию 4 символа) задаётся параметром
ft_min_word_len. Короткие запросы могут не находить результатов. - Индекс не поддерживает точные фразы в режиме NATURAL LANGUAGE - для этого применяется BOOLEAN MODE.
Простой поиск с оператором LIKE
Как сделать простейший поиск без полнотекстового индекса?
Подходит для небольших объёмов данных или когда нельзя менять структуру БД. Используется оператор LIKE со знаками подстановки.
$stmt = $pdo->prepare("SELECT id, title, body FROM articles WHERE title LIKE :pattern OR body LIKE :pattern2");
$pattern = '%' . $searchTerm . '%';
$stmt->execute(['pattern' => $pattern, 'pattern2' => $pattern]);Когда это оправдано?
Для прототипов или поиска по небольшому числу записей. Недостаток - полное сканирование таблицы, медленная работа на больших объёмах.
Типичные ошибки:
- Отсутствие экранирования символов
%и_в строке запроса - их следует экранировать черезaddcslashes. - Использование LIKE без подготовленных выражений приводит к SQL-инъекциям.
Внешний поисковый движок Elasticsearch
Как внедрить высокопроизводительный поиск с кластеризацией?
Elasticsearch (ES) - распределённый поисковый сервер с REST API. Для PHP используется официальный клиент elasticsearch/elasticsearch.
// Установка: composer require elasticsearch/elasticsearch
use Elastic\Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()->setHosts(['localhost:9200'])->build();
// Индексация документа
$params = [
'index' => 'articles',
'body' => ['title' => 'Пример', 'body' => 'Текст статьи']
];
$client->index($params);
// Поиск
$searchParams = [
'index' => 'articles',
'body' => [
'query' => [
'multi_match' => [
'query' => $searchTerm,
'fields' => ['title', 'body']
]
]
]
];
$response = $client->search($searchParams);
$hits = $response['hits']['hits'];Когда стоит выбрать Elasticsearch?
Для высоконагруженных проектов, где требуется нечёткий поиск, синонимы, морфология, faceted search.
Проблемы:
- Необходимость поддерживать отдельный сервер ES, синхронизировать данные.
- Сложность настройки анализаторов и маппинга.
Поиск через облачный сервис Algolia
Как добавить мгновенный поиск без администрирования сервера?
Algolia - SaaS-платформа для поиска. PHP API позволяет легко индексировать данные и выполнять запросы.
// Установка: composer require algolia/algoliasearch-client-php
use Algolia\AlgoliaSearch\SearchClient;
$client = SearchClient::create('ApplicationID', 'AdminAPIKey');
$index = $client->initIndex('articles');
// Добавление записи
$index->saveObject(['objectID' => 1, 'title' => 'Заголовок', 'body' => 'Содержимое']);
// Поиск
$results = $index->search($searchTerm);
foreach ($results['hits'] as $hit) { echo $hit['title']; }Algolia автоматически обрабатывает опечатки, ранжирование и мгновенную выдачу.
Недостатки:
- Зависимость от внешнего сервиса и плата за использование.
- Ограничения бесплатного тарифа (10 000 записей, 100 000 операций поиска в месяц).
Расширенные примеры и результаты
1. MySQL FULLTEXT: булев режим и расширение запроса
Булев режим позволяет использовать операторы +, -, *, фраза
.
// Поиск слов, где 'php' обязательно, а 'java' исключено
$query = '+php -java';
$stmt = $pdo->prepare("SELECT id, title, MATCH(title, body) AGAINST(:query IN BOOLEAN MODE) AS relevance FROM articles WHERE MATCH(title, body) AGAINST(:query IN BOOLEAN MODE)");
$stmt->execute(['query' => $query]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($results);Array
(
[0] => Array
(
[id] => 1
[title] => PHP for beginners
[relevance] => 5.2
)
)2. LIKE с экранированием спецсимволов
Чтобы пользователь мог искать символы % и _, их необходимо экранировать.
$searchTerm = '100% organic';
$escaped = str_replace(['%', '_'], ['\%', '\_'], $searchTerm);
$pattern = '%' . $escaped . '%';
$stmt = $pdo->prepare("SELECT id, title FROM articles WHERE title LIKE :pattern");
$stmt->execute(['pattern' => $pattern]);
$results = $stmt->fetchAll();
echo count($results) . ' результатов';2 результатов
3. Elasticsearch: создание индекса с маппингом
Для русского языка требуется анализатор с морфологией.
$params = [
'index' => 'articles_ru',
'body' => [
'settings' => [
'analysis' => [
'analyzer' => [
'russian' => [
'type' => 'standard',
'stopwords' => '_russian_'
]
]
]
],
'mappings' => [
'properties' => [
'title' => ['type' => 'text', 'analyzer' => 'russian'],
'body' => ['type' => 'text', 'analyzer' => 'russian']
]
]
]
];
$client->indices()->create($params);
// Поиск с усечением результатов
$search = $client->search([
'index' => 'articles_ru',
'body' => [
'query' => ['match' => ['title' => 'программирование']],
'size' => 10
]
]);
echo 'Найдено: ' . $search['hits']['total']['value']; // 8Найдено: 8
4. Algolia: группировка и реплики
Algolia поддерживает виртуальные реплики для разных сортировок.
$index->setSettings([
'attributesForFaceting' => ['category'],
'replicas' => [
'articles_rating_desc' => [
'ranking' => ['desc(rating)']
]
]
]);
// Поиск с фильтром по категории
$results = $index->search('php', ['filters' => 'category:backend']);
foreach ($results['hits'] as $hit) {
echo $hit['title'] . ' - ' . $hit['rating'] . "\n";
}PHP 8 нововведения - 4.5 Laravel 11 - 4.9