Skip to content

Commit 40a2e96

Browse files
committed
Add multi search
1 parent ecd40c1 commit 40a2e96

File tree

5 files changed

+350
-5
lines changed

5 files changed

+350
-5
lines changed

Diff for: src/Contracts/SearchQuery.php

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Meilisearch\Bundle\Contracts;
6+
7+
use Meilisearch\Contracts\SearchQuery as EngineQuery;
8+
9+
class SearchQuery
10+
{
11+
/**
12+
* @var class-string
13+
*/
14+
private string $className;
15+
private ?string $q = null;
16+
private ?array $filter = null;
17+
private ?array $attributesToRetrieve = null;
18+
private ?array $attributesToCrop = null;
19+
private ?int $cropLength;
20+
private ?array $attributesToHighlight = null;
21+
private ?string $cropMarker = null;
22+
private ?string $highlightPreTag = null;
23+
private ?string $highlightPostTag = null;
24+
private ?array $facets = null;
25+
private ?bool $showMatchesPosition = null;
26+
private ?array $sort = null;
27+
private ?string $matchingStrategy = null;
28+
private ?int $offset = null;
29+
private ?int $limit = null;
30+
private ?int $hitsPerPage = null;
31+
private ?int $page = null;
32+
private ?array $vector = null;
33+
private ?array $attributesToSearchOn = null;
34+
private ?bool $showRankingScore = null;
35+
private ?bool $showRankingScoreDetails = null;
36+
37+
/**
38+
* @param class-string $className
39+
*/
40+
public function __construct(string $className)
41+
{
42+
$this->className = $className;
43+
}
44+
45+
/**
46+
* @return class-string
47+
*/
48+
public function getClassName(): string
49+
{
50+
return $this->className;
51+
}
52+
53+
public function setQuery(string $q): SearchQuery
54+
{
55+
$this->q = $q;
56+
57+
return $this;
58+
}
59+
60+
public function getQuery(): ?string
61+
{
62+
return $this->q;
63+
}
64+
65+
public function setFilter(array $filter): SearchQuery
66+
{
67+
$this->filter = $filter;
68+
69+
return $this;
70+
}
71+
72+
public function getFilter(): ?array
73+
{
74+
return $this->filter;
75+
}
76+
77+
public function setAttributesToRetrieve(array $attributesToRetrieve): SearchQuery
78+
{
79+
$this->attributesToRetrieve = $attributesToRetrieve;
80+
81+
return $this;
82+
}
83+
84+
public function setAttributesToCrop(array $attributesToCrop): SearchQuery
85+
{
86+
$this->attributesToCrop = $attributesToCrop;
87+
88+
return $this;
89+
}
90+
91+
public function setCropLength(?int $cropLength): SearchQuery
92+
{
93+
$this->cropLength = $cropLength;
94+
95+
return $this;
96+
}
97+
98+
public function setAttributesToHighlight(array $attributesToHighlight): SearchQuery
99+
{
100+
$this->attributesToHighlight = $attributesToHighlight;
101+
102+
return $this;
103+
}
104+
105+
public function setCropMarker(string $cropMarker): SearchQuery
106+
{
107+
$this->cropMarker = $cropMarker;
108+
109+
return $this;
110+
}
111+
112+
public function setHighlightPreTag(string $highlightPreTag): SearchQuery
113+
{
114+
$this->highlightPreTag = $highlightPreTag;
115+
116+
return $this;
117+
}
118+
119+
public function setHighlightPostTag(string $highlightPostTag): SearchQuery
120+
{
121+
$this->highlightPostTag = $highlightPostTag;
122+
123+
return $this;
124+
}
125+
126+
public function setFacets(array $facets): SearchQuery
127+
{
128+
$this->facets = $facets;
129+
130+
return $this;
131+
}
132+
133+
public function setShowMatchesPosition(?bool $showMatchesPosition): SearchQuery
134+
{
135+
$this->showMatchesPosition = $showMatchesPosition;
136+
137+
return $this;
138+
}
139+
140+
public function setShowRankingScore(?bool $showRankingScore): SearchQuery
141+
{
142+
$this->showRankingScore = $showRankingScore;
143+
144+
return $this;
145+
}
146+
147+
/**
148+
* @param bool $showRankingScoreDetails whether the feature is enabled or not
149+
*/
150+
public function setShowRankingScoreDetails(?bool $showRankingScoreDetails): SearchQuery
151+
{
152+
$this->showRankingScoreDetails = $showRankingScoreDetails;
153+
154+
return $this;
155+
}
156+
157+
public function setSort(array $sort): SearchQuery
158+
{
159+
$this->sort = $sort;
160+
161+
return $this;
162+
}
163+
164+
public function setMatchingStrategy(string $matchingStrategy): SearchQuery
165+
{
166+
$this->matchingStrategy = $matchingStrategy;
167+
168+
return $this;
169+
}
170+
171+
public function setOffset(?int $offset): SearchQuery
172+
{
173+
$this->offset = $offset;
174+
175+
return $this;
176+
}
177+
178+
public function setLimit(?int $limit): SearchQuery
179+
{
180+
$this->limit = $limit;
181+
182+
return $this;
183+
}
184+
185+
public function setHitsPerPage(?int $hitsPerPage): SearchQuery
186+
{
187+
$this->hitsPerPage = $hitsPerPage;
188+
189+
return $this;
190+
}
191+
192+
public function setPage(?int $page): SearchQuery
193+
{
194+
$this->page = $page;
195+
196+
return $this;
197+
}
198+
199+
/**
200+
* @param list<float|list<float>> $vector a multi-level array floats
201+
*/
202+
public function setVector(array $vector): SearchQuery
203+
{
204+
$this->vector = $vector;
205+
206+
return $this;
207+
}
208+
209+
/**
210+
* @param list<non-empty-string> $attributesToSearchOn
211+
*/
212+
public function setAttributesToSearchOn(array $attributesToSearchOn): SearchQuery
213+
{
214+
$this->attributesToSearchOn = $attributesToSearchOn;
215+
216+
return $this;
217+
}
218+
219+
/**
220+
* @internal
221+
*/
222+
public function toEngineQuery(string $prefix, array $indices): EngineQuery
223+
{
224+
$query = new EngineQuery();
225+
foreach ($indices as $indice) {
226+
if ($indice['class'] === $this->className) {
227+
$query->setIndexUid("$prefix{$indice['name']}");
228+
229+
break;
230+
}
231+
}
232+
if (null !== $this->q) {
233+
$query->setQuery($this->q);
234+
}
235+
236+
// @todo: set all data
237+
238+
return $query;
239+
}
240+
}

Diff for: src/Engine.php

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Meilisearch\Bundle;
66

77
use Meilisearch\Client;
8+
use Meilisearch\Contracts\SearchQuery;
89
use Meilisearch\Exceptions\ApiException;
910

1011
final class Engine
@@ -133,6 +134,14 @@ public function search(string $query, string $indexUid, array $searchParams): ar
133134
return $this->client->index($indexUid)->rawSearch($query, $searchParams);
134135
}
135136

137+
/**
138+
* @param list<SearchQuery> $queries
139+
*/
140+
public function multiSearch(array $queries): array
141+
{
142+
return $this->client->multiSearch($queries);
143+
}
144+
136145
/**
137146
* Search the index and returns the number of results.
138147
*/

Diff for: src/SearchService.php

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Meilisearch\Bundle;
66

77
use Doctrine\Persistence\ObjectManager;
8+
use Meilisearch\Bundle\Contracts\SearchQuery;
89

910
interface SearchService
1011
{
@@ -60,6 +61,13 @@ public function search(
6061
array $searchParams = []
6162
): array;
6263

64+
/**
65+
* @param list<SearchQuery> $queries
66+
*
67+
* @return array<non-empty-string, list<object>>
68+
*/
69+
public function multiSearch(ObjectManager $objectManager, array $queries): array;
70+
6371
/**
6472
* Get the raw search result.
6573
*

Diff for: src/Services/MeilisearchService.php

+53
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\Common\Util\ClassUtils;
88
use Doctrine\Persistence\ObjectManager;
99
use Meilisearch\Bundle\Collection;
10+
use Meilisearch\Bundle\Contracts\SearchQuery;
1011
use Meilisearch\Bundle\Engine;
1112
use Meilisearch\Bundle\Entity\Aggregator;
1213
use Meilisearch\Bundle\Exception\ObjectIdNotFoundException;
@@ -188,6 +189,42 @@ public function search(
188189
return $results;
189190
}
190191

192+
public function multiSearch(ObjectManager $objectManager, array $queries): array
193+
{
194+
$prefix = $this->configuration->get('prefix');
195+
$indices = $this->configuration->get('indices');
196+
197+
// $response = $this->engine->multiSearch(array_map(function (SearchQuery $query) use ($prefix, $indices) {
198+
// $this->assertIsSearchable($query->getClassName());
199+
//
200+
// return $query->toEngineQuery($prefix, $indices);
201+
// }, $queries));
202+
$response = $this->engine->multiSearch(array_map(static fn (SearchQuery $query) => $query->toEngineQuery($prefix, $indices), $queries));
203+
$results = [];
204+
205+
foreach ($response['results'] as $indexResponse) {
206+
$indexResults = [];
207+
$className = $this->getClassNameFromIndex($indexResponse['indexUid']);
208+
209+
foreach ($indexResponse['hits'] as $hit) {
210+
if (!isset($hit[self::RESULT_KEY_OBJECTID])) {
211+
throw new ObjectIdNotFoundException(sprintf('There is no "%s" key in the multi search "%s" result.', self::RESULT_KEY_OBJECTID, $indexResponse['indexUid']));
212+
}
213+
214+
$repo = $objectManager->getRepository($className);
215+
$entity = $repo->find($hit['objectID']);
216+
217+
if (null !== $entity) {
218+
$indexResults[] = $entity;
219+
}
220+
}
221+
222+
$results[$className] = $indexResults;
223+
}
224+
225+
return $results;
226+
}
227+
191228
public function rawSearch(
192229
string $className,
193230
string $query = '',
@@ -346,4 +383,20 @@ private function assertIsSearchable(string $className): void
346383
throw new Exception('Class '.$className.' is not searchable.');
347384
}
348385
}
386+
387+
/**
388+
* @return class-string
389+
*/
390+
private function getClassNameFromIndex(string $index): string
391+
{
392+
$prefix = $this->configuration->get('prefix');
393+
394+
foreach ($this->configuration->get('indices') as $indice) {
395+
if ("$prefix{$indice['name']}" === $index) {
396+
return $indice['class'];
397+
}
398+
}
399+
400+
throw new Exception(sprintf('Cannot find searchable class for "%s" index.', $index));
401+
}
349402
}

0 commit comments

Comments
 (0)