Feature: Updating Products Service and Locale MIddleware
This commit is contained in:
@@ -2,15 +2,17 @@
|
||||
|
||||
namespace Modules\Core\Catalog;
|
||||
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Lunar\Models\Product;
|
||||
use Modules\Core\Localization\LocaleMiddleware;
|
||||
|
||||
/**
|
||||
* Storefront product listing/filtering, reading directly from the Meilisearch index
|
||||
* (Modules\Core\Search\ProductIndexer) rather than the database — no ->get() model
|
||||
* hydration, so callers get plain arrays of the indexed document, not Eloquent models.
|
||||
* Consumers needing the full record (e.g. a product detail page) should look the
|
||||
* product up directly via Lunar's Product model instead.
|
||||
* Storefront product listing/filtering AND single-product lookup, all reading directly
|
||||
* from the Meilisearch index (Modules\Core\Search\ProductIndexer) - one data source, no
|
||||
* ->get() model hydration anywhere in this service. Callers get plain arrays of the
|
||||
* indexed document, not Eloquent models.
|
||||
*
|
||||
* Full-text query search lives separately in Modules\Core\Search\ProductSearchService;
|
||||
* this service is for browsing/filtering without a search term.
|
||||
@@ -28,13 +30,10 @@ class ProductService
|
||||
])
|
||||
->paginateRaw(perPage: $perPage, page: $page);
|
||||
|
||||
// For the Meilisearch driver, Scout's paginateRaw() puts the whole raw
|
||||
// response (hits, query, processingTimeMs, ...) in items(), not a plain
|
||||
// list of hits — the actual documents are under the 'hits' key.
|
||||
$rawResponse = $paginator->items();
|
||||
|
||||
return [
|
||||
'data' => collect($rawResponse['hits'] ?? [])->values()->all(),
|
||||
'data' => collect($this->hitsFrom($paginator))
|
||||
->map(fn (array $product) => $this->withLocalizedFields($product))
|
||||
->all(),
|
||||
'meta' => [
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
@@ -44,6 +43,71 @@ class ProductService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a single product by its URL slug (any locale - slugs are indexed across
|
||||
* all languages, see Modules\Core\Search\ProductIndexer). Returns the full indexed
|
||||
* product document, or null if no product has that slug.
|
||||
*/
|
||||
public function getBySlug(string $slug): ?array
|
||||
{
|
||||
return $this->findOneWhere('slugs = "'.addcslashes($slug, '"\\').'"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a single product by its primary key. Returns the full indexed product
|
||||
* document, or null if no product has that id.
|
||||
*/
|
||||
public function getById(int $id): ?array
|
||||
{
|
||||
return $this->findOneWhere("id = \"{$id}\"");
|
||||
}
|
||||
|
||||
private function findOneWhere(string $filter): ?array
|
||||
{
|
||||
$paginator = Product::search('')
|
||||
->options(['filter' => $filter])
|
||||
->paginateRaw(perPage: 1, page: 1);
|
||||
|
||||
$product = $this->hitsFrom($paginator)[0] ?? null;
|
||||
|
||||
return $product !== null ? $this->withLocalizedFields($product) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current-locale `name`/`description` from the indexer's
|
||||
* per-locale `name_{locale}`/`description_{locale}` fields, falling back to
|
||||
* the store's default language (Language::default, see
|
||||
* LocaleMiddleware::defaultLocale()) when the current locale has no
|
||||
* translation - e.g. a product with no English copy yet still shows its
|
||||
* Greek name/description on /en/ rather than rendering blank.
|
||||
*
|
||||
* Deliberately not config('app.locale') - App::setLocale() overwrites that
|
||||
* config value on every request, so by request time it's just whatever the
|
||||
* current locale already is, not a stable fallback.
|
||||
*/
|
||||
private function withLocalizedFields(array $product): array
|
||||
{
|
||||
$locale = App::getLocale();
|
||||
$fallbackLocale = LocaleMiddleware::defaultLocale();
|
||||
|
||||
$product['name'] = $product['name_'.$locale] ?? $product['name_'.$fallbackLocale] ?? null;
|
||||
$product['description'] = $product['description_'.$locale] ?? $product['description_'.$fallbackLocale] ?? null;
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the Meilisearch driver, Scout's paginateRaw() puts the whole raw response
|
||||
* (hits, query, processingTimeMs, ...) in items(), not a plain list of hits - the
|
||||
* actual documents are under the 'hits' key.
|
||||
*/
|
||||
private function hitsFrom(LengthAwarePaginator $paginator): array
|
||||
{
|
||||
$rawResponse = $paginator->items();
|
||||
|
||||
return collect($rawResponse['hits'] ?? [])->values()->all();
|
||||
}
|
||||
|
||||
private function buildFilter(?ProductFilters $filters): ?string
|
||||
{
|
||||
if ($filters === null) {
|
||||
|
||||
Reference in New Issue
Block a user