Files
2025-08-02 16:30:27 +02:00

161 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
namespace KupShop\GraphQLBundle\ApiPublic\Controller;
use Elastica\Query\Ids;
use Elastica\Result;
use KupShop\AdminBundle\Util\SystemImageUtils;
use KupShop\CatalogBundle\Search\Exception\FulltextException;
use KupShop\CatalogBundle\Search\FulltextElastic;
use KupShop\CatalogElasticBundle\Util\ElasticaFactory;
use KupShop\CatalogElasticBundle\Util\ElasticQueryClient;
use KupShop\GraphQLBundle\ApiAdmin\Annotation\Module;
use KupShop\GraphQLBundle\ApiPublic\Types\Search\AutocompleteProductResultItem;
use KupShop\GraphQLBundle\ApiPublic\Types\Search\AutocompleteResult;
use KupShop\GraphQLBundle\ApiPublic\Types\Search\AutocompleteResultItem;
use KupShop\I18nBundle\Util\PriceConverter;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Contexts;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use TheCodingMachine\GraphQLite\Annotations\Query;
class SearchController
{
private const RESULTS_COUNT = 6;
private const ALLOWED_PRESEARCH_TYPES = [
FulltextElastic::INDEX_PRODUCTS,
FulltextElastic::INDEX_SECTIONS,
FulltextElastic::INDEX_PRODUCERS,
];
public function __construct(
private readonly FulltextElastic $fulltext,
private readonly SystemImageUtils $systemImageUtils,
private readonly ?ElasticaFactory $searchClientFactory,
private readonly ?ElasticQueryClient $elasticQueryClient,
private readonly CurrencyContext $currencyContext,
private readonly PriceConverter $priceConverter,
) {
}
/**
* @param string[] $types
*
* @return \Generator<AutocompleteResult>
*
* @throws FulltextException
*/
#[Query]
#[Module(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH)]
public function autocomplete(string $term, array $types): \Generator
{
$invalidTypes = array_diff($types, $this->fulltext->getIndexTypes());
if ($invalidTypes) {
throw new \Exception('Invalid types:'.implode(', ', $invalidTypes));
}
$multiSearch = $this->searchClientFactory->createMultiSearch();
foreach ($types as $searchTopic) {
$pager = new \Pager();
$pager->setOnPage(self::RESULTS_COUNT);
$pager->setPageNumber(1);
$search = $this->elasticQueryClient->createGenericSearch(
searchTerm: $term,
index: $searchTopic,
pager: $pager,
);
$multiSearch->addSearch($search, $searchTopic);
}
$response = $multiSearch->search();
foreach ($response->getResultSets() as $type => $result) {
$items = array_map(
static fn (Result $r) => array_merge($r->getSource(), ['id' => $r->getId()]),
$result->getResults(),
);
$items = $this->processFulltextItems($type, $items);
yield new AutocompleteResult($type, $items, $result->getTotalHits());
}
}
/**
* @param string[] $types
*
* @return \Generator<AutocompleteResult>
*
* @throws BadRequestHttpException
*/
#[Query]
#[Module(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH)]
public function presearch(array $types): \Generator
{
$dbcfg = \Settings::getDefault();
$config = $dbcfg->fulltext['preload'] ?? [];
if ($invalidTypes = array_diff($types, static::ALLOWED_PRESEARCH_TYPES)) {
throw new BadRequestHttpException('Invalid types: '.implode(', ', $invalidTypes));
}
$multiSearch = $this->searchClientFactory->createMultiSearch();
foreach ($types as $type) {
if (empty($config[$type])) {
continue;
}
$query = new \KupShop\CatalogElasticBundle\Elastica\Query();
$query->setQuery(new Ids($config[$type]));
$search = $this->searchClientFactory->createSearch($type)->setQuery($query);
$multiSearch->addSearch($search, $type);
}
if (empty($multiSearch->getSearches())) {
return yield from [];
}
$resultSet = $multiSearch->search();
foreach ($resultSet->getResultSets() as $type => $result) {
$items = $this->processFulltextItems($type, array_map(fn (Result $x) => ['id' => $x->getId(), ...$x->getSource()], $result->getResults()));
yield new AutocompleteResult($type, $items, 0);
}
}
protected function processFulltextItems(string $type, array $items)
{
$cfg = \Settings::getDefault();
$lang = Contexts::get(LanguageContext::class)->getActiveId();
$defaultImagePath = $this->systemImageUtils->getSystemImagePlaceholderPath();
if (!str_starts_with($defaultImagePath, '/')) {
$defaultImagePath = '/'.$defaultImagePath;
}
if (!is_array($cfg['Photo']['placeholder'] ?? null)) {
$placeholder = getVal('placeholder', $cfg['Photo'], $defaultImagePath);
}
if (empty($placeholder)) {
$placeholder = $cfg['Photo']['placeholder']['lang'][$lang] ?? $cfg['Photo']['placeholder']['default'] ?? $defaultImagePath;
}
if ($type == FulltextElastic::INDEX_PRODUCTS) {
foreach ($items as $item) {
yield new AutocompleteProductResultItem($type, $item, $placeholder, $this->currencyContext, $this->priceConverter);
}
return;
}
foreach ($items as $item) {
yield new AutocompleteResultItem($type, $item, $placeholder);
}
}
}