em = $em; $this->providers[] = $searchPerson; $this->providers[] = $thirdPartyApiSearch; $this->providers[] = $searchUser; $this->paginator = $paginator; } /** * @return Model/Result[] */ 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, $types, $parameters); $paginator = $this->paginator->create($total); $rawResults = $this->fetchRawResult($queries, $types, $parameters, $paginator); $this->prepareProviders($rawResults); $results = $this->buildResults($rawResults); return new Collection($results, $paginator); } private function findQueries($pattern, array $types, array $parameters): array { return \array_map( fn($p) => $p->provideQuery($pattern, $parameters), $this->findProviders($pattern, $types, $parameters), ); } private function findProviders(string $pattern, array $types, array $parameters): array { return \array_filter( $this->providers, fn($p) => $p->supportsTypes($pattern, $types, $parameters) ); } private function countItems($providers, $types, $parameters): int { list($countQuery, $parameters) = $this->buildCountQuery($providers, $types, $parameters); $rsmCount = new ResultSetMappingBuilder($this->em); $rsmCount->addScalarResult('count', 'count'); $countNq = $this->em->createNativeQuery($countQuery, $rsmCount); $countNq->setParameters($parameters); return $countNq->getSingleScalarResult(); } private function buildCountQuery(array $queries, $types, $parameters) { $query = "SELECT COUNT(sq.key) AS count FROM ({union_unordered}) AS sq"; $unions = []; $parameters = []; foreach ($queries as $q) { $unions[] = $q->buildQuery(); $parameters = \array_merge($parameters, $q->buildParameters()); } $unionUnordered = \implode(" UNION ", $unions); return [ \strtr($query, [ '{union_unordered}' => $unionUnordered ]), $parameters ]; } private function buildUnionQuery(array $queries, $types, $parameters) { $query = "{unions} ORDER BY pertinence DESC"; $unions = []; $parameters = []; foreach ($queries as $q) { $unions[] = $q->buildQuery(); $parameters = \array_merge($parameters, $q->buildParameters()); } $union = \implode(" UNION ", $unions); return [ \strtr($query, [ '{unions}' => $union]), $parameters ]; } private function fetchRawResult($queries, $types, $parameters, $paginator): array { list($union, $parameters) = $this->buildUnionQuery($queries, $types, $parameters, $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 prepareProviders(array $rawResults) { $metadatas = []; foreach ($rawResults as $r) { foreach ($this->providers as $k => $p) { if ($p->supportsResult($r['key'], $r['metadata'])) { $metadatas[$k][] = $r['metadata']; break; } } } foreach ($metadatas as $k => $m) { $this->providers[$k]->prepare($m); } } private function buildResults(array $rawResults): array { $items = []; foreach ($rawResults as $r) { foreach ($this->providers as $k => $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; } }