generated from boboko/starter
95 lines
3.3 KiB
PHP
95 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Catalog\ProductCard;
|
|
use App\Catalog\ProductSortOptions;
|
|
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Collection;
|
|
use Lunar\Models\Product;
|
|
use Modules\Core\Catalog\Enums\ProductSort;
|
|
use Modules\Core\Catalog\Services\ProductSearchService;
|
|
|
|
class SearchController extends Controller
|
|
{
|
|
private const PER_PAGE = 12;
|
|
|
|
public function __construct(private readonly ProductSearchService $search) {}
|
|
|
|
public function show(string $locale)
|
|
{
|
|
$query = trim((string) request()->query('q', ''));
|
|
|
|
// Nothing to search for — send them to the all-products page rather than
|
|
// render an empty results page.
|
|
if ($query === '') {
|
|
return redirect()->route('products');
|
|
}
|
|
|
|
$sort = ProductSort::tryFrom((string) request()->query('sort'));
|
|
$page = max(1, (int) request()->query('page', 1));
|
|
|
|
$cards = $this->cardsFor($query, $sort);
|
|
|
|
$products = new LengthAwarePaginator(
|
|
items: $cards->forPage($page, self::PER_PAGE)->values(),
|
|
total: $cards->count(),
|
|
perPage: self::PER_PAGE,
|
|
currentPage: $page,
|
|
options: ['path' => LengthAwarePaginator::resolveCurrentPath()],
|
|
);
|
|
$products->appends(array_filter(
|
|
['q' => $query, 'sort' => $sort?->value],
|
|
fn ($value) => $value !== null,
|
|
));
|
|
|
|
// Sort dropdown links: same query, swapped sort (default sort => no param).
|
|
$sortOptions = ProductSortOptions::build(
|
|
$sort,
|
|
fn (?ProductSort $option) => route('search', array_filter(
|
|
['q' => $query, 'sort' => $option?->value],
|
|
fn ($value) => $value !== null,
|
|
)),
|
|
);
|
|
|
|
return view('search.index', [
|
|
'query' => $query,
|
|
'products' => $products,
|
|
'sortOptions' => $sortOptions,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Full-text matches as product-card arrays. ProductSearchService returns
|
|
* hydrated models in relevance order with no sort or pagination, so ordering
|
|
* is done here in PHP and the controller paginates the mapped collection.
|
|
*
|
|
* @return Collection<int, array<string, mixed>>
|
|
*/
|
|
private function cardsFor(string $query, ?ProductSort $sort): Collection
|
|
{
|
|
$results = $this->search->search($query)->load(['variants.prices', 'media']);
|
|
|
|
return $this->sortResults($results, $sort)
|
|
->map(ProductCard::fromModel(...))
|
|
->values();
|
|
}
|
|
|
|
/**
|
|
* @param EloquentCollection<int, Product> $results
|
|
* @return EloquentCollection<int, Product>
|
|
*/
|
|
private function sortResults(EloquentCollection $results, ?ProductSort $sort): EloquentCollection
|
|
{
|
|
$price = fn (Product $product) => $product->variants->first()?->prices->first()?->price->value ?? 0;
|
|
|
|
return match ($sort) {
|
|
ProductSort::PriceAsc => $results->sortBy($price)->values(),
|
|
ProductSort::PriceDesc => $results->sortByDesc($price)->values(),
|
|
ProductSort::Newest => $results->sortByDesc('created_at')->values(),
|
|
default => $results, // Meilisearch relevance order
|
|
};
|
|
}
|
|
}
|