DoctrineSolrBundle
Добрый день, хочу представить свой symfony 2 бандл для автоматической синхронизации Doctrine entity в Solr и последующим поиском. Бандл предназначен для работы с Solr на уровне Doctrine entity и позволяет избежать написания низкоуровневых запросов в solr. Процесс установки и подробную документацию можно посмотреть на github.
Возможности
Реализованы основные (не все) возможности поиска стандартного парсера запросов Solr :
Также реализована поддержка SuggestComponent
Пример конфигурации
После установки для начала работы требуется настроить бандл в config.yml. Пример минимальной конфигурации бандла:
mdiyakov_doctrine_solr:
indexed_entities:
page:
class: AppBundle\Entity\Page
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 }
Как результат после каждого создания, обновления "AppBundle\Entity\Page" сущности, будут прондексированы поля "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: AppBundle\Entity\Page
schema: page
config:
- { name: app_version, value: %app_version% }
- { name: host, value: %host% }
...
....
Для каждой индексируемой сущности должно быть задано как минимум одно конфигурационное поле с уникальным значением относительно всех индексируемых сущностей обозначенное как discriminator: true в schemes. Например:
mdiyakov_doctrine_solr:
indexed_entities:
page:
class: AppBundle\Entity\Article
schema: page
config:
- { name: type, value: article }
news:
class: AppBundle\Entity\News
schema: page
config:
- { name: type, value: news }
schemes:
page:
...
config_entity_fields:
- { config_field_name: 'type', document_field_name: 'discriminator', discriminator: true }
Т.к. и AppBundle\Entity\Article и AppBundle\Entity\News использует одну и ту же схему "page" то соотв. уникальность их primary key теряется т.к. могут существовать News и Article с одинаковым id. Чтобы избежать неопределенности задается конфигурационное поле используемое как дискриминатор значение которого добавляется к primary key сущности и результат записывается в уникальное поле документа.
Также есть возможность задать фильтры для индексируемых сущностей применяемых перед тем как сущность будет проиндексирована в solr. В зависимости от результата фильтра сущность может быть проиндексирована, удалена или пропущена во время индексации. Пример:
indexed_entities:
page:
class: AppBundle\Entity\Page
...
filters: [ big_id, published, ... ]
news:
class: AppBundle\Entity\News
...
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 и реализуется через https://symfony.com/doc/current/bundles/DoctrineBundle/entity-listeners.html
В случае когда надо выполнить первичную индексацию для всех сущностей в базе, реализована консольная команда:
app/console doctrine-solr:index
Подробности ее работы и аргументы можно найти на github.
Пример поиска:
После того как конфигурация задана и индексация выполнена можно искать как в рамках отдельной сущности так и в рамках схемы. Пример:
// MyController
//...
// @var \Mdiyakov\DoctrineSolrBundle\Finder\ClassFinder $finder
$finder = $this->get('ds.finder')->getClassFinder(Article::class);
/** @var Article[] $searchResults */
$searchResults = $finder->findSearchTermByFields($searchTerm, ['title']);
Результатом будет массив состоящий только из Article::class.
Если схема используется несколькими сущностями, например:
indexed_entities:
page:
class: AppBundle\Entity\Article
schema: page
...
news:
class: AppBundle\Entity\News
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
UPD: Добавлена поддержка Symfony 3 и PHP 7