Практические подходы к построению сложного поиска в PHP

Раздел: Веб-разработка -> Поисковые функции

Расширенный поиск в PHP: возможности и реализация

Наиболее эффективное решение для расширенного поиска в PHP заключается в создании гибкого построителя запросов, который динамически формирует SQL с использованием подготовленных выражений. Это позволяет безопасно комбинировать любое количество условий.

class SearchQueryBuilder {
    private array $conditions = [];
    private array $params = [];
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function addCondition(string $field, string $operator, mixed $value, string $boolean = 'AND'): void {
        $paramKey = ':' . $field . '_' . count($this->conditions);
        $this->conditions[] = "$field $operator $paramKey";
        $this->params[$paramKey] = $value;
    }

    public function getQuery(): string {
        $where = implode(' AND ', $this->conditions);
        return "SELECT * FROM products WHERE $where";
    }

    public function execute(): array {
        $stmt = $this->pdo->prepare($this->getQuery());
        $stmt->execute($this->params);
        return $stmt->fetchAll();
    }
}

$builder = new SearchQueryBuilder($pdo);
$builder->addCondition('price', '>=', 100);
$builder->addCondition('category_id', 'IN', [2, 5], 'AND');
$builder->addCondition('name', 'LIKE', '%телефон%');
$results = $builder->execute();

Search php search author (поиск по автору в php)

Array of product objects matching price >= 100, category 2 or 5, and name containing 'телефон'

Index php act search ru (поиск с параметром ru в php)

Типичная ошибка:

неверный порядок условий или пропуск экранирования. Все параметры передаются через подготовленные запросы, что исключает SQL-инъекции. Однако если в условии используется IN с массивом, необходимо динамически генерировать плейсхолдеры для каждого элемента.

Как организовать поиск с использованием ORM (Doctrine QueryBuilder)?

Doctrine QueryBuilder предоставляет объектно-ориентированный интерфейс для построения DQL (Doctrine Query Language) запросов, что упрощает работу с сущностями.

$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder->select('p')
    ->from(Product::class, 'p')
    ->where('p.price >= :minPrice')
    ->andWhere('p.category IN (:categories)')
    ->setParameter('minPrice', 100)
    ->setParameter('categories', [2, 5])
    ->orderBy('p.createdAt', 'DESC');
$results = $queryBuilder->getQuery()->getResult();

Advance search php (расширенный поиск в php)

Array of Product entities

Search php vars (переменные поиска в php)

Проблемы:

при использовании ORM возрастает накладные расходы на гидратацию объектов. Для больших наборов данных может потребоваться пагинация или использование частичных результатов.

Как выполнить полнотекстовый поиск в MySQL через PHP?

MySQL поддерживает полнотекстовые индексы и оператор MATCH ... AGAINST. В PHP это реализуется через подготовленные запросы.

$searchTerm = 'смартфон samsung';
$sql = "SELECT *, MATCH(title, description) AGAINST(:term IN BOOLEAN MODE) AS relevance
        FROM products
        WHERE MATCH(title, description) AGAINST(:term IN BOOLEAN MODE)
        ORDER BY relevance DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute(['term' => $searchTerm]);
$results = $stmt->fetchAll();

Search php search id 2 (поиск по id 2 в php)

Array of products ordered by relevance

Search php key (поиск по ключу в php)

Типичная ошибка:

отсутствие полнотекстового индекса на столбцах приводит к ошибке. Также минимальная длина слова (ft_min_word_len) может отсекать короткие запросы. Необходимо настроить параметры MySQL и использовать BOOLEAN MODE для управления операторами (+ - *).
Как использовать Elasticsearch для расширенного поиска в PHP?

Elasticsearch предоставляет мощный движок поиска с поддержкой анализа текста, фильтрации и агрегаций. В PHP используется официальный клиент elasticsearch-php.

require 'vendor/autoload.php';
use Elastic\Elasticsearch\ClientBuilder;

$client = ClientBuilder::create()->build();
$params = [
    'index' => 'products',
    'body' => [
        'query' => [
            'bool' => [
                'must' => [
                    ['match' => ['title' => 'смартфон']],
                    ['range' => ['price' => ['gte' => 100]]]
                ],
                'filter' => [
                    ['terms' => ['category_id' => [2, 5]]]
                ]
            ]
        ],
        'sort' => ['_score' => 'desc']
    ]
];
$response = $client->search($params);
$results = $response->asArray()['hits']['hits'];

Search php p 1 (поиск по странице 1 в php)

Array of documents with relevance scores

Search php new (поиск новых записей в php)

Проблемы:

требуется установка и настройка отдельного сервера Elasticsearch. Также необходимо синхронизировать данные из базы с индексом. Библиотека требует PHP 7.4+ и может иметь разные версии API.
Как реализовать поиск по массиву данных с помощью регулярных выражений в PHP?

Для небольших наборов данных в памяти удобно использовать preg_grep для фильтрации массива по регулярному выражению.

$products = [
    ['name' => 'Samsung Galaxy', 'price' => 300],
    ['name' => 'Apple iPhone', 'price' => 500],
    ['name' => 'Xiaomi Redmi', 'price' => 200]
];
$pattern = '/samsung|apple/i';
$filtered = preg_grep($pattern, array_column($products, 'name'));
$filteredProducts = array_intersect_key($products, $filtered);

Search php search id 0 (поиск по id 0 в php)

Array with Samsung Galaxy and Apple iPhone

Index php act search query (поиск с параметром query в php)

Ошибки:

регулярные выражения чувствительны к регистру без флага i; при большом объёме данных производительность падает. Использовать только для статических коллекций.
Как использовать Sphinx Search с PHP для полнотекстового поиска?

Sphinx – внешняя поисковая система, оптимизированная для полнотекстового поиска. Подключение через PDO с драйвером mysql, так как Sphinx поддерживает протокол MySQL.

$sphinxPdo = new PDO('mysql:host=127.0.0.1;port=9306;charset=utf8', '', '');
$sql = "SELECT id, weight() FROM products_index WHERE MATCH('@title смартфон | @description samsung') LIMIT 20";
$stmt = $sphinxPdo->query($sql);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

Search city php (поиск города в php)

Array with document ids and weights

Типичная ошибка:

Sphinx не поддерживает все операторы SQL – только SELECT и SHOW. Необходимо настроить источник данных (indexer) для регулярной индексации базы.

Расширенные примеры реализации сложного поиска

Построение поиска с пагинацией и сортировкой

Пример класса, поддерживающего пагинацию и несколько полей сортировки без дублирования кода.

Пример
class AdvancedSearch {
    private PDO $pdo;
    private array $conditions = [];
    private array $params = [];
    private string $orderBy = 'id ASC';
    private int $limit = 20;
    private int $offset = 0;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function addCondition(string $sqlPart, array $params): void {
        $this->conditions[] = $sqlPart;
        $this->params = array_merge($this->params, $params);
    }

    public function setOrder(string $order): void {
        $this->orderBy = $order;
    }

    public function setPage(int $page, int $perPage = 20): void {
        $this->limit = $perPage;
        $this->offset = ($page - 1) * $perPage;
    }

    public function search(): array {
        $where = $this->conditions ? 'WHERE ' . implode(' AND ', $this->conditions) : '';
        $sql = "SELECT * FROM products $where ORDER BY {$this->orderBy} LIMIT :limit OFFSET :offset";
        $stmt = $this->pdo->prepare($sql);
        $this->params[':limit'] = $this->limit;
        $this->params[':offset'] = $this->offset;
        $stmt->execute($this->params);
        return $stmt->fetchAll();
    }

    public function count(): int {
        $where = $this->conditions ? 'WHERE ' . implode(' AND ', $this->conditions) : '';
        $sql = "SELECT COUNT(*) FROM products $where";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($this->params);
        return (int)$stmt->fetchColumn();
    }
}

$search = new AdvancedSearch($pdo);
$search->addCondition('price BETWEEN :min AND :max', [':min' => 100, ':max' => 1000]);
$search->addCondition('title LIKE :title', [':title' => '%телефон%']);
$search->setOrder('price ASC');
$search->setPage(2, 10);
$total = $search->count();
$items = $search->search();
$total = 45, $items = array of 10 products from page 2

Особенности:

при большом количестве условий необходимо следить за уникальностью имен параметров. Для IN-условий потребуется ручное расширение плейсхолдеров.

Поиск с синонимами на основе MySQL-функции SOUNDEX

Для поиска похожих по звучанию слов можно использовать SOUNDEX вместе с LIKE.

Пример
$word = 'фотел';
$soundex = soundex($word);
$sql = "SELECT * FROM products WHERE SOUNDEX(name) = :soundex OR name LIKE :like";
$stmt = $pdo->prepare($sql);
$stmt->execute([':soundex' => $soundex, ':like' => '%' . $word . '%']);
Returns products with name 'фотол' or 'фотел'

Типичная ошибка:

SOUNDEX работает только для английского и некоторых других языков. Для русского языка требуется метафон или Levenshtein расстояние, но это может снизить производительность.

Кэширование результатов расширенного поиска

Использование Memcached для кэширования результатов по уникальному ключу, сформированному из параметров запроса.

Пример
$memcached = new Memcached();
$memcached->addServer('localhost', 11211);

$cacheKey = 'search_' . md5(serialize([$_GET['q'], $_GET['category'], $_GET['page']]));
$results = $memcached->get($cacheKey);
if ($results === false) {
    // выполнение поискового запроса
    $results = performSearch($pdo, $_GET);
    $memcached->set($cacheKey, $results, 300); // кэш на 5 минут
}
$results from cache or DB

Ошибки:

кэширование может приводить к выдаче устаревших данных; необходимо инвалидировать кэш при изменении данных (например, через события).

Комбинированный поиск с использованием нескольких индексов (MySQL + Elasticsearch)

Для получения быстрых результатов по полному тексту и точных данных из реляционной БД.

Пример
$ids = searchElasticsearch($searchTerm); // возвращает массив ID
if (!$ids) {
    $ids = [0]; // чтобы не было пустого IN
}
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "SELECT * FROM products WHERE id IN ($placeholders) AND price >= ?";
$stmt = $pdo->prepare($sql);
$stmt->execute(array_merge($ids, [100]));
$products = $stmt->fetchAll();
Array of products with IDs from Elasticsearch, filtered by price

Проблема:

дублирование логики – данные в ES и MySQL необходимо синхронизировать. Подходит для сценариев, где нужны сложные полнотекстовые возможности.

Расширенный поиск в PHP - comments

En
Advance search php (php)