Yii2 → Sphinx подсветка результатов поиска

В предыдущей статье я описывал как установить и использовать Sphinx для поиска в Yii2.

Теперь задача: используя Yii2 сделать поисковый запрос к Sphinx, подсветить поисковые слова (snippets, excerpts) и вывести их по 10 на страницу.

  1. Подключаем расширение yiisoft/yii2-sphinx
  2. Выполняем запрос (индекс предварительно настроен и работоспособность проверена)
$sql = "SELECT id, type_id, SNIPPET(title, :q) as _title, SNIPPET(content, :q) AS _content FROM index WHERE MATCH(:q)";
$rows = Yii::$app->sphinx->createCommand($sql)
    ->bindValue('q', Yii::$app->sphinx->escapeMatchValue($q))
    ->queryAll();

Где index — ваш поисковый индекс.

Что бы Sphinx вернул столбец с именем и при этом полноценно производил полнотекстовый поиск по нему, нужно использовать типsql_field_string для полей title и content из примера.

Из документации:

sql_attr_string only stores the column value but does not full-text index it. In some cases it might be desired to both full-text index the column and store it as attribute. sql_field_string lets you do exactly that. Both the field and the attribute will be named the same.

Таким образом настроив конфиг

sql_query = SELECT id, type_id, title, content \
                FROM table \
                WHERE id >= $start AND id <= $end
sql_query_range = SELECT MIN(id),MAX(id) FROM table
sql_attr_uint = type_id
sql_field_string = title # will be both indexed and stored
sql_field_string = content

Мы получаем возможность используя 1 запрос получить список ID записей удовлетворяющих результатам, а также получить дополнительные атрибуты содержащие подсвеченные поисковые слова (highlighted).

На выходе у нас массив записей, содержащий

array (size=3)
  0 => array 
      'id' => string '1' 
      'type_id' => string '3' 
      '_title' => string 'Текст <b>выделение</b> текст' 
      '_content' => string ' ...  текст <b>выделение</b> другой текст... ' 
  1 => array
      'id' => string '2'
      'type_id' => string '3'
      '_title' => string '... <b>выделение</b>' 
      '_content' => string 'Текст выделен только в заголовке ' 
  2 =>
  ...

Теперь вывести результат и разбить постранично — дело техники.

Привожу полный код:

public function actionSearch($q = '')
{
    $q = Yii::$app->sphinx->escapeMatchValue($q);
    $sql = "SELECT id, type_id, SNIPPET(title, :q) as _title, SNIPPET(content, :q) AS _content FROM Index WHERE MATCH(:q)";
    $rows = Yii::$app->sphinx->createCommand($sql)
        ->bindValue('q', $q)
        ->queryAll();

    $snippets = [];
    foreach ($rows as $row) {
        $snippets[$row['id']] = ['title' => $row['_title'], 'content' => $row['_content']];
    }

    $pagination = new Pagination([
        'defaultPageSize' => 5,
        'totalCount' => count($rows),
    ]);

    $model = Table::find()
        ->where(['id' => array_keys($snippets)])
        ->offset($pagination->offset)
        ->limit($pagination->limit)
        ->all();

    return $this->render('search', ['q' => $q, 'model' => $model, 'snippets' => $snippets, 'pagination' => $pagination]);
}

Надеюсь материал будет вам полезен.

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

  • Alex Tiber

    Вот почему-то нигде не акцентируется внимание, что для match-поиска нужны другие типы полей — только здесь и нашел. Спасибо)

  • Artem Petrov

    parse error: unknown identifier ‘snippet’ (not an attribute, not a function)
    Такую ошибку выдает при попытке работы с этим кодом. Гугл тоже в общем не знает что такое snippet в sql