Реализация поиска на 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, больше ресурсов; сложность настройки и синхронизации данных; необходимость обучения.

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

Подробные примеры и результаты работы

Полнотекстовый поиск в 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.

Страница поиска в PHP - comments

En
Index php page search (php)