- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
225 lines
7.3 KiB
PHP
225 lines
7.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Application\Search;
|
|
|
|
use App\Framework\Exception\ErrorCode;
|
|
use App\Framework\Exception\FrameworkException;
|
|
use App\Framework\Http\Request;
|
|
|
|
/**
|
|
* Represents a search request with validation
|
|
*/
|
|
final readonly class SearchRequest
|
|
{
|
|
/**
|
|
* @param array<string, array> $filters
|
|
* @param array<string, float> $boosts
|
|
* @param array<string> $fields
|
|
* @param array<string> $highlight
|
|
*/
|
|
public function __construct(
|
|
public string $query,
|
|
public array $filters = [],
|
|
public array $boosts = [],
|
|
public array $fields = [],
|
|
public array $highlight = [],
|
|
public int $limit = 20,
|
|
public int $offset = 0,
|
|
public ?string $sortBy = null,
|
|
public string $sortDirection = 'asc',
|
|
public bool $sortByRelevance = true,
|
|
public bool $enableHighlighting = true,
|
|
public bool $enableFuzzy = false,
|
|
public float $minScore = 0.0
|
|
) {
|
|
}
|
|
|
|
public static function fromHttpRequest(Request $request): self
|
|
{
|
|
$query = $request->query;
|
|
|
|
// Parse search query
|
|
$searchQuery = $query->get('q', '*');
|
|
|
|
// Parse filters
|
|
$filters = [];
|
|
if ($query->has('filters')) {
|
|
$filtersParam = $query->get('filters');
|
|
if (is_string($filtersParam)) {
|
|
$filters = json_decode($filtersParam, true) ?? [];
|
|
} elseif (is_array($filtersParam)) {
|
|
$filters = $filtersParam;
|
|
}
|
|
}
|
|
|
|
// Parse individual filter parameters (filter[field][type]=value)
|
|
foreach ($query->toArray() as $key => $value) {
|
|
if (preg_match('/^filter\[([^]]+)](?:\[([^]]+)])?$/', $key, $matches)) {
|
|
$field = $matches[1];
|
|
$type = $matches[2] ?? 'equals';
|
|
|
|
if (! isset($filters[$field])) {
|
|
$filters[$field] = [];
|
|
}
|
|
|
|
$filters[$field]['type'] = $type;
|
|
$filters[$field]['value'] = $value;
|
|
}
|
|
}
|
|
|
|
// Parse boosts
|
|
$boosts = [];
|
|
if ($query->has('boosts')) {
|
|
$boostsParam = $query->get('boosts');
|
|
if (is_string($boostsParam)) {
|
|
$boosts = json_decode($boostsParam, true) ?? [];
|
|
} elseif (is_array($boostsParam)) {
|
|
$boosts = $boostsParam;
|
|
}
|
|
}
|
|
|
|
// Parse fields restriction
|
|
$fields = [];
|
|
if ($query->has('fields')) {
|
|
$fieldsParam = $query->get('fields');
|
|
if (is_string($fieldsParam)) {
|
|
$fields = array_map('trim', explode(',', $fieldsParam));
|
|
} elseif (is_array($fieldsParam)) {
|
|
$fields = $fieldsParam;
|
|
}
|
|
}
|
|
|
|
// Parse highlight fields
|
|
$highlight = [];
|
|
if ($query->has('highlight')) {
|
|
$highlightParam = $query->get('highlight');
|
|
if (is_string($highlightParam)) {
|
|
$highlight = array_map('trim', explode(',', $highlightParam));
|
|
} elseif (is_array($highlightParam)) {
|
|
$highlight = $highlightParam;
|
|
}
|
|
} elseif ($query->getBool('enable_highlighting', true)) {
|
|
// Default highlighting for text fields
|
|
$highlight = ['title', 'content', 'description'];
|
|
}
|
|
|
|
// Parse pagination
|
|
$limit = min(100, max(1, $query->getInt('limit', 20)));
|
|
$offset = max(0, $query->getInt('offset', 0));
|
|
|
|
// Alternative pagination via page parameter
|
|
if ($query->has('page')) {
|
|
$page = max(1, $query->getInt('page', 1));
|
|
$perPage = min(100, max(1, $query->getInt('per_page', 20)));
|
|
$offset = ($page - 1) * $perPage;
|
|
$limit = $perPage;
|
|
}
|
|
|
|
// Parse sorting
|
|
$sortBy = $query->get('sort_by');
|
|
$sortDirection = strtolower($query->get('sort_direction', 'asc'));
|
|
$sortByRelevance = $query->getBool('sort_by_relevance', ! $sortBy);
|
|
|
|
// Validate sort direction
|
|
if (! in_array($sortDirection, ['asc', 'desc'])) {
|
|
throw FrameworkException::create(
|
|
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
|
'Invalid sort direction. Must be "asc" or "desc"'
|
|
);
|
|
}
|
|
|
|
// Parse advanced options
|
|
$enableHighlighting = $query->getBool('enable_highlighting', true);
|
|
$enableFuzzy = $query->getBool('enable_fuzzy', false);
|
|
$minScore = max(0.0, $query->getFloat('min_score', 0.0));
|
|
|
|
return new self(
|
|
query: $searchQuery,
|
|
filters: $filters,
|
|
boosts: $boosts,
|
|
fields: $fields,
|
|
highlight: $highlight,
|
|
limit: $limit,
|
|
offset: $offset,
|
|
sortBy: $sortBy,
|
|
sortDirection: $sortDirection,
|
|
sortByRelevance: $sortByRelevance,
|
|
enableHighlighting: $enableHighlighting,
|
|
enableFuzzy: $enableFuzzy,
|
|
minScore: $minScore
|
|
);
|
|
}
|
|
|
|
public function validate(): void
|
|
{
|
|
if (empty(trim($this->query))) {
|
|
throw FrameworkException::create(
|
|
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
|
'Search query cannot be empty'
|
|
);
|
|
}
|
|
|
|
if ($this->limit < 1 || $this->limit > 100) {
|
|
throw FrameworkException::create(
|
|
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
|
'Limit must be between 1 and 100'
|
|
);
|
|
}
|
|
|
|
if ($this->offset < 0) {
|
|
throw FrameworkException::create(
|
|
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
|
'Offset cannot be negative'
|
|
);
|
|
}
|
|
|
|
if ($this->minScore < 0) {
|
|
throw FrameworkException::create(
|
|
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
|
'Minimum score cannot be negative'
|
|
);
|
|
}
|
|
|
|
// Validate filters
|
|
foreach ($this->filters as $field => $filterData) {
|
|
if (! is_array($filterData) || ! isset($filterData['type'])) {
|
|
throw FrameworkException::create(
|
|
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
|
"Invalid filter format for field '{$field}'"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Validate boosts
|
|
foreach ($this->boosts as $field => $boost) {
|
|
if (! is_numeric($boost) || $boost < 0) {
|
|
throw FrameworkException::create(
|
|
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
|
"Invalid boost value for field '{$field}'. Must be a positive number"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'query' => $this->query,
|
|
'filters' => $this->filters,
|
|
'boosts' => $this->boosts,
|
|
'fields' => $this->fields,
|
|
'highlight' => $this->highlight,
|
|
'limit' => $this->limit,
|
|
'offset' => $this->offset,
|
|
'sort_by' => $this->sortBy,
|
|
'sort_direction' => $this->sortDirection,
|
|
'sort_by_relevance' => $this->sortByRelevance,
|
|
'enable_highlighting' => $this->enableHighlighting,
|
|
'enable_fuzzy' => $this->enableFuzzy,
|
|
'min_score' => $this->minScore,
|
|
];
|
|
}
|
|
}
|