Feature: Reviews restructuring and Creating Collection Indexer and Services
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Catalog\Services;
|
||||
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator as LengthAwarePaginatorContract;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Lunar\Base\AttributeManifest;
|
||||
use Lunar\FieldTypes\TranslatedText;
|
||||
use Lunar\Models\Collection as CollectionModel;
|
||||
use Modules\Core\Catalog\DTOs\CollectionFilters;
|
||||
use Modules\Core\Catalog\Enums\CollectionSort;
|
||||
use Modules\Core\Localization\Services\LanguageCache;
|
||||
|
||||
/**
|
||||
* Category browsing (tree/nav) AND single-collection lookup, all reading directly
|
||||
* from the Meilisearch index (Modules\Core\Catalog\Services\CollectionIndexer) — same
|
||||
* shape and reasoning as Modules\Core\Catalog\Services\ProductService. Callers get
|
||||
* plain arrays of the indexed document, not Eloquent models.
|
||||
*/
|
||||
class CollectionService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LanguageCache $languages,
|
||||
private readonly AttributeManifest $attributes,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns a real LengthAwarePaginator (not Scout's own paginateRaw() result — see
|
||||
* ProductService's "Meilisearch driver quirk" note) so a controller/view gets
|
||||
* normal pagination behaviour without ever touching the raw Meilisearch response.
|
||||
*/
|
||||
public function list(?CollectionFilters $filters = null, int $perPage = 24, int $page = 1, ?CollectionSort $sort = null): LengthAwarePaginator
|
||||
{
|
||||
$options = ['filter' => $this->buildFilter($filters)];
|
||||
|
||||
if ($sort !== null) {
|
||||
$options['sort'] = [$sort->toMeilisearchSort()];
|
||||
}
|
||||
|
||||
$paginator = CollectionModel::search('')
|
||||
->options($options)
|
||||
->paginateRaw(perPage: $perPage, page: $page);
|
||||
|
||||
$data = collect($this->hitsFrom($paginator))
|
||||
->map(fn (array $collection) => $this->withLocalizedFields($collection))
|
||||
->all();
|
||||
|
||||
return new LengthAwarePaginator(
|
||||
items: $data,
|
||||
total: $paginator->total(),
|
||||
perPage: $paginator->perPage(),
|
||||
currentPage: $paginator->currentPage(),
|
||||
options: ['path' => LengthAwarePaginator::resolveCurrentPath()],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a single collection by its URL slug (any locale). Returns the full
|
||||
* indexed collection document, or null if no collection has that slug.
|
||||
*/
|
||||
public function getBySlug(string $slug): ?array
|
||||
{
|
||||
return $this->findOneWhere('slugs = "'.addcslashes($slug, '"\\').'"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a single collection by its primary key. Returns the full indexed
|
||||
* collection document, or null if no collection has that id.
|
||||
*/
|
||||
public function getById(int $id): ?array
|
||||
{
|
||||
return $this->findOneWhere("id = \"{$id}\"");
|
||||
}
|
||||
|
||||
private function findOneWhere(string $filter): ?array
|
||||
{
|
||||
$paginator = CollectionModel::search('')
|
||||
->options(['filter' => $filter])
|
||||
->paginateRaw(perPage: 1, page: 1);
|
||||
|
||||
$collection = $this->hitsFrom($paginator)[0] ?? null;
|
||||
|
||||
return $collection !== null ? $this->withLocalizedFields($collection) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves every translated Collection attribute's current-locale value — same
|
||||
* logic as ProductService::withLocalizedFields(), see there for the full
|
||||
* reasoning (AttributeManifest-driven, store-default-locale fallback, raw
|
||||
* per-locale keys stripped after resolving).
|
||||
*/
|
||||
private function withLocalizedFields(array $collection): array
|
||||
{
|
||||
$locale = App::getLocale();
|
||||
$fallbackLocale = $this->languages->defaultLocale();
|
||||
$availableLocales = $this->languages->availableLocales();
|
||||
|
||||
foreach ($this->translatedAttributeHandles() as $handle) {
|
||||
$collection[$handle] = $collection[$handle.'_'.$locale] ?? $collection[$handle.'_'.$fallbackLocale] ?? null;
|
||||
|
||||
foreach ($availableLocales as $availableLocale) {
|
||||
unset($collection[$handle.'_'.$availableLocale]);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function translatedAttributeHandles(): array
|
||||
{
|
||||
return $this->attributes->getSearchableAttributes((new CollectionModel)->getMorphClass())
|
||||
->filter(fn ($attribute) => $attribute->type === TranslatedText::class)
|
||||
->pluck('handle')
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* For the Meilisearch driver, Scout's paginateRaw() puts the whole raw response
|
||||
* in items(), not a plain list of hits — see ProductService's identical note.
|
||||
*/
|
||||
private function hitsFrom(LengthAwarePaginatorContract $paginator): array
|
||||
{
|
||||
$rawResponse = $paginator->items();
|
||||
|
||||
return collect($rawResponse['hits'] ?? [])->values()->all();
|
||||
}
|
||||
|
||||
private function buildFilter(?CollectionFilters $filters): ?string
|
||||
{
|
||||
if ($filters === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$clauses = Collection::make([
|
||||
$filters->parentId !== null ? "parent_id = \"{$filters->parentId}\""
|
||||
: ($filters->rootOnly ? 'parent_id IS NULL' : null),
|
||||
$filters->groupId !== null ? "collection_group_id = \"{$filters->groupId}\"" : null,
|
||||
])->filter();
|
||||
|
||||
return $clauses->isEmpty() ? null : $clauses->join(' AND ');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user