From 751aff59392a4615bda43a01b9b8da73b5a34203 Mon Sep 17 00:00:00 2001 From: Konstantinos Arvanitakis Date: Wed, 26 Aug 2026 18:05:15 +0300 Subject: [PATCH] Feature: Adding Sorting To Product List --- docs/product-listing.md | 20 ++++++++++++++++++++ src/Catalog/ProductService.php | 12 ++++++++---- src/Catalog/ProductSort.php | 25 +++++++++++++++++++++++++ src/Search/ProductIndexer.php | 8 ++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/Catalog/ProductSort.php diff --git a/docs/product-listing.md b/docs/product-listing.md index 7d2397d..c417bdc 100644 --- a/docs/product-listing.md +++ b/docs/product-listing.md @@ -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`: diff --git a/src/Catalog/ProductService.php b/src/Catalog/ProductService.php index 5b1489e..6c7631b 100644 --- a/src/Catalog/ProductService.php +++ b/src/Catalog/ProductService.php @@ -22,12 +22,16 @@ class ProductService /** * @return array{data: 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 [ diff --git a/src/Catalog/ProductSort.php b/src/Catalog/ProductSort.php new file mode 100644 index 0000000..a36aea4 --- /dev/null +++ b/src/Catalog/ProductSort.php @@ -0,0 +1,25 @@ + 'price:asc', + self::PriceDesc => 'price:desc', + self::Newest => 'created_at:desc', + }; + } +} diff --git a/src/Search/ProductIndexer.php b/src/Search/ProductIndexer.php index 2e06194..de73393 100644 --- a/src/Search/ProductIndexer.php +++ b/src/Search/ProductIndexer.php @@ -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([