Files
michaelschiemer/src/Framework/Search/SearchQueryBuilder.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

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;
}
}