$taxonomyTermIds Filter by these taxonomy term IDs * @param bool $includeChildTerms Include child terms in filter (recursive) * @param int $limit Maximum results * @return array{ * results: array>, * taxonomy_filter: array{term_ids: array, chunk_ids: array}, * total_before_filter: int, * total_after_filter: int * } */ public function execute( string $query, array $taxonomyTermIds = [], bool $includeChildTerms = false, int $limit = 10 ): array { // If no taxonomy filter, delegate directly to search service if (empty($taxonomyTermIds)) { $results = $this->searchService->search($query, [], $limit); return [ 'results' => $results, 'taxonomy_filter' => ['term_ids' => [], 'chunk_ids' => []], 'total_before_filter' => count($results), 'total_after_filter' => count($results), ]; } // Expand term IDs if includeChildTerms is true $effectiveTermIds = $includeChildTerms ? $this->expandWithChildTerms($taxonomyTermIds) : $taxonomyTermIds; // Get chunk IDs that match the taxonomy filter $filteredChunkIds = $this->getChunkIdsForTerms($effectiveTermIds); if (empty($filteredChunkIds)) { return [ 'results' => [], 'taxonomy_filter' => ['term_ids' => $effectiveTermIds, 'chunk_ids' => []], 'total_before_filter' => 0, 'total_after_filter' => 0, ]; } // Perform search with higher limit to allow for filtering $searchResults = $this->searchService->search($query, [], $limit * 5); $totalBeforeFilter = count($searchResults); // Filter results to only include chunks matching taxonomy $filteredResults = array_filter( $searchResults, static fn (array $result): bool => in_array($result['chunk_id'] ?? 0, $filteredChunkIds, true) ); // Re-index and limit $filteredResults = array_values($filteredResults); $limitedResults = array_slice($filteredResults, 0, $limit); return [ 'results' => $limitedResults, 'taxonomy_filter' => [ 'term_ids' => $effectiveTermIds, 'chunk_ids' => $filteredChunkIds, ], 'total_before_filter' => $totalBeforeFilter, 'total_after_filter' => count($filteredResults), ]; } /** * Search within a specific taxonomy term and its children. * * @return array> */ public function searchInTaxonomy(string $query, int $termId, int $limit = 10): array { $result = $this->execute($query, [$termId], true, $limit); return $result['results']; } /** * Get search suggestions based on taxonomy structure. * * @return array{ * terms: array, * suggested_filters: array * } */ public function getSuggestionsForQuery(string $query): array { // First, get search results without filter $results = $this->searchService->search($query, [], 20); if (empty($results)) { return ['terms' => [], 'suggested_filters' => []]; } // Extract chunk IDs from results $chunkIds = array_map( static fn (array $r): int => (int) ($r['chunk_id'] ?? 0), $results ); $chunkIds = array_filter($chunkIds); // Find common taxonomy terms for these chunks $termCounts = []; foreach ($chunkIds as $chunkId) { $mappings = $this->chunkTaxonomyRepository->findByChunkId($chunkId); foreach ($mappings as $mapping) { $termId = $mapping->getTaxonomyTermId(); if (!isset($termCounts[$termId])) { $termCounts[$termId] = 0; } $termCounts[$termId]++; } } // Sort by count and get top terms arsort($termCounts); $topTermIds = array_slice(array_keys($termCounts), 0, 5); // Get term details $terms = []; foreach ($topTermIds as $termId) { $term = $this->taxonomyRepository->find($termId); if ($term !== null) { $terms[] = [ 'id' => $termId, 'name' => $term['name'], 'chunk_count' => $termCounts[$termId], ]; } } return [ 'terms' => $terms, 'suggested_filters' => $topTermIds, ]; } /** * Expand term IDs to include all child terms. * * @param array $termIds * @return array */ private function expandWithChildTerms(array $termIds): array { $allTerms = $this->taxonomyRepository->findAll(); $expanded = $termIds; // Build parent-child map $childrenMap = []; foreach ($allTerms as $term) { $parentId = $term['parent_id']; if ($parentId !== null) { if (!isset($childrenMap[$parentId])) { $childrenMap[$parentId] = []; } $childrenMap[$parentId][] = $term['id']; } } // Recursively add children $queue = $termIds; while (!empty($queue)) { $currentId = array_shift($queue); if (isset($childrenMap[$currentId])) { foreach ($childrenMap[$currentId] as $childId) { if (!in_array($childId, $expanded, true)) { $expanded[] = $childId; $queue[] = $childId; } } } } return $expanded; } /** * Get chunk IDs that have mappings to any of the given taxonomy terms. * * @param array $termIds * @return array */ private function getChunkIdsForTerms(array $termIds): array { $chunkIds = []; foreach ($termIds as $termId) { $mappings = $this->chunkTaxonomyRepository->findByTaxonomyTermId($termId); foreach ($mappings as $mapping) { $chunkId = (int) $mapping['chunk_id']; if ($chunkId > 0 && !in_array($chunkId, $chunkIds, true)) { $chunkIds[] = $chunkId; } } } return $chunkIds; } }