Поиск на PHP: эффективные стратегии и код

Раздел: -> Поиск

Методы реализации поиска на PHP

Как найти записи по части слова с помощью SQL оператора LIKE?

Цель: быстрый старт без дополнительных настроек базы данных. Подходит для малых объёмов данных и простых запросов.


$search = '%' . $query . '%';
$stmt = $pdo->prepare('SELECT * FROM articles WHERE title LIKE :search OR content LIKE :search');
$stmt->execute(['search' => $search]);
$results = $stmt->fetchAll();

Search php (реализация поиска на php)

Проблемы: оператор LIKE не использует индексы при подстановочном знаке в начале, что замедляет поиск на больших таблицах. Также нет учёта морфологии и релевантности.

Как задать сложный шаблон поиска через регулярные выражения в MySQL?

Цель: точный поиск с гибкими шаблонами, например, поиск слов с определённым окончанием или исключение символов.


$pattern = '[[:<:]]' . preg_quote($query, '/') . '[[:>:]]';  // целое слово
$stmt = $pdo->prepare("SELECT * FROM articles WHERE title REGEXP :pattern");
$stmt->execute(['pattern' => $pattern]);

Results php (результаты php)

Ошибки: REGEXP не использует индексы, производительность падает с ростом таблицы. Некорректное экранирование специальных символов в пользовательском запросе.

Как организовать быстрый поиск по тексту с учётом морфологии и релевантности?

Цель: эффективный поиск в текстовых полях с ранжированием результатов, поддержка стоп-слов и минимальной длины слова. Используется полнотекстовый индекс MySQL (FULLTEXT).

Для работы необходим индекс FULLTEXT на столбцах, по которым ведётся поиск. Индекс может быть создан как на таблицах MyISAM, так и InnoDB (начиная с MySQL 5.6).


-- Создание индекса FULLTEXT
ALTER TABLE articles ADD FULLTEXT INDEX ft_search (title, content);

// Поиск в естественном режиме
$query = 'PHP поиск';
$stmt = $pdo->prepare("SELECT *, MATCH(title, content) AGAINST(:query) AS relevance FROM articles WHERE MATCH(title, content) AGAINST(:query) ORDER BY relevance DESC");
$stmt->execute(['query' => $query]);

Типичные ошибки:

  • Отсутствие индекса FULLTEXT приводит к ошибке SQL.
  • Стоп-слова (например, "и", "в", "на") игнорируются по умолчанию.
  • Минимальная длина слова (по умолчанию 4 символа для InnoDB) может отсекать короткие запросы. Изменяется через параметры ft_min_word_len (MyISAM) или innodb_ft_min_token_size (InnoDB).
  • Булев режим (BOOLEAN MODE) требует экранирования специальных символов (+, -, *, ~).

Как обрабатывать большие объёмы данных с высокой скоростью и настраиваемой логикой поиска?

Цель: полноценная поисковая система с кластеризацией, фасетами, исправлением опечаток и многопоточной индексацией. Используются специализированные инструменты: Elasticsearch, Sphinx, Apache Solr.

Пример интеграции с Elasticsearch через HTTP API (без библиотек).


// Индексация документа
$data = [
    'title'   => 'Пример реализации поиска',
    'content' => 'Текст статьи о PHP и Elasticsearch'
];
$json = json_encode($data);
$ch = curl_init('http://localhost:9200/articles/_doc');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

// Поиск
$searchQuery = ['query' => ['match' => ['content' => 'PHP']]];
$json = json_encode($searchQuery);
$ch = curl_init('http://localhost:9200/articles/_search');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);

Сложности: требуется установка и настройка отдельного сервера (Elasticsearch, Sphinx). Необходимо поддерживать синхронизацию индекса с базой данных. Повышенные требования к памяти и диску.

Расширенные примеры реализации

Ниже приведены подробные примеры кода с пояснением каждого шага и выводом результатов.

Пример 1: Полнотекстовый поиск в MySQL с булевым режимом и настройкой минимальной длины слова

Для демонстрации создана таблица articles с индексом FULLTEXT и вставлены несколько записей.

Пример

-- Настройка минимальной длины токена (InnoDB) перед созданием индекса
SET GLOBAL innodb_ft_min_token_size = 2;

-- Создание таблицы и индекса
CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    content TEXT,
    FULLTEXT ft_index (title, content)
) ENGINE=InnoDB;

-- Вставка данных
INSERT INTO articles (title, content) VALUES
('Основы PHP', 'PHP это скриптовый язык для веб-разработки.'),
('Поиск данных в MySQL', 'MySQL поддерживает полнотекстовый поиск с 5.6.'),
('Elasticsearch для новичков', 'Elasticsearch распределенная поисковая система.');

Запрос в булевом режиме с принудительным включением слова (+), исключением (-) и подстановкой (*).

Пример

$query = '+PHP -Elasticsearch *'; // слово 'PHP' обязательно, 'Elasticsearch' исключено, любое слово по маске
$stmt = $pdo->prepare("SELECT *, 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]);
$results = $stmt->fetchAll();
print_r($results);
Array
(
    [0] => Array
        (
            [id] => 1
            [title] => Основы PHP
            [content] => PHP это скриптовый язык для веб-разработки.
            [relevance] => 1.234567
        )
)

Обратите внимание: стоп-слово это не участвует в поиске, слово для тоже игнорируется. Если нужно изменить список стоп-слов, используется системная таблица INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD.

Пример 2: Поиск с использованием библиотеки Elasticsearch-PHP (официальная)

Установка через Composer: composer require elasticsearch/elasticsearch.

Пример

require 'vendor/autoload.php';

use Elastic\Elasticsearch\ClientBuilder;

$client = ClientBuilder::create()->build();

// Параметры индекса (создание, если не существует)
$params = [
    'index' => 'articles',
    'body'  => [
        'settings' => [
            'number_of_shards' => 1,
            'number_of_replicas' => 0
        ],
        'mappings' => [
            'properties' => [
                'title'   => ['type' => 'text'],
                'content' => ['type' => 'text']
            ]
        ]
    ]
];
$client->indices()->create($params);

// Индексация двух документов
$documents = [
    ['title' => 'PHP фреймворки', 'content' => 'Laravel, Symfony, Yii'],
    ['title' => 'Поиск в PHP', 'content' => 'Реализация поиска через Elasticsearch']
];
foreach ($documents as $doc) {
    $client->index([
        'index' => 'articles',
        'body'  => $doc
    ]);
}
$client->indices()->refresh(['index' => 'articles']);

// Поиск с выделением фрагментов (highlighting)
$searchResult = $client->search([
    'index' => 'articles',
    'body'  => [
        'query' => [
            'multi_match' => [
                'query'  => 'PHP',
                'fields' => ['title', 'content']
            ]
        ],
        'highlight' => [
            'fields' => [
                'title'   => ['number_of_fragments' => 1],
                'content' => ['number_of_fragments' => 2]
            ]
        ]
    ]
]);

// Вывод результатов
echo '
';
foreach ($searchResult['hits']['hits'] as $hit) {
    echo "ID: {$hit['_id']}\n";
    echo "Score: {$hit['_score']}\n";
    echo "Title: {$hit['_source']['title']}\n";
    if (isset($hit['highlight'])) {
        echo "Highlight: ";
        print_r($hit['highlight']);
    }
    echo "\n---\n";
}
echo '
';
ID: 1
Score: 0.2876821
Title: PHP фреймворки
Highlight: Array
(
    [title] => Array
        (
            [0] => PHP фреймворки
        )
    [content] => Array
        (
            [0] => Laravel, Symfony, Yii
        )
)

---
ID: 2
Score: 0.2876821
Title: Поиск в PHP
Highlight: Array
(
    [title] => Array
        (
            [0] => Поиск в PHP
        )
    [content] => Array
        (
            [0] => Реализация поиска через Elasticsearch
        )
)

Пример 3: Поиск с использованием Sphinx через PDO-подобный API (sphinxapi)

Необходимо скачать sphinxapi.php из дистрибутива Sphinx и подключить.

Пример

require 'sphinxapi.php';

$sphinx = new SphinxClient();
$sphinx->setServer('localhost', 9312);
$sphinx->setMatchMode(SPH_MATCH_EXTENDED2);
$sphinx->setSortMode(SPH_SORT_RELEVANCE);

// Поиск с морфологией (морфологический словарь должен быть настроен)
$sphinx->setLimits(0, 10);
$result = $sphinx->query('@(title,content) PHP поиск', 'articles_index');

if ($result === false) {
    echo "Ошибка: {$sphinx->GetLastError()}\n";
} else {
    echo "Найдено совпадений: {$result['total']}\n";
    foreach ($result['matches'] as $id => $match) {
        echo "ID: $id, Вес: {$match['weight']}\n";
    }
}
Найдено совпадений: 2
ID: 1, Вес: 1523
ID: 2, Вес: 1487

Типичные проблемы: для корректной работы Sphinx требуется настроить источник данных (часто XML или SQL), а также демон searchd. Ошибки соединения или отсутствие индекса приводят к пустому результату или исключению.

Реализация поиска на PHP - comments

En
Search php (php)