Создание поискового функционала на 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

Поиск на PHP - comments

En
Search php page (php)