Feature: Adding Sorting To Product List

This commit is contained in:
2026-08-26 18:05:15 +03:00
parent 09844ec2b5
commit 751aff5939
4 changed files with 61 additions and 4 deletions
+20
View File
@@ -24,6 +24,7 @@ carry that full shape.
```php
use Modules\Core\Catalog\ProductFilters;
use Modules\Core\Catalog\ProductService;
use Modules\Core\Catalog\ProductSort;
$service = app(ProductService::class);
@@ -37,6 +38,10 @@ $result = $service->list(
page: 1,
);
// Sort — cheapest/priciest first, or newest first. Omit for Meilisearch's default
// relevance ordering (irrelevant here since the query is always empty).
$result = $service->list(perPage: 24, page: 1, sort: ProductSort::PriceAsc);
$result['data']; // array of Meilisearch documents (plain arrays, not models)
$result['meta']['total'];
$result['meta']['per_page'];
@@ -113,6 +118,21 @@ variants don't.
---
## Sorting
`ProductSort` (`Modules\Core\Catalog\ProductSort`) is a fixed enum of supported sort orders —
`PriceAsc`, `PriceDesc`, `Newest` — each mapping to a Meilisearch `sort` clause against a field
`Modules\Core\Search\ProductIndexer::getSortableFields()` marks sortable (`price`, plus
`created_at`/`updated_at`/`skus`/`status` inherited from Lunar's base indexer). Adding a new
`ProductSort` case requires adding the matching field to `getSortableFields()` and re-syncing (see
below) — sortable attributes are index settings, not computed per-query, same as filterable ones.
Omitting `sort` leaves Meilisearch's default ordering, which is meaningless here since `list()`
always searches with an empty query string (`Product::search('')`) — there's no relevance score to
rank by, so results come back in whatever order the index returns them absent an explicit sort.
---
## Registering the indexer
Not automatic — an app opts in via its own `config/lunar/search.php`:
+8 -4
View File
@@ -22,12 +22,16 @@ class ProductService
/**
* @return array{data: array<int, array>, meta: array}
*/
public function list(?ProductFilters $filters = null, int $perPage = 24, int $page = 1): array
public function list(?ProductFilters $filters = null, int $perPage = 24, int $page = 1, ?ProductSort $sort = null): array
{
$options = ['filter' => $this->buildFilter($filters)];
if ($sort !== null) {
$options['sort'] = [$sort->toMeilisearchSort()];
}
$paginator = Product::search('')
->options([
'filter' => $this->buildFilter($filters),
])
->options($options)
->paginateRaw(perPage: $perPage, page: $page);
return [
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Modules\Core\Catalog;
/**
* Sort options for ProductService::list(), each mapped to a Meilisearch `sort`
* clause against a field indexed as sortable by Modules\Core\Search\ProductIndexer
* (see its getSortableFields()). Adding a case here requires the matching field
* to also be sortable in the index, re-synced via `php artisan lunar:meilisearch:setup`.
*/
enum ProductSort: string
{
case PriceAsc = 'price_asc';
case PriceDesc = 'price_desc';
case Newest = 'newest';
public function toMeilisearchSort(): string
{
return match ($this) {
self::PriceAsc => 'price:asc',
self::PriceDesc => 'price:desc',
self::Newest => 'created_at:desc',
};
}
}
+8
View File
@@ -56,6 +56,14 @@ class ProductIndexer extends BaseProductIndexer
];
}
public function getSortableFields(): array
{
return [
...parent::getSortableFields(),
'price',
];
}
public function makeAllSearchableUsing(Builder $query): Builder
{
return parent::makeAllSearchableUsing($query)->with([