- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
285 lines
5.9 KiB
PHP
285 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Search;
|
|
|
|
/**
|
|
* Fluent builder for search queries
|
|
*/
|
|
final class SearchQueryBuilder
|
|
{
|
|
private string $query = '*';
|
|
|
|
private array $filters = [];
|
|
|
|
private array $boosts = [];
|
|
|
|
private array $fields = [];
|
|
|
|
private array $highlightFields = [];
|
|
|
|
private int $limit = 20;
|
|
|
|
private int $offset = 0;
|
|
|
|
private SearchSortBy $sortBy;
|
|
|
|
private bool $enableHighlighting = true;
|
|
|
|
private bool $enableFuzzyMatching = false;
|
|
|
|
private float $minScore = 0.0;
|
|
|
|
public function __construct(
|
|
private readonly SearchEngine $engine,
|
|
private readonly string $entityType
|
|
) {
|
|
$this->sortBy = SearchSortBy::relevance();
|
|
}
|
|
|
|
/**
|
|
* Set the search query string
|
|
*/
|
|
public function query(string $query): self
|
|
{
|
|
$this->query = $query;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Add a filter
|
|
*/
|
|
public function filter(string $field, SearchFilter $filter): self
|
|
{
|
|
$this->filters[$field] = $filter;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Add multiple filters at once
|
|
*/
|
|
public function filters(array $filters): self
|
|
{
|
|
foreach ($filters as $field => $value) {
|
|
if ($value instanceof SearchFilter) {
|
|
$this->filters[$field] = $value;
|
|
} else {
|
|
$this->filters[$field] = SearchFilter::equals($value);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Add a field boost
|
|
*/
|
|
public function boost(string $field, float $boost): self
|
|
{
|
|
$this->boosts[$field] = $boost;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Restrict search to specific fields
|
|
*/
|
|
public function fields(array $fields): self
|
|
{
|
|
$this->fields = $fields;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set fields to highlight
|
|
*/
|
|
public function highlight(array $fields): self
|
|
{
|
|
$this->highlightFields = $fields;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set result limit
|
|
*/
|
|
public function limit(int $limit): self
|
|
{
|
|
$this->limit = $limit;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set result offset for pagination
|
|
*/
|
|
public function offset(int $offset): self
|
|
{
|
|
$this->offset = $offset;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set pagination (page-based)
|
|
*/
|
|
public function page(int $page, int $perPage = 20): self
|
|
{
|
|
$this->limit = $perPage;
|
|
$this->offset = ($page - 1) * $perPage;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sort by relevance (default)
|
|
*/
|
|
public function sortByRelevance(): self
|
|
{
|
|
$this->sortBy = SearchSortBy::relevance();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sort by a field
|
|
*/
|
|
public function sortBy(string $field, SearchSortDirection $direction = SearchSortDirection::ASC): self
|
|
{
|
|
$this->sortBy = SearchSortBy::field($field, $direction);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sort by multiple fields
|
|
*/
|
|
public function sortByMultiple(SearchSortField ...$fields): self
|
|
{
|
|
$this->sortBy = SearchSortBy::multiple(...$fields);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Enable/disable highlighting
|
|
*/
|
|
public function highlighting(bool $enable): self
|
|
{
|
|
$this->enableHighlighting = $enable;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Enable/disable fuzzy matching
|
|
*/
|
|
public function fuzzy(bool $enable): self
|
|
{
|
|
$this->enableFuzzyMatching = $enable;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set minimum score threshold
|
|
*/
|
|
public function minScore(float $minScore): self
|
|
{
|
|
$this->minScore = $minScore;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Common filter shortcuts
|
|
*/
|
|
public function where(string $field, mixed $value): self
|
|
{
|
|
return $this->filter($field, SearchFilter::equals($value));
|
|
}
|
|
|
|
public function whereIn(string $field, array $values): self
|
|
{
|
|
return $this->filter($field, SearchFilter::in($values));
|
|
}
|
|
|
|
public function whereRange(string $field, mixed $min, mixed $max): self
|
|
{
|
|
return $this->filter($field, SearchFilter::range($min, $max));
|
|
}
|
|
|
|
public function whereGreaterThan(string $field, mixed $value): self
|
|
{
|
|
return $this->filter($field, SearchFilter::greaterThan($value));
|
|
}
|
|
|
|
public function whereLessThan(string $field, mixed $value): self
|
|
{
|
|
return $this->filter($field, SearchFilter::lessThan($value));
|
|
}
|
|
|
|
public function whereContains(string $field, string $value): self
|
|
{
|
|
return $this->filter($field, SearchFilter::contains($value));
|
|
}
|
|
|
|
public function whereStartsWith(string $field, string $value): self
|
|
{
|
|
return $this->filter($field, SearchFilter::startsWith($value));
|
|
}
|
|
|
|
/**
|
|
* Build and return the search query
|
|
*/
|
|
public function build(): SearchQuery
|
|
{
|
|
return new SearchQuery(
|
|
entityType: $this->entityType,
|
|
query: $this->query,
|
|
filters: $this->filters,
|
|
boosts: $this->boosts,
|
|
fields: $this->fields,
|
|
highlightFields: $this->highlightFields,
|
|
limit: $this->limit,
|
|
offset: $this->offset,
|
|
sortBy: $this->sortBy,
|
|
enableHighlighting: $this->enableHighlighting,
|
|
enableFuzzyMatching: $this->enableFuzzyMatching,
|
|
minScore: $this->minScore
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Build and execute the search query
|
|
*/
|
|
public function search(): SearchResult
|
|
{
|
|
return $this->engine->search($this->build());
|
|
}
|
|
|
|
/**
|
|
* Execute search and return only the first result
|
|
*/
|
|
public function first(): ?SearchHit
|
|
{
|
|
$result = $this->limit(1)->search();
|
|
|
|
return $result->hits[0] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Count total results without retrieving them
|
|
*/
|
|
public function count(): int
|
|
{
|
|
$result = $this->limit(0)->search();
|
|
|
|
return $result->total;
|
|
}
|
|
}
|