Downloads
Stars
Version
This package provides simple facet filtering (sometimes called Faceted Search or Faceted Navigation) in Laravel projects. It helps narrow down query results based on the attributes of your models.
Feel free to contribute to this package, either by creating a pull request or reporting an issue.
This package can be installed through Composer.
composer require mgussekloo/laravel-facet-filter
php artisan vendor:publish --tag="facet-filter-migrations"
php artisan migrate
Add a Facettable trait and a facetDefinitions() method to models that should support facet filtering.
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Mgussekloo\FacetFilter\Traits\Facettable;
class Product extends Model
{
use HasFactory;
use Facettable;
public static function facetDefinitions()
{
// Return an array of definitions
return [
[
'title' => 'Main color', // The title will be used for the parameter.
'fieldname' => 'color' // Model property from which to get the values.
],
[
'title' => 'Sizes',
'fieldname' => 'sizes.name' // Use dot notation to get the value from related models.
]
];
}
}
Before you can start filtering you will have to build an index. There's an Indexer included.
use Mgussekloo\FacetFilter\Indexer;
$products = Product::with(['sizes'])->get(); // get some products
$indexer = new Indexer();
$indexer->resetIndex(); // clears the index
$indexer->buildIndex($products); // process the models
$filter = request()->all(); // use the request parameters
$filter = ['main-color' => ['green']]; // (or provide your own array)
$products = Product::facetsMatchFilter($filter)->get();
$facets = Product::getFacets();
/* You can filter and sort like any regular Laravel collection. */
$singleFacet = $facets->firstWhere('fieldname', 'color');
/* Find out stuff about the facet. */
$paramName = $singleFacet->getParamName(); // "main-color"
$options = $singleFacet->getOptions();
/*
Options look like this:
(object)[
'value' => 'Red',
'selected' => false,
'total' => 3,
'slug' => 'color_red',
'http_query' => 'main-color%5B1%5D=red&sizes%5B0%5D=small'
]
*/
Here's a simple demo project that demonstrates a basic frontend.
<div class="flex">
<div class="w-1/4 flex-0">
@foreach ($facets as $facet)
<p>
<h3>{{ $facet->title }}</h3>
@foreach ($facet->getOptions() as $option)
<a href="?{{ $option->http_query }}" class="{{ $option->selected ? 'underline' : '' }}">{{ $option->value }} ({{ $option->total }}) </a><br />
@endforeach
</p><br />
@endforeach
</div>
<div class="w-3/4">
@foreach ($products as $product)
<p>
<h1>{{ $product->name }} ({{ $product->sizes->pluck('name')->join(', ') }})</h1>
{{ $product->color }}<br /><br />
</p>
@endforeach
</div>
</div>
This is how it could look like with Livewire.
<h2>Colors</h2>
@foreach ($facet->getOptions() as $option)
<div class="facet-checkbox-pill">
<input
wire:model="filter.{{ $facet->getParamName() }}"
type="checkbox"
id="{{ $option->slug }}"
value="{{ $option->value }}"
/>
<label for="{{ $option->slug }}" class="{{ $option->selected ? 'selected' : '' }}">
{{ $option->value }} ({{ $option->total }})
</label>
</div>
@endforeach
Extend the Indexer to customize behavior, e.g. to save a "range bracket" value instead of a "individual price" value to the index.
class CustomIndexer extends Mgussekloo\FacetFilter\Indexer
{
public function buildRow($facet, $model, $value) {
$row = parent::buildRow($facet, $model, $value);
if ($facet->getSlug() == 'App\Models\Product.price') {
if ($row['value'] > 0 && $row['value'] < 100) {
$row['value'] = '0-100';
}
}
return $row;
}
}
Process models in chunks for very large datasets.
$perPage = 1000; $currentPage = Cache::get('facetIndexingPage', 1);
$products = Product::with(['sizes'])->paginate($perPage, ['*'], 'page', $currentPage);
$indexer = new Indexer($products);
if ($currentPage == 1) {
$indexer->resetIndex();
}
$indexer->buildIndex();
if ($products->hasMorePages()) {}
// next iteration, increase currentPage with one
}
Provide custom attributes and an optional custom Facet class in the facet definitions.
public static function facetDefinitions()
{
return [
[
'title' => 'Main color',
'description' => 'The main color.',
'fieldname' => 'color',
'facet_class' => CustomFacet::class
]
];
}
The MIT License (MIT). Please see License File for more information.