Search with Filters
This example demonstrates how to create a search with multiple filters, similar to what you might use in an e-commerce product search.
Product Search with Multiple Filters
<?php
use Zvonchuk\Elastic\Client;
use Zvonchuk\Elastic\Core\SearchRequest;
use Zvonchuk\Elastic\Search\Builder\SearchSourceBuilder;
use Zvonchuk\Elastic\Query\QueryBuilders;
use Zvonchuk\Elastic\Search\Sort\SortBuilders;
$client = Client::getInstance(['localhost:9200']);
// User input parameters
$searchTerm = 'laptop';
$category = 'electronics';
$minPrice = 500;
$maxPrice = 2000;
$brands = ['Apple', 'Dell', 'HP'];
$inStock = true;
$page = 1;
$perPage = 20;
// Create bool query for filtering
$boolQuery = QueryBuilders::boolQuery();
// Add full-text search if a search term is provided
if (!empty($searchTerm)) {
$boolQuery->must(
QueryBuilders::matchQuery('name', $searchTerm)
->operator('OR')
->fuzziness('AUTO')
);
}
// Add category filter
if (!empty($category)) {
$boolQuery->filter(
QueryBuilders::termQuery('category', $category)
);
}
// Add price range filter
if ($minPrice > 0 || $maxPrice > 0) {
$rangeQuery = QueryBuilders::rangeQuery('price');
if ($minPrice > 0) {
$rangeQuery->gte((string)$minPrice);
}
if ($maxPrice > 0) {
$rangeQuery->lte((string)$maxPrice);
}
$boolQuery->filter($rangeQuery);
}
// Add brands filter (if any brands are selected)
if (!empty($brands)) {
$boolQuery->filter(
QueryBuilders::termsQuery('brand', $brands)
);
}
// Add in-stock filter
if ($inStock) {
$boolQuery->filter(
QueryBuilders::termQuery('in_stock', true)
);
}
// Set up the search source with pagination
$from = ($page - 1) * $perPage;
$searchSource = new SearchSourceBuilder();
$searchSource->query($boolQuery);
$searchSource->from($from);
$searchSource->size($perPage);
// Add sorting (relevance if search term provided, otherwise price)
if (!empty($searchTerm)) {
// Default sort is by relevance (_score)
} else {
$searchSource->sort(
SortBuilders::fieldSort('price')->order('asc')
);
}
// Execute the search
$request = new SearchRequest('products');
$request->source($searchSource);
$response = $client->search($request);
// Process results
$total = $response->getTotal();
$hits = $response->getHits();
echo "Found $total products matching your criteria.\n";
echo "Showing " . count($hits) . " products (page $page):\n\n";
foreach ($hits as $hit) {
$product = $hit['_source'];
echo "- {$product['name']}\n";
echo " Brand: {$product['brand']}\n";
echo " Price: \${$product['price']}\n";
echo " In Stock: " . ($product['in_stock'] ? "Yes" : "No") . "\n";
if (isset($hit['_score'])) {
echo " Relevance Score: {$hit['_score']}\n";
}
echo "\n";
}
Faceted Navigation with Filters
This example extends the previous one by adding aggregations to build faceted navigation:
<?php
use Zvonchuk\Elastic\Client;
use Zvonchuk\Elastic\Core\SearchRequest;
use Zvonchuk\Elastic\Search\Builder\SearchSourceBuilder;
use Zvonchuk\Elastic\Query\QueryBuilders;
use Zvonchuk\Elastic\Search\Aggregations\AggregationBuilders;
$client = Client::getInstance(['localhost:9200']);
// User input parameters
$searchTerm = 'laptop';
$selectedCategory = 'electronics';
$selectedBrands = ['Apple']; // Currently selected brands
$minPrice = 500;
$maxPrice = 2000;
// Build the main query
$boolQuery = QueryBuilders::boolQuery();
// Full-text search
if (!empty($searchTerm)) {
$boolQuery->must(
QueryBuilders::matchQuery('name', $searchTerm)
->operator('OR')
);
}
// Category filter
if (!empty($selectedCategory)) {
$boolQuery->filter(
QueryBuilders::termQuery('category', $selectedCategory)
);
}
// Brand filter
if (!empty($selectedBrands)) {
$boolQuery->filter(
QueryBuilders::termsQuery('brand', $selectedBrands)
);
}
// Price range filter
$rangeQuery = QueryBuilders::rangeQuery('price');
if ($minPrice > 0) {
$rangeQuery->gte((string)$minPrice);
}
if ($maxPrice > 0) {
$rangeQuery->lte((string)$maxPrice);
}
$boolQuery->filter($rangeQuery);
// Set up search
$searchSource = new SearchSourceBuilder();
$searchSource->query($boolQuery);
$searchSource->size(20); // Product results to show
// Aggregations for faceted navigation
// 1. Categories aggregation
$searchSource->aggregation(
AggregationBuilders::terms('categories')
->field('category')
->size(10)
);
// 2. Brands aggregation
$searchSource->aggregation(
AggregationBuilders::terms('brands')
->field('brand')
->size(20)
);
// 3. Price ranges aggregation
$searchSource->aggregation(
AggregationBuilders::histogram('price_ranges')
->field('price')
->interval(500) // $500 intervals
->minDocCount(1)
);
// 4. Average price aggregation
$searchSource->aggregation(
AggregationBuilders::avg('avg_price')
->field('price')
);
// Execute the search
$request = new SearchRequest('products');
$request->source($searchSource);
$response = $client->search($request);
// Process results
$hits = $response->getHits();
$total = $response->getTotal();
$aggregations = $response->getAggregations();
// Display product results
echo "Found $total products matching your criteria.\n\n";
foreach ($hits as $hit) {
$product = $hit['_source'];
echo "- {$product['name']} (\${$product['price']})\n";
}
// Display facets for further filtering
// 1. Categories facet
echo "\nCategories:\n";
foreach ($aggregations['categories']['buckets'] as $bucket) {
$isSelected = ($bucket['key'] === $selectedCategory) ? ' (selected)' : '';
echo "- {$bucket['key']}: {$bucket['doc_count']}$isSelected\n";
}
// 2. Brands facet
echo "\nBrands:\n";
foreach ($aggregations['brands']['buckets'] as $bucket) {
$isSelected = in_array($bucket['key'], $selectedBrands) ? ' (selected)' : '';
echo "- {$bucket['key']}: {$bucket['doc_count']}$isSelected\n";
}
// 3. Price ranges facet
echo "\nPrice Ranges:\n";
foreach ($aggregations['price_ranges']['buckets'] as $bucket) {
$min = $bucket['key'];
$max = $bucket['key'] + 500;
echo "- \$$min - \$$max: {$bucket['doc_count']}\n";
}
// 4. Average price
$avgPrice = $aggregations['avg_price']['value'];
echo "\nAverage Price: \$" . number_format($avgPrice, 2) . "\n";