Поиск в PHP с использованием поддействий (search index subaction)
Реализация поиска с поддействиями в PHP
Как организовать в PHP поиск по индексу с поддержкой различных поддействий (фильтрация, сортировка, пагинация)?
Наиболее эффективное решение базируется на использовании полнотекстового индекса MySQL в сочетании с параметром subaction, который передаётся через URL или POST. Это позволяет отделить логику поиска от других операций (вывод формы, обработка AJAX).
// config.php
$host = 'localhost'; $db = 'search_db'; $user = 'root'; $pass = '';
$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);Search tags php tag (поиск по тегам в php)
// search.php?subaction=search&query=php&page=1&sort=relevance
$allowedSubactions = ['search', 'filter', 'sort', 'page'];
$subaction = $_GET['subaction'] ?? 'search';
if (!in_array($subaction, $allowedSubactions)) {
die('Invalid subaction');
}
$query = trim($_GET['query'] ?? '');
$page = max(1, (int)($_GET['page'] ?? 1));
$sort = $_GET['sort'] ?? 'relevance';
$perPage = 10;
$offset = ($page - 1) * $perPage;
if ($subaction === 'search' || $subaction === 'filter') {
$sql = "SELECT *, MATCH(title, content) AGAINST(:query IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE)";
if ($subaction === 'filter') {
$category = $_GET['category'] ?? '';
if ($category) {
$sql .= " AND category = :category";
}
}
$sql .= " ORDER BY relevance DESC LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':query', $query, PDO::PARAM_STR);
if ($subaction === 'filter' && !empty($category)) {
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
}
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
} elseif ($subaction === 'sort') {
// смена сортировки (например, по дате)
$order = ($sort === 'date') ? 'created_at DESC' : 'relevance DESC';
$sql = "SELECT *, MATCH(title, content) AGAINST(:query IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE)
ORDER BY $order LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':query', $query, PDO::PARAM_STR);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
} elseif ($subaction === 'page') {
// только пагинация, переиспользование предыдущего запроса
// аналогично search, но page уже учтён
}Search topic php (поиск по теме в php)
Проблемы и их решения
- SQL-инъекция: использование подготовленных выражений (bindValue) предотвращает внедрение. Не рекомендуется склеивать строку запроса напрямую.
- Некорректная работа полнотекстового индекса: минимальная длина слова (ft_min_word_len) по умолчанию 4; короткие слова игнорируются. Необходимо настроить в my.cnf или использовать IN BOOLEAN MODE для точного совпадения.
- Пагинация при больших объёмах данных: LIMIT с большим OFFSET медленный; рекомендуется использовать курсорную пагинацию (WHERE id > last_id LIMIT 10).
Вариант: поиск через оператор LIKE
Как реализовать простой поиск без полнотекстового индекса?
$searchTerm = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $term) . '%';
$sql = "SELECT * FROM articles WHERE title LIKE :term OR content LIKE :term";Search type php id type (тип поиска по id в php)
Проблема: низкая производительность на больших таблицах (полное сканирование), нет релевантности. Решение: использовать только для малых объёмов или в связке с индексами FULLTEXT.
Вариант: внешний поисковый движок (Sphinx, Elasticsearch)
Как обеспечить масштабируемый поиск с поддействиями на уровне сервера?
// Пример для Sphinx через API
$sphinx = new SphinxClient();
$sphinx->SetServer('localhost', 9312);
$sphinx->SetMatchMode(SPH_MATCH_EXTENDED2);
$sphinx->SetSortMode(SPH_SORT_RELEVANCE);
$sphinx->SetLimits($offset, $perPage);
$result = $sphinx->Query($query, 'articles_index');Проблема: необходимо установить и настроить дополнительное ПО, сложность синхронизации данных. Решение: использовать только при высокой нагрузке.
Расширенные примеры реализации поиска с поддействиями
Ниже приведены детальные примеры кода с пояснениями и результатами.
Пример 1. Полнотекстовый поиск с поддействием filter по категории
// search.php?subaction=filter&query=php&category=backend&page=1
$query = 'php';
$category = 'backend';
$page = 1;
$perPage = 5;
$offset = ($page - 1) * $perPage;
$sql = "SELECT id, title, LEFT(content, 200) AS snippet,
MATCH(title, content) AGAINST(:query IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE)
AND category = :category
ORDER BY relevance DESC
LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':query', $query, PDO::PARAM_STR);
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Вывод результатов
foreach ($results as $row) {
echo "" . htmlspecialchars($row['title']) . "
";
echo htmlspecialchars($row['snippet']) . "
";
echo "Релевантность: " . $row['relevance'] . "
";
}Результат (пример вывода):Основы PHP для бэкенда
PHP - один из популярных языков...
Релевантность: 5.42PHP и MySQL
Соединение с базой данных...
Релевантность: 3.15
Пример 2. Сортировка по дате (поддействие sort)
// URL: search.php?subaction=sort&query=php&sort=date&page=1
$sort = 'date';
$orders = ['relevance' => 'relevance DESC', 'date' => 'created_at DESC', 'title' => 'title ASC'];
$orderBy = $orders[$sort] ?? $orders['relevance'];
$sql = "SELECT id, title, created_at,
MATCH(title, content) AGAINST(:query IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE)
ORDER BY $orderBy
LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
// ... привязка параметров
$stmt->execute();
$results = $stmt->fetchAll();
// вывод с указанием даты
foreach ($results as $r) {
echo "" . htmlspecialchars($r['title']) . " (" . $r['created_at'] . ")
";
}Результат (сортировка по дате, новые сверху):Новые возможности PHP 8 (2025-03-10)
Основы PHP для бэкенда (2025-02-20)
Пример 3. Пагинация с поддействием page и передачей состояния
$totalResults = 47;
$currentPage = 2;
$perPage = 10;
$totalPages = ceil($totalResults / $perPage);
for ($i = 1; $i <= $totalPages; $i++) {
$active = ($i === $currentPage) ? ' class="active"' : '';
echo "$i ";
}Результат (ссылки пагинации): 1 2 3 4 5
Пример 4. Защита от инъекций при построении ORDER BY (динамическое поле)
$allowedSortFields = ['relevance', 'date', 'title'];
$sortField = $_GET['sort'] ?? 'relevance';
if (!in_array($sortField, $allowedSortFields)) {
$sortField = 'relevance';
}
$orderBy = ($sortField === 'date') ? 'created_at' : (($sortField === 'title') ? 'title' : 'relevance DESC');
// Используем только белый список, исключаем прямое вставлениеТипичная ошибка: прямое включение имени поля из GET в ORDER BY без проверки. Решение: всегда использовать белый список допустимых значений.
Пример 5. Использование AJAX для поддействий без перезагрузки страницы
// JavaScript (фрагмент)
fetch('search.php?subaction=filter&query='+encodeURIComponent(q)+'&category='+cat)
.then(res => res.text())
.then(html => document.getElementById('results').innerHTML = html);На сервере возвращается только фрагмент HTML, который вставляется в контейнер.