em = $em; $this->providers = $providers; $this->paginator = $paginator; } public function getResults(string $pattern, array $types, array $parameters): Collection { $queries = $this->findQueries($pattern, $types, $parameters); if (0 === count($queries)) { throw new SearchApiNoQueryException($pattern, $types, $parameters); } $total = $this->countItems($queries); $paginator = $this->paginator->create($total); $rawResults = $this->fetchRawResult($queries, $types, $paginator); $this->prepareProviders($rawResults); $results = $this->buildResults($rawResults); return new Collection($results, $paginator); } private function buildCountQuery(array $queries): array { $query = 'SELECT SUM(c) AS count FROM ({union_unordered}) AS sq'; $unions = []; $parameters = []; foreach ($queries as $q) { $unions[] = $q->buildQuery(true); $parameters = array_merge($parameters, $q->buildParameters(true)); } $unionUnordered = implode(' UNION ', $unions); return [ strtr($query, ['{union_unordered}' => $unionUnordered]), $parameters, ]; } private function buildResults(array $rawResults): array { $items = []; foreach ($rawResults as $r) { foreach ($this->providers as $p) { if ($p->supportsResult($r['key'], $r['metadata'])) { $items[] = (new SearchApiResult($r['pertinence'])) ->setResult( $p->getResult($r['key'], $r['metadata'], $r['pertinence']) ); break; } } } return $items; } private function buildUnionQuery(array $queries, PaginatorInterface $paginator): array { $query = '{unions} ORDER BY pertinence DESC LIMIT ? OFFSET ?'; $unions = []; $parameters = []; foreach ($queries as $q) { $unions[] = $q->buildQuery(); $parameters = array_merge($parameters, $q->buildParameters()); } // add pagination limit $parameters[] = $paginator->getItemsPerPage(); $parameters[] = $paginator->getCurrentPageFirstItemNumber(); $union = implode(' UNION ', $unions); return [ strtr($query, ['{unions}' => $union]), $parameters, ]; } private function countItems($providers): int { [$countQuery, $parameters] = $this->buildCountQuery($providers); $rsmCount = new ResultSetMappingBuilder($this->em); $rsmCount->addScalarResult('count', 'count'); $countNq = $this->em->createNativeQuery($countQuery, $rsmCount); $countNq->setParameters($parameters); return (int) $countNq->getSingleScalarResult(); } private function fetchRawResult($queries, $types, PaginatorInterface $paginator): array { [$union, $parameters] = $this->buildUnionQuery($queries, $paginator); $rsm = new ResultSetMappingBuilder($this->em); $rsm->addScalarResult('key', 'key', Types::STRING) ->addScalarResult('metadata', 'metadata', Types::JSON) ->addScalarResult('pertinence', 'pertinence', Types::FLOAT); $nq = $this->em->createNativeQuery($union, $rsm); $nq->setParameters($parameters); return $nq->getResult(); } private function findProviders(string $pattern, array $types, array $parameters): array { $providers = []; foreach ($this->providers as $provider) { if ($provider->supportsTypes($pattern, $types, $parameters)) { $providers[] = $provider; } } return $providers; } private function findQueries($pattern, array $types, array $parameters): array { return array_map( static fn ($p) => $p->provideQuery($pattern, $parameters), $this->findProviders($pattern, $types, $parameters), ); } private function prepareProviders(array $rawResults): void { $metadatas = []; $providers = []; foreach ($rawResults as $r) { foreach ($this->providers as $k => $p) { if ($p->supportsResult($r['key'], $r['metadata'])) { $metadatas[$k][] = $r['metadata']; $providers[$k] = $p; break; } } } foreach ($metadatas as $k => $m) { $providers[$k]->prepare($m); } } }