$filters * @param array $boosts * @param array $fields * @param array $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, ]; } }