Работа с Facet API и Apache Solr. Часть 2

Главные вкладки

Аватар пользователя gWashington gWashington 14 апреля 2012 в 9:05

Всем привет. В предыдущем посте я рассказал, как можно "повлиять" на процесс индексации, добавляя дополнительные поля в индекс, и создавать свои фасеты (фильтры) с помощью Facet API. Сегодня я хочу рассказать об альтернативном способе индексирования полей и возможности поиска по дополнительным полям. Дело в том, что Solr не ищет по всем полям, а только по тем, о которых его просят. Итак, поехали.

Представим задачу, когда нам нужно не добавить какое-то поле из связанных материалов, а изменить способ индексации какого-то текущего поля. Например, есть такой модуль Field Collection, позволяющий сделать поле-контейнер, содержащее в себе несколько полей. В моей структуре с помощью него у публикации указывается автор, а к автору - организация, в которой он работает:

В базе данных у меня есть таблица field_data_field_author_org, в которой есть поле value, которое указывает на entity_id поле таблиц field_data_field_author и field_data_field_jobs. Таким образом в индексированном документе публикации я получаю поле im_field_author_org, значение которого мне абсолютно не нужно. Вот для того, чтобы указать, как будет индексироваться поле, мне нужно определить indexing_callback для этого поля в функции, реализующей хук hook_apachesolr_field_mappings(), находящийся в файле apachesolr.api.inc:

function inti_apachesolr_field_mappings() {
// Функция должна вернуть массив, в котором ключем может быть либо тип поля, либо значение 'per_field'.
// В первом случае мы указываем, как будут индексироваться поля этого типа, во втором - значением будет массив,
// у которого ключем будет имя поля. Настоятельно рекомендую ознакомиться с документацией ко всем хукам, которые
// я указываю в заметках, для этого я пишу, в каких файлах они находятся. Дело в том, что в документации
// это описано довольно подробно, а я лишь хочу описать сам принцип.
$mappings = array();
$mappings['per-field']['field_author_org'] = array(
'indexing_callback' => 'inti_field_author_org_indexing_callback',
// В это поле я хочу записать имена авторов и организаций, поэтому меняю тип с int на text,
// это повлияет на имя поля (см. мой предыдущий пост), которое в данном случае генерируется
// автоматически
'index_type' => 'text',
// Фасеты по этому полю мне не нужны, потому что их я формирую вручную (описано также в предыдщем посте)
'facets' => FALSE,
);
return $mappings;
}

function inti_field_author_org_indexing_callback($entity, $field_name, $index_key, $field_info) {
$fields = array();

// Получаем все значения нашего поля
$field_values = array_map(function($n) { return $n['value']; }, $entity->{$field_name}['und']);

// По этим значениям достаем всех авторов и организации прямо из базы данных
// В момент написания заметки я предположил, что можно было бы воспользоваться функционалом
// самого модуля Field Collection, но это предположение я проверю позже. Smile
$select = db_select('node', 'n');
$select->join('field_data_field_author', 'fdfa', 'fdfa.field_author_nid = n.nid');
$select->condition('fdfa.entity_id', $field_values, 'IN');
$select->fields('n', array('title'));
$authors = $select->execute()->fetchCol();

$select = db_select('node', 'n');
$select->join('field_data_field_jobs', 'fdfj', 'fdfj.field_jobs_nid = n.nid');
$select->condition('fdfj.entity_id', $field_values, 'IN');
$select->fields('n', array('title'));
$orgs = $select->execute()->fetchCol();

// Формируем массив с $index_key в кач-ве ключей, и
// именами авторов и названиями всех организаций в кач-ве значений.
$fields[] = array(
'key' => $index_key,
'value' => implode(' ', $authors),
);
$fields[] = array(
'key' => $index_key,
'value' => implode(' ', $orgs),
);
return $fields;
}

?>

Таким образом в результате индексирования поле у меня выглядит следующим образом:

[tm_field_author_org] => Array
(
    [0] => Иванов Е. С. Петрова Р. Ш. // Авторы
    [1] => Рога и копыта Министерство образования и науки // Организации
)

Теперь мне нужно сообщить Solr'у, что по этому полю тоже необходимо производить поиск. Делается это с помощью реализации хука hook_apachesolr_query_alter(), описанном всё в том же чудесном файле apachesolr.api.php:

function inti_apachesolr_query_alter($query) {
// Поля, в которых нужно искать, должны быть добавлены в параметр 'qf' поискового запроса.
// Формат параметров - обычный массив, значения которого выглядят как fieldname^boost, т.е. тут же поисковик
// будет уведомлен о приоритетах. В моём случае поле достаточно важное, поэтому приоритет делаю
// высоким. Напомню, что имя поля должно быть указано не то, которое в Drupal'е, а то, которое
// в индексированном документе.

$params = array('tm_field_author_org^25.0');
$query->addParam('qf', $params);
}

?>
Кстати, $query - это интерфейс DrupalSolrQueryInterface, описанный в файле apachesolr.interface.inc, с которым рекомендую ознакомиться, если вы хотите узнать, как еще можно работать с запросом перед его отправкой.

Собственно, вот и всё. В следующем посте расскажу о status_callback и индексировании определенных нод.

Примечание: вышенаписанное актуально для текущих версий модулей apachesolr (7.x-1.0-beta19) и facetapi (7.x-1.0-rc4). Они активно развиваются, поэтому если у вас другая версия и что-то не работает, читайте release notes.

Ссылки:
Часть 1. О добавлении полей в индекс и фасетах.
Часть 3. О том, как не индексировать, если не хочется.
Часть 4. Установка Solr 3.x и поиск с использованием *

Комментарии

Аватар пользователя volocuga@drupal.org volocuga@drupal.org 15 апреля 2012 в 6:18

Класс! Очень интересно было бы услышать о работе Facet API с диапзонами чисел. Например, как в интернет магазинах:

Цена

  • 10 - 100$
  • 100 - 200$
  • 200 - 400$
  • более 400$

Если конечно такие изыскания проводились.

Аватар пользователя gWashington gWashington 15 апреля 2012 в 8:08

Ну у меня работа с диапазонами ограничилось доработкой патча, чтобы facetapi_slider начал работать с датами.
Собсно, я думаю, что на его примере можно сделать что угодно.
Для отображения виджета нужно создать класс, наследующий FacetapiWidget, кажется, и реализовать хук hook_facetapi_widgets() (я на память сейчас пишу, глянуть пока негде), а там уже можно творить что угодно.