/src/module-elasticsuite-core/Search/Request/Builder.php

https://github.com/Smile-SA/elasticsuite · PHP · 275 lines · 125 code · 32 blank · 118 comment · 7 complexity · 4276a864d1428f10bd5009454b081943 MD5 · raw file

  1. <?php
  2. /**
  3. * DISCLAIMER
  4. *
  5. * Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer
  6. * versions in the future.
  7. *
  8. * @category Smile
  9. * @package Smile\ElasticsuiteCore
  10. * @author Aurelien FOUCRET <aurelien.foucret@smile.fr>
  11. * @copyright 2020 Smile
  12. * @license Open Software License ("OSL") v. 3.0
  13. */
  14. namespace Smile\ElasticsuiteCore\Search\Request;
  15. use Magento\Framework\Search\Request\DimensionFactory;
  16. use Smile\ElasticsuiteCore\Api\Search\Request\ContainerConfiguration\AggregationResolverInterface;
  17. use Smile\ElasticsuiteCore\Search\Request\Query\Builder as QueryBuilder;
  18. use Smile\ElasticsuiteCore\Search\Request\SortOrder\SortOrderBuilder;
  19. use Smile\ElasticsuiteCore\Search\Request\Aggregation\AggregationBuilder;
  20. use Smile\ElasticsuiteCore\Search\RequestInterface;
  21. use Smile\ElasticsuiteCore\Search\RequestFactory;
  22. use Magento\Framework\Search\Request\Dimension;
  23. use Smile\ElasticsuiteCore\Api\Search\Request\ContainerConfigurationInterface;
  24. use Smile\ElasticsuiteCore\Api\Search\Spellchecker\RequestInterfaceFactory as SpellcheckRequestFactory;
  25. use Smile\ElasticsuiteCore\Api\Search\SpellcheckerInterface;
  26. /**
  27. * ElasticSuite search requests builder.
  28. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  29. *
  30. * @category Smile
  31. * @package Smile\ElasticsuiteCore
  32. * @author Aurelien FOUCRET <aurelien.foucret@smile.fr>
  33. */
  34. class Builder
  35. {
  36. /**
  37. * @var ContainerConfigurationFactory
  38. */
  39. private $containerConfigFactory;
  40. /**
  41. * @var QueryBuilder
  42. */
  43. private $queryBuilder;
  44. /**
  45. * @var SortOrderBuilder
  46. */
  47. private $sortOrderBuilder;
  48. /**
  49. * @var AggregationBuilder
  50. */
  51. private $aggregationBuilder;
  52. /**
  53. * @var RequestFactory
  54. */
  55. private $requestFactory;
  56. /**
  57. * @var SpellcheckRequestFactory
  58. */
  59. private $spellcheckRequestFactory;
  60. /**
  61. * @var SpellcheckerInterface
  62. */
  63. private $spellchecker;
  64. /**
  65. * @var DimensionFactory
  66. */
  67. private $dimensionFactory;
  68. /**
  69. * @var \Smile\ElasticsuiteCore\Api\Search\Request\ContainerConfiguration\AggregationResolverInterface
  70. */
  71. private $aggregationResolver;
  72. /**
  73. * Constructor.
  74. *
  75. * @param RequestFactory $requestFactory Factory used to build the request.
  76. * @param DimensionFactory $dimensionFactory Factory used to dimensions of the request.
  77. * @param QueryBuilder $queryBuilder Builder for the query part of the request.
  78. * @param SortOrderBuilder $sortOrderBuilder Builder for the sort part of the request.
  79. * @param AggregationBuilder $aggregationBuilder Builder for the aggregation part of the request.
  80. * @param ContainerConfigurationFactory $containerConfigFactory Search requests configuration.
  81. * @param SpellcheckRequestFactory $spellcheckRequestFactory Spellchecking request factory.
  82. * @param SpellcheckerInterface $spellchecker Spellchecker.
  83. * @param AggregationResolverInterface $aggregationResolver Aggregation Resolver.
  84. */
  85. public function __construct(
  86. RequestFactory $requestFactory,
  87. DimensionFactory $dimensionFactory,
  88. QueryBuilder $queryBuilder,
  89. SortOrderBuilder $sortOrderBuilder,
  90. AggregationBuilder $aggregationBuilder,
  91. ContainerConfigurationFactory $containerConfigFactory,
  92. SpellcheckRequestFactory $spellcheckRequestFactory,
  93. SpellcheckerInterface $spellchecker,
  94. AggregationResolverInterface $aggregationResolver
  95. ) {
  96. $this->spellcheckRequestFactory = $spellcheckRequestFactory;
  97. $this->spellchecker = $spellchecker;
  98. $this->requestFactory = $requestFactory;
  99. $this->dimensionFactory = $dimensionFactory;
  100. $this->queryBuilder = $queryBuilder;
  101. $this->sortOrderBuilder = $sortOrderBuilder;
  102. $this->aggregationBuilder = $aggregationBuilder;
  103. $this->containerConfigFactory = $containerConfigFactory;
  104. $this->aggregationResolver = $aggregationResolver;
  105. }
  106. /**
  107. * Create a new search request.
  108. *
  109. * @param integer $storeId Search request store id.
  110. * @param string $containerName Search request name.
  111. * @param integer $from Search request pagination from clause.
  112. * @param integer $size Search request pagination size.
  113. * @param string|QueryInterface $query Search request query.
  114. * @param array $sortOrders Search request sort orders.
  115. * @param array $filters Search request filters.
  116. * @param QueryInterface[] $queryFilters Search request filters prebuilt as QueryInterface.
  117. * @param array $facets Search request facets.
  118. *
  119. * @return RequestInterface
  120. */
  121. public function create(
  122. $storeId,
  123. $containerName,
  124. $from,
  125. $size,
  126. $query = null,
  127. $sortOrders = [],
  128. $filters = [],
  129. $queryFilters = [],
  130. $facets = []
  131. ) {
  132. $containerConfig = $this->getRequestContainerConfiguration($storeId, $containerName);
  133. $containerFilters = $this->getContainerFilters($containerConfig);
  134. $containerAggs = $this->getContainerAggregations($containerConfig, $query, $filters, $queryFilters);
  135. $facets = array_merge($facets, $containerAggs);
  136. $facetFilters = array_intersect_key($filters, $facets);
  137. $queryFilters = array_merge($queryFilters, $containerFilters, array_diff_key($filters, $facetFilters));
  138. $spellingType = SpellcheckerInterface::SPELLING_TYPE_EXACT;
  139. if ($query && is_string($query)) {
  140. $spellingType = $this->getSpellingType($containerConfig, $query);
  141. }
  142. $requestParams = [
  143. 'name' => $containerName,
  144. 'indexName' => $containerConfig->getIndexName(),
  145. 'from' => $from,
  146. 'size' => $size,
  147. 'dimensions' => $this->buildDimensions($storeId),
  148. 'query' => $this->queryBuilder->createQuery($containerConfig, $query, $queryFilters, $spellingType),
  149. 'sortOrders' => $this->sortOrderBuilder->buildSordOrders($containerConfig, $sortOrders),
  150. 'buckets' => $this->aggregationBuilder->buildAggregations($containerConfig, $facets, $facetFilters),
  151. 'spellingType' => $spellingType,
  152. 'trackTotalHits' => $containerConfig->getTrackTotalHits(),
  153. ];
  154. if (!empty($facetFilters)) {
  155. $requestParams['filter'] = $this->queryBuilder->createFilterQuery($containerConfig, $facetFilters);
  156. }
  157. $request = $this->requestFactory->create($requestParams);
  158. return $request;
  159. }
  160. /**
  161. * Returns search request applied to each request for a given search container.
  162. *
  163. * @param ContainerConfigurationInterface $containerConfig Search request configuration.
  164. *
  165. * @return \Smile\ElasticsuiteCore\Search\Request\QueryInterface[]
  166. */
  167. private function getContainerFilters(ContainerConfigurationInterface $containerConfig)
  168. {
  169. return $containerConfig->getFilters();
  170. }
  171. /**
  172. * Returns aggregations configured in the search container.
  173. *
  174. * @param ContainerConfigurationInterface $containerConfig Search request configuration.
  175. * @param string|QueryInterface $query Search Query.
  176. * @param array $filters Search request filters.
  177. * @param QueryInterface[] $queryFilters Search request filters prebuilt as QueryInterface.
  178. *
  179. * @return array
  180. */
  181. private function getContainerAggregations(ContainerConfigurationInterface $containerConfig, $query, $filters, $queryFilters)
  182. {
  183. return $this->aggregationResolver->getContainerAggregations($containerConfig, $query, $filters, $queryFilters);
  184. }
  185. /**
  186. * Retireve the spelling type for a fulltext query.
  187. *
  188. * @param ContainerConfigurationInterface $containerConfig Search request configuration.
  189. * @param string|string[] $queryText Query text.
  190. *
  191. * @return int
  192. */
  193. private function getSpellingType(ContainerConfigurationInterface $containerConfig, $queryText)
  194. {
  195. if (is_array($queryText)) {
  196. $queryText = implode(" ", $queryText);
  197. }
  198. $spellcheckRequestParams = [
  199. 'index' => $containerConfig->getIndexName(),
  200. 'queryText' => $queryText,
  201. 'cutoffFrequency' => $containerConfig->getRelevanceConfig()->getCutOffFrequency(),
  202. ];
  203. $spellcheckRequest = $this->spellcheckRequestFactory->create($spellcheckRequestParams);
  204. $spellingType = $this->spellchecker->getSpellingType($spellcheckRequest);
  205. return $spellingType;
  206. }
  207. /**
  208. * Load the search request configuration (index, type, mapping, ...) using the search request container name.
  209. *
  210. * @throws \LogicException Thrown when the search container is not found into the configuration.
  211. *
  212. * @param integer $storeId Store id.
  213. * @param string $containerName Search request container name.
  214. *
  215. * @return ContainerConfigurationInterface
  216. */
  217. private function getRequestContainerConfiguration($storeId, $containerName)
  218. {
  219. if ($containerName === null) {
  220. throw new \LogicException('Request name is not set');
  221. }
  222. $config = $this->containerConfigFactory->create(
  223. ['containerName' => $containerName, 'storeId' => $storeId]
  224. );
  225. if ($config === null) {
  226. throw new \LogicException("No configuration exists for request {$containerName}");
  227. }
  228. return $config;
  229. }
  230. /**
  231. * Build a dimenstion object from
  232. * It is quite useless since we have a per store index but required by the RequestInterface specification.
  233. *
  234. * @param integer $storeId Store id.
  235. *
  236. * @return Dimension[]
  237. */
  238. private function buildDimensions($storeId)
  239. {
  240. $dimensions = ['scope' => $this->dimensionFactory->create(['name' => 'scope', 'value' => $storeId])];
  241. return $dimensions;
  242. }
  243. }