Príklad: vlastná výsledková stránka
Výsledková stránka s facetmi, radením a stránkovaním nad Search API. Volanie prebieha zo servera, takže verejný kľúč nemusí opustiť backend a odpoveď sa dá cachovať.
Backend
final class SearchController
{
private const PER_PAGE = 24;
public function __construct(private readonly MettyClient $metty) {}
public function __invoke(Request $request): Response
{
$query = SearchQuery::for($request->query->get('q', ''))
->perPage(self::PER_PAGE)
->page(max(1, $request->query->getInt('page', 1)))
->withSections('facets', 'categories', 'suggestions');
if ($sort = $request->query->get('sort')) {
$query = $query->sortBy($sort);
}
if ($category = $request->query->get('category')) {
$query = $query->category($category);
}
foreach ($request->query->all('filter') as $field => $values) {
foreach ((array) $values as $value) {
$query = $query->facet($field, $value);
}
}
try {
$response = $this->metty->search()->search($query);
} catch (ConfigurationException) {
return new RedirectResponse($this->urlFor(['q' => $request->query->get('q', '')]));
}
return $this->render('search.html.twig', ['results' => $response]);
}
}ConfigurationException zachytávame preto, že požiadavka za hranicou 200 výsledkov je neplatná. Namiesto chyby je používateľsky prijateľnejšie vrátiť sa na prvú stranu.
Zvýraznenie zhody
Zhodu označuje server hranatými zátvorkami v poli highlight. Na klientovi ich stačí nahradiť značkou a nič nedopočítavať:
function highlight(?string $marked, string $fallback): string
{
if ($marked === null) {
return htmlspecialchars($fallback);
}
$escaped = htmlspecialchars($marked);
return str_replace(['[', ']'], ['<mark>', '</mark>'], $escaped);
}<h3>{{ highlight(product.highlight.name ?? null, product.name)|raw }}</h3>Escapovanie musí prebehnúť pred nahradením zátvoriek, inak by sa do stránky dostalo HTML z názvu produktu.
Facety a filtre
facets je zoznam polí a každé nesie strojový názov aj názov na zobrazenie:
{% for facet in results.facets %}
<fieldset>
<legend>{{ facet.label }}</legend>
{% for value in facet.values %}
<label>
<input type="checkbox" name="filter[{{ facet.field }}][]" value="{{ value.value }}"
{{ value.value in active[facet.field]|default([]) ? 'checked' }}>
{{ value.value }} <span>({{ value.count }})</span>
</label>
{% endfor %}
</fieldset>
{% endfor %}Viac hodnôt jedného poľa znamená ALEBO, hodnoty rôznych polí sa kombinujú cez A ZÁROVEŇ. Počty v odpovedi sú prepočítané podľa aktívnych filtrov, takže sa dajú zobraziť priamo.
Stránkovanie
$lastPage = min(
$response->pages,
intdiv(SearchQuery::MAX_WINDOW, $response->perPage),
);Ranguje sa prvých 200 výsledkov, preto je posledná dostupná strana daná touto hranicou, nie celkovým počtom nájdených produktov. Ak potrebujete zákazníkovi ponúknuť viac, zúžte výber kategóriou alebo facetom — hlbšie stránkovanie by aj tak vrátilo neusporiadané poradie.
Volanie priamo z prehliadača
Verejný kľúč je určený na čítanie, takže Search API sa dá volať aj z frontendu:
const params = new URLSearchParams({
key: PUBLIC_API_KEY,
q: query,
per_page: '24',
include: 'facets,categories'
})
for (const [field, values] of Object.entries(filters)) {
for (const value of values) {
params.append(`${field}[]`, value)
}
}
const response = await fetch(`https://search.api.metty.eu/search?${params}`)
if (!response.ok) {
const { error } = await response.json()
throw new Error(error)
}
const { products, total, facets } = await response.json()Opakovanú hodnotu posielajte so zátvorkami (farba[]=modrá&farba[]=čierna). Bez nich si server ponechá iba poslednú hodnotu.
Ak vyhľadávanie voláte pri každom stlačenom znaku, použite radšej našepkávanie — má vyšší rate limit a menšiu odpoveď.