Feature: Adding Sorting To Product List
This commit is contained in:
@@ -24,6 +24,7 @@ carry that full shape.
|
|||||||
```php
|
```php
|
||||||
use Modules\Core\Catalog\ProductFilters;
|
use Modules\Core\Catalog\ProductFilters;
|
||||||
use Modules\Core\Catalog\ProductService;
|
use Modules\Core\Catalog\ProductService;
|
||||||
|
use Modules\Core\Catalog\ProductSort;
|
||||||
|
|
||||||
$service = app(ProductService::class);
|
$service = app(ProductService::class);
|
||||||
|
|
||||||
@@ -37,6 +38,10 @@ $result = $service->list(
|
|||||||
page: 1,
|
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['data']; // array of Meilisearch documents (plain arrays, not models)
|
||||||
$result['meta']['total'];
|
$result['meta']['total'];
|
||||||
$result['meta']['per_page'];
|
$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
|
## Registering the indexer
|
||||||
|
|
||||||
Not automatic — an app opts in via its own `config/lunar/search.php`:
|
Not automatic — an app opts in via its own `config/lunar/search.php`:
|
||||||
|
|||||||
@@ -22,12 +22,16 @@ class ProductService
|
|||||||
/**
|
/**
|
||||||
* @return array{data: array<int, array>, meta: array}
|
* @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('')
|
$paginator = Product::search('')
|
||||||
->options([
|
->options($options)
|
||||||
'filter' => $this->buildFilter($filters),
|
|
||||||
])
|
|
||||||
->paginateRaw(perPage: $perPage, page: $page);
|
->paginateRaw(perPage: $perPage, page: $page);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,14 @@ class ProductIndexer extends BaseProductIndexer
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSortableFields(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...parent::getSortableFields(),
|
||||||
|
'price',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function makeAllSearchableUsing(Builder $query): Builder
|
public function makeAllSearchableUsing(Builder $query): Builder
|
||||||
{
|
{
|
||||||
return parent::makeAllSearchableUsing($query)->with([
|
return parent::makeAllSearchableUsing($query)->with([
|
||||||
|
|||||||
Reference in New Issue
Block a user