DoctrineSolrBundle — поиск по Doctrine entity на базе Solr в Symfony2

в 6:37, , рубрики: doctrine, php, search, solr, symfony, symfony2 bundle

DoctrineSolrBundle

Добрый день, хочу представить свой symfony 2 бандл для автоматической синхронизации Doctrine entity в Solr и последующим поиском. Бандл предназначен для работы с Solr на уровне Doctrine entity и позволяет избежать написания низкоуровневых запросов в solr. Процесс установки и подробную документацию можно посмотреть на github.

Возможности

Реализованы основные (не все) возможности поиска стандартного парсера запросов Solr:
Wildcard Searches
Fuzzy Searches
Range Searches
Boosting a Term with ^

Также реализована поддержка SuggestComponent

Пример конфигурации

После установки для начала работы требуется настроить бандл в config.yml. Пример минимальной конфигурации бандла:

mdiyakov_doctrine_solr:
    indexed_entities:
        page:
            class:  AppBundleEntityPage
            schema: page
            config:
                - { name: type, value: page }
    schemes:
        page:
            document_unique_field: { name: 'uid' }
            config_entity_fields:
                - {  config_field_name: 'type', document_field_name: 'type', discriminator: true }
            fields:
                - {  entity_field_name: 'id', document_field_name: 'entity_id', field_type: int, entity_primary_key: true }
                - {  entity_field_name: 'textField', document_field_name: 'text', priority: 100, suggester: true }

Как результат после каждого создания, обновления «AppBundleEntityPage» сущности, будут прондексированы поля «id» и «textField», а также конфигурационное поле «type». В случае удаления экземпляра сущности соответствущий solr документ будет удален.

В «schemes» секции описываютс схемы индексации. В схему индексации входит описание полей сущности (fields), конфигурационных полей (config_entity_fields) которые должны быть проиндексированы в solr. А также «document_unique_field» указывающее уникальное поле для schemes.xml в solr. Помимо этого есть необязательное поле client в случае если надо использовать несколько solr core для разных схем индексации (подробнее об этом тут). По сути каждая схема в schemes отражает конкретное solr core.

Конфигурационное поле это поле которое задается в секции «indexed_entities» в рамках конфига сущности. Его можно использовать для индексации параметров заданных в parameters.yml, например:

....            
class:  AppBundleEntityPage
schema: page
config:
        - { name: app_version, value: %app_version% }
        - { name: host, value: %host% }
       ...
....

Для каждой индексируемой сущности должно быть задано как минимум одно конфигурационное поле с уникальным значением относительно всех индексируемых сущностей обозначенное как discriminator: true в schemes. Например:

mdiyakov_doctrine_solr:
    indexed_entities:
        page:
            class:  AppBundleEntityArticle
            schema: page
            config:
                - { name: type, value: article }
	news:
            class:  AppBundleEntityNews
            schema: page
            config:
                - { name: type, value: news }
  schemes:         
        page:
            ...
            config_entity_fields:
                - {  config_field_name: 'type', document_field_name: 'discriminator', discriminator: true }

Т.к. и AppBundleEntityArticle и AppBundleEntityNews использует одну и ту же схему «page» то соотв. уникальность их primary key теряется т.к. могут существовать News и Article с одинаковым id. Чтобы избежать неопределенности задается конфигурационное поле используемое как дискриминатор значение которого добавляется к primary key сущности и результат записывается в уникальное поле документа.

Также есть возможность задать фильтры для индексируемых сущностей применяемых перед тем как сущность будет проиндексирована в solr. В зависимости от результата фильтра сущность может быть проиндексирована, удалена или пропущена во время индексации. Пример:

indexed_entities:
    page:
        class:  AppBundleEntityPage
        ...
        filters: [ big_id, published, ... ]
    news:
        class:  AppBundleEntityNews
        ...
        filters: [ published, ... ]        
schemes:
       ....
filters:    
    fields:
        big_id: { entity_field_name: "id", entity_field_value: 3, operator: ">=" }
        published: { entity_field_name: "published", entity_field_value: true, operator: "=" }

Соотв. если после того как Page или News были созданы и например поле «published» = false то индексация будет пропущена и в solr ничего не будет записано.

Если же Page или News были обновлены и например поле «published» = false то solr документ соответствующий этому экземпляру сущности будет удален в solr

То же самое и для фильтра big_id в случае если значение поле id < 3. «big_id» и «published» это произвольные названия для фильтров, могут быть какими угодно. Также есть возможность задачть symfony service как фильтр который применяется к экземпляру сущности а не к отдельному полю, подробности тут
Для успешной индексации должны быть выполнены условия для всех фильтров.

Индексация

Индексация сущности запускается каждый раз когда выполняется $em->flush и реализуется через symfony.com/doc/current/bundles/DoctrineBundle/entity-listeners.html

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

app/console doctrine-solr:index

Подробности ее работы и аргументы можно найти на github

Пример поиска:

После того как конфигурация задана и индексация выполнена можно искать как в рамках отдельной сущности так и в рамках схемы. Пример:

// MyController
//...
// @var MdiyakovDoctrineSolrBundleFinderClassFinder $finder 
$finder = $this->get('ds.finder')->getClassFinder(Article::class);

/** @var Article[] $searchResults */
$searchResults = $finder->findSearchTermByFields($searchTerm, ['title']);

Результатом будет массив состоящий только из Article::class.

Если схема используется несколькими сущностями, например:

  indexed_entities:
        page:
            class:  AppBundleEntityArticle
            schema: page
           ...
	news:
            class:  AppBundleEntityNews
            schema: page
            ...
...

то можно искать по всем сущностям использующим эту схему:

$schemaFinder = $this->get('ds.finder')->getSchemaFinder('page');
$schemaFinder->addSelectClass(Article::class);
$schemaFinder->addSelectClass(News::class);

/** @var object[] **/
$result = $schemaFinder->findSearchTermByFields($searchTerm, ['title', 'category']);

Результатом будет массив состоящий из Article и News экземпляров отсортированных в соотв. с релевантностью.

Подробнее про методы поиска тут

Заключение

Это вводная статься не описывающая полностью все возможности. Если вас заинтересовал бандл привожу пару ссылок для быстрого перехода по остальным возможностям бандла:
— использование SuggestComponent
— построение своих запросов Query Building
— реализация своего ClassFinder

Автор: KoloBango

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js