diff --git a/config/packages/monolog.php b/config/packages/monolog.php new file mode 100644 index 0000000..1287791 --- /dev/null +++ b/config/packages/monolog.php @@ -0,0 +1,12 @@ +channels(['webgriffe_sylius_elasticsearch_plugin']); +}; diff --git a/config/services/builder.php b/config/services/builder.php new file mode 100644 index 0000000..59728da --- /dev/null +++ b/config/services/builder.php @@ -0,0 +1,19 @@ +services(); + + $services->set('webgriffe.sylius_elasticsearch_plugin.builder.query', TwigQueryBuilder::class) + ->args([ + service('twig'), + service('sylius.context.locale'), + service('monolog.logger.webgriffe_sylius_elasticsearch_plugin'), + ]) + ; +}; diff --git a/config/services/controller.php b/config/services/controller.php index 14d47c0..4679c18 100644 --- a/config/services/controller.php +++ b/config/services/controller.php @@ -19,6 +19,7 @@ service('webgriffe.sylius_elasticsearch_plugin.provider.document_type'), service('webgriffe.sylius_elasticsearch_plugin.parser.elasticsearch_document'), service('form.factory'), + service('webgriffe.sylius_elasticsearch_plugin.builder.query'), ]) ->call('setContainer', [service('service_container')]) ->tag('controller.service_arguments') diff --git a/src/Builder/QueryBuilderInterface.php b/src/Builder/QueryBuilderInterface.php new file mode 100644 index 0000000..f4f077c --- /dev/null +++ b/src/Builder/QueryBuilderInterface.php @@ -0,0 +1,18 @@ + $sorting + */ + public function buildTaxonQuery( + TaxonInterface $taxon, + array $sorting = [], + ): array; +} diff --git a/src/Builder/TwigQueryBuilder.php b/src/Builder/TwigQueryBuilder.php new file mode 100644 index 0000000..c2b6ddd --- /dev/null +++ b/src/Builder/TwigQueryBuilder.php @@ -0,0 +1,51 @@ +twig->render('@WebgriffeSyliusElasticsearchPlugin/query/taxon/query.json.twig', [ + 'taxon' => $taxon, + ]); + $taxonQuery = []; + /** @var array $queryNormalized */ + $queryNormalized = json_decode($query, true, 512, JSON_THROW_ON_ERROR); + $taxonQuery['query'] = $queryNormalized; + $localeCode = $this->localeContext->getLocaleCode(); + + foreach ($sorting as $field => $order) { + $sort = $this->twig->render('@WebgriffeSyliusElasticsearchPlugin/query/taxon/sort/' . $field . '.json.twig', [ + 'field' => $field, + 'order' => $order, + 'taxon' => $taxon, + 'localeCode' => $localeCode, + ]); + /** @var array $sortNormalized */ + $sortNormalized = json_decode($sort, true, 512, JSON_THROW_ON_ERROR); + $taxonQuery['sort'][] = $sortNormalized; + } + + $this->logger->debug(sprintf('Built taxon query: "%s".', json_encode($taxonQuery, JSON_THROW_ON_ERROR))); + + return $taxonQuery; + } +} diff --git a/src/Client/ClientInterface.php b/src/Client/ClientInterface.php index d09e9f8..7603ab8 100644 --- a/src/Client/ClientInterface.php +++ b/src/Client/ClientInterface.php @@ -15,7 +15,7 @@ interface ClientInterface extends LoggerAwareInterface /** * @throws CreateIndexException */ - public function createIndex(string $name, array $mappings): void; + public function createIndex(string $name, array $mappings, array $settings): void; /** * @throws SwitchAliasException diff --git a/src/Client/ElasticsearchClient.php b/src/Client/ElasticsearchClient.php index 1741845..18c65ec 100644 --- a/src/Client/ElasticsearchClient.php +++ b/src/Client/ElasticsearchClient.php @@ -36,11 +36,14 @@ private function getLogger(): ?LoggerInterface return $this->logger; } - public function createIndex(string $name, array $mappings): void + public function createIndex(string $name, array $mappings, array $settings): void { $params = [ 'index' => $name, - 'body' => ['mappings' => $mappings], + 'body' => [ + 'mappings' => $mappings, + 'settings' => $settings, + ], ]; try { diff --git a/src/Controller/ElasticsearchController.php b/src/Controller/ElasticsearchController.php index 19dc186..a1a9a66 100644 --- a/src/Controller/ElasticsearchController.php +++ b/src/Controller/ElasticsearchController.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Webgriffe\SyliusElasticsearchPlugin\Builder\QueryBuilderInterface; use Webgriffe\SyliusElasticsearchPlugin\Client\ClientInterface; use Webgriffe\SyliusElasticsearchPlugin\DocumentType\ProductDocumentType; use Webgriffe\SyliusElasticsearchPlugin\Form\SearchType; @@ -38,6 +39,7 @@ public function __construct( private readonly DocumentTypeProviderInterface $documentTypeProvider, private readonly DocumentParserInterface $documentParser, private readonly FormFactoryInterface $formFactory, + private readonly QueryBuilderInterface $queryBuilder, ) { } @@ -91,7 +93,7 @@ public function searchAction(Request $request, ?string $query = null): Response ]); } - public function taxonAction(string $slug): Response + public function taxonAction(Request $request, string $slug): Response { $localeCode = $this->localeContext->getLocaleCode(); $taxon = $this->taxonRepository->findOneBySlug($slug, $localeCode); @@ -102,44 +104,14 @@ public function taxonAction(string $slug): Response Assert::isInstanceOf($channel, ChannelInterface::class); $productIndexAliasName = $this->indexNameGenerator->generateAlias($channel, $this->documentTypeProvider->getDocumentType(ProductDocumentType::CODE)); - $query = [ - 'query' => [ - 'bool' => [ - 'must' => [ - [ - 'nested' => [ - 'path' => 'taxons', - 'query' => [ - 'bool' => [ - 'must' => [ - [ - 'term' => [ - 'taxons.sylius-id' => $taxon->getId(), - ], - ], - ], - ], - ], - ], - ], - [ - 'term' => [ - 'enabled' => true, - ], - ] - ], - ], - ], - 'sort' => [ - ['taxons.position' => [ - 'order' => 'asc', - 'mode' => 'min', - 'nested' => [ - 'path' => 'taxons', - ], - ]] - ] - ]; + + /** @var array $sorting */ + $sorting = $request->query->all('sorting'); + if ($sorting === []) { + $sorting = ['position' => 'asc']; + } + + $query = $this->queryBuilder->buildTaxonQuery($taxon, $sorting); $result = $this->indexManager->query($query, [$productIndexAliasName]); $responses = []; /** @var array{_index: string, _id: string, score: float, _source: array} $hit */ diff --git a/src/DocumentType/DocumentTypeInterface.php b/src/DocumentType/DocumentTypeInterface.php index fb3cf47..0e84c04 100644 --- a/src/DocumentType/DocumentTypeInterface.php +++ b/src/DocumentType/DocumentTypeInterface.php @@ -17,4 +17,7 @@ public function getDocuments(ChannelInterface $channel): array; /** @return array */ public function getMappings(): array; + + /** @return array */ + public function getSettings(): array; } diff --git a/src/DocumentType/ProductDocumentType.php b/src/DocumentType/ProductDocumentType.php index 2c223cb..0e6583d 100644 --- a/src/DocumentType/ProductDocumentType.php +++ b/src/DocumentType/ProductDocumentType.php @@ -42,6 +42,21 @@ public function getDocuments(ChannelInterface $channel): array return $documents; } + public function getSettings(): array + { + return [ + 'analysis' => [ + 'analyzer' => [ + 'search_standard' => [ + 'type' => 'custom', + 'tokenizer' => 'icu_tokenizer', + 'filter' => ['lowercase', 'icu_folding', 'elision'], + ], + ], + ], + ]; + } + public function getMappings(): array { return [ @@ -152,7 +167,7 @@ private static function nestedTranslationValues(bool $indexValue = true): array 'dynamic' => 'false', 'include_in_parent' => true, 'properties' => [ - 'locale' => self::text(false), + 'locale' => self::text(), 'value' => self::keyword($indexValue), ], ]; @@ -271,7 +286,7 @@ private static function variantProperties(): array 'enabled' => true, 'subobjects' => true, 'properties' => self::priceProperties(), - ] + ], ]; } diff --git a/src/MessageHandler/CreateIndexHandler.php b/src/MessageHandler/CreateIndexHandler.php index 505c4ff..a76adbb 100644 --- a/src/MessageHandler/CreateIndexHandler.php +++ b/src/MessageHandler/CreateIndexHandler.php @@ -38,7 +38,7 @@ public function __invoke(CreateIndex $message): void $aliasName = $this->indexNameGenerator->generateAlias($channel, $documentType); $indexesToRemoveWildcard = $this->indexNameGenerator->generateWildcardPattern($channel, $documentType); - $this->indexManager->createIndex($indexName, $documentType->getMappings()); + $this->indexManager->createIndex($indexName, $documentType->getMappings(), $documentType->getSettings()); $this->indexManager->bulk($indexName, $documentType->getDocuments($channel)); $this->indexManager->switchAlias($aliasName, $indexName); $this->indexManager->removeIndexes($indexesToRemoveWildcard, [$indexName]); diff --git a/src/Serializer/ProductNormalizer.php b/src/Serializer/ProductNormalizer.php index 68f9286..f7c895a 100644 --- a/src/Serializer/ProductNormalizer.php +++ b/src/Serializer/ProductNormalizer.php @@ -160,7 +160,7 @@ private function normalizeProductTaxon(ProductTaxonInterface $productTaxon): arr return array_merge( $this->normalizeTaxon($taxon), - ['position' => $productTaxon->getPosition()] + ['position' => $productTaxon->getPosition()], ); } diff --git a/templates/query/taxon/query.json.twig b/templates/query/taxon/query.json.twig new file mode 100644 index 0000000..60782e8 --- /dev/null +++ b/templates/query/taxon/query.json.twig @@ -0,0 +1,27 @@ +{ + "bool": { + "must": [ + { + "nested": { + "path": "taxons", + "query": { + "bool": { + "must": [ + { + "term": { + "taxons.sylius-id": "{{ taxon.id }}" + } + } + ] + } + } + } + }, + { + "term": { + "enabled": true + } + } + ] + } +} diff --git a/templates/query/taxon/sort/createdAt.json.twig b/templates/query/taxon/sort/createdAt.json.twig new file mode 100644 index 0000000..f4429fa --- /dev/null +++ b/templates/query/taxon/sort/createdAt.json.twig @@ -0,0 +1,5 @@ +{ + "created-at": { + "order": "{{ order }}" + } +} diff --git a/templates/query/taxon/sort/name.json.twig b/templates/query/taxon/sort/name.json.twig new file mode 100644 index 0000000..2c3accd --- /dev/null +++ b/templates/query/taxon/sort/name.json.twig @@ -0,0 +1,13 @@ +{ + "name.value": { + "order": "{{ order }}", + "nested": { + "path": "name", + "filter": { + "match": { + "name.locale": "{{ localeCode }}" + } + } + } + } +} diff --git a/templates/query/taxon/sort/position.json.twig b/templates/query/taxon/sort/position.json.twig new file mode 100644 index 0000000..e587e11 --- /dev/null +++ b/templates/query/taxon/sort/position.json.twig @@ -0,0 +1,13 @@ +{ + "taxons.position": { + "order": "{{ order }}", + "nested": { + "path": "taxons", + "filter": { + "term": { + "taxons.sylius-id": "{{ taxon.id }}" + } + } + } + } +} diff --git a/templates/query/taxon/sort/price.json.twig b/templates/query/taxon/sort/price.json.twig new file mode 100644 index 0000000..86b24d2 --- /dev/null +++ b/templates/query/taxon/sort/price.json.twig @@ -0,0 +1,9 @@ +{ + "variants.price.price": { + "order": "{{ order }}", + "mode": "avg", + "nested": { + "path": "variants" + } + } +} diff --git a/tests/Application/config/packages/dev/monolog.yaml b/tests/Application/config/packages/dev/monolog.yaml index da2b092..4cba833 100644 --- a/tests/Application/config/packages/dev/monolog.yaml +++ b/tests/Application/config/packages/dev/monolog.yaml @@ -7,3 +7,8 @@ monolog: firephp: type: firephp level: info + elasticsearch_plugin: + type: stream + path: "%kernel.logs_dir%/es_%kernel.environment%.log" + level: debug + channels: ["webgriffe_sylius_elasticsearch_plugin"]