Search & filters

  1. Basic search
  2. Reading hits
  3. Filters
    1. Filtering by type
    2. Fetching a single document
  4. Sorting
  5. Presets and variables
  6. Facets
  7. Extensions

BrowseService powers search result pages and category listings: full text search, filters, sorting, pagination, facets and merchandising extensions in one request.

use BatteryIncludedSdk\Shop\BrowseSearchStruct;
use BatteryIncludedSdk\Shop\BrowseService;

$searchStruct = new BrowseSearchStruct();
$searchStruct->setQuery('smartphone');
$searchStruct->setLocale('en');
$searchStruct->setPerPage(24);   // default 10
$searchStruct->setPage(2);       // default 1

$result = (new BrowseService($apiClient))->browse($searchStruct);

$result->getFound();  // total number of hits
$result->getPage();   // current page
$result->getPages();  // number of pages

Without a query (or with *) all documents are returned, which is what you want for category pages.

Reading hits

foreach ($result->getHits() as $hit) {
    $document = $hit['document'];

    match ($document['type']) {
        'PRODUCT' => printf("%s – %.2f €\n", $document['_PRODUCT']['name'], $document['_PRODUCT']['price']),
        'BLOG' => printf("%s by %s\n", $document['_BLOG']['title'], $document['_BLOG']['author']),
    };
}

Each hit also contains highlight with the matched tokens of the query. See translations for how getHits() and getRawHits() differ.

Filters

// several values of one field are combined with OR
$searchStruct->addFilter('_PRODUCT.properties.Color', 'Black');
$searchStruct->addFilter('_PRODUCT.properties.Color', 'Blue');

// different fields are combined with AND
$searchStruct->addFilter('_PRODUCT.properties.Storage', '512GB');

// a category filter includes all subcategories
$searchStruct->addFilter('_PRODUCT.categories', 'Electronics > TVs');

// ranges
$searchStruct->addRangeFilter('_PRODUCT.price', '100', '500');

// filters from a request, e.g. ?f[_PRODUCT.properties.Color][]=Black&f[_PRODUCT.price][from]=100
$searchStruct->addFilters($_GET['f'] ?? []);

Filtering by type

A collection can mix products and blog posts. Restrict the result with the type filter:

$searchStruct->addFilter('type', 'PRODUCT');

Fetching a single document

Filter by the document id to load one document, e.g. for a detail page:

$searchStruct = new BrowseSearchStruct();
$searchStruct->addFilter('id', 'PRODUCT-1002');
$searchStruct->setPerPage(1);

$document = (new BrowseService($apiClient))->browse($searchStruct)->getHits()[0]['document'] ?? null;

Sorting

$searchStruct->setSort('_PRODUCT.price:asc');
$searchStruct->setSort('_PRODUCT.rating:desc');

Without sort the result is ordered by relevance.

Presets and variables

A preset bundles search settings that are maintained in the backend (e.g. ranking rules for a landing page):

$searchStruct->setPresetId('857e117c-3766-494d-a692-d7a23c384c33');

List the available presets with the PresetsService.

Variables are passed to the gateway as v[name]. locale is set by setLocale(), others are defined in your collection:

$searchStruct->addVariable('currency', 'EUR');

Facets

Facets exist only for fields that are configured as filters in the backend. getFacets() turns the facet counts into typed objects. Pass the field that holds your category tree, e.g. _PRODUCT_i18n.categories for translated categories:

use BatteryIncludedSdk\Shop\FacetCategoryDto;
use BatteryIncludedSdk\Shop\FacetRangeDto;
use BatteryIncludedSdk\Shop\FacetRatingDto;
use BatteryIncludedSdk\Shop\FacetSelectDto;

foreach ($result->getFacets('_PRODUCT.categories') as $facet) {
    echo $facet->getFieldLabel();   // label from the backend, e.g. "Color"
    $facet->getFieldName();         // filter key, e.g. "_PRODUCT.properties.Color"
    $facet->getFieldUnit();         // e.g. "€"

    if ($facet instanceof FacetSelectDto) {
        foreach ($facet->getValues() as $value) {
            printf("[%s] %s (%d)\n", $value->isChecked() ? 'x' : ' ', $value->getName(), $value->getCount());
        }
    } elseif ($facet instanceof FacetRangeDto) {
        printf("%s – %s, selected %s – %s\n", $facet->getMin(), $facet->getMax(), $facet->getSelectedMin(), $facet->getSelectedMax());
    } elseif ($facet instanceof FacetRatingDto) {
        $facet->getRatings();       // FacetValueDto[] per star rating
    } elseif ($facet instanceof FacetCategoryDto) {
        $facet->getCategories();    // nested tree: ['Electronics' => ['count' => 12, 'checked' => true, 'childs' => [...]]]
    }
}

The checked state reflects the filters of the search struct, so you can render the sidebar directly from the response. Facets without counts for the current result are skipped, the api delivers them without a type.

Extensions

Merchandising rules configured in the backend are delivered as extensions:

Method Content
getTeaserExtensions() Banners to place between the hits (position, image, url)
getPromotionsExtensions() Banners above the result
getRedirectsExtensions() A url to redirect to, e.g. for the query “contact”
getCodesExtensions() A promotion code that matched the query
getAllExtensions() All of the above
foreach ($result->getRedirectsExtensions() as $redirect) {
    header('Location: ' . $redirect->getData()['url']);
    exit;
}