|
|
@@ -3,33 +3,28 @@
|
|
|
namespace Webkul\BagistoApi\Resolver;
|
|
|
|
|
|
use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
|
|
|
-use ApiPlatform\Metadata\GetCollection;
|
|
|
use ApiPlatform\State\Pagination\PaginatorInterface;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
-use Webkul\BagistoApi\Dto\CategoryProducts\AppliedFilterDto;
|
|
|
-use Webkul\BagistoApi\Dto\CategoryProducts\CategoryFilterEdgeDto;
|
|
|
use Webkul\BagistoApi\Dto\ProductSearch\ProductSearchEdgeDto;
|
|
|
use Webkul\BagistoApi\Models\CategoryProducts;
|
|
|
-use Webkul\BagistoApi\State\FilterableAttributesProvider;
|
|
|
use Webkul\BagistoApi\State\ProductGraphQLProvider;
|
|
|
|
|
|
class CategoryProductsResolver implements QueryItemResolverInterface
|
|
|
{
|
|
|
public function __construct(
|
|
|
private readonly ProductGraphQLProvider $productProvider,
|
|
|
- private readonly FilterableAttributesProvider $filterProvider,
|
|
|
) {}
|
|
|
|
|
|
public function __invoke(?object $item, array $context): object
|
|
|
{
|
|
|
$args = $context['args'] ?? [];
|
|
|
|
|
|
- $categorySlug = $args['categorySlug'] ?? null;
|
|
|
+ $categorySlug = $args['slug'] ?? null;
|
|
|
|
|
|
- // When a collection field (appliedFilters / availableFilters) is empty,
|
|
|
- // API Platform's ResolverFactory treats the empty array as "not yet
|
|
|
- // fetched" and re-invokes the item resolver for that sub-field. Guard
|
|
|
- // against this by returning an empty result for non-root field names.
|
|
|
+ // When the "products" collection field is empty, API Platform's
|
|
|
+ // ResolverFactory treats the empty array as "not yet fetched" and
|
|
|
+ // re-invokes the item resolver for that sub-field. Guard against this by
|
|
|
+ // returning an empty result for non-root field names.
|
|
|
$fieldName = $context['info']->fieldName ?? 'categoryProducts';
|
|
|
if (! $categorySlug || $fieldName !== 'categoryProducts') {
|
|
|
return new CategoryProducts;
|
|
|
@@ -43,8 +38,6 @@ class CategoryProductsResolver implements QueryItemResolverInterface
|
|
|
throw new \RuntimeException("Category not found for slug: {$categorySlug}");
|
|
|
}
|
|
|
|
|
|
- // 1. Products — reuse the product provider with category + filters and
|
|
|
- // default price-ascending order.
|
|
|
$filters = $this->parseFilter($args['filter'] ?? null);
|
|
|
$filters['category_id'] = (int) $categoryId;
|
|
|
|
|
|
@@ -78,32 +71,6 @@ class CategoryProductsResolver implements QueryItemResolverInterface
|
|
|
$result->products[] = $edge;
|
|
|
}
|
|
|
|
|
|
- // 2. Available filters — reuse the filter provider (returns attributes
|
|
|
- // with option-level product counts for the category). The client's
|
|
|
- // selected filters are passed through so facet counts reflect the
|
|
|
- // remaining result set (excluding each attribute's own selection).
|
|
|
- $filterArgs = [
|
|
|
- 'categorySlug' => $categorySlug,
|
|
|
- 'filter' => $args['filter'] ?? null,
|
|
|
- ];
|
|
|
-
|
|
|
- $filterPaginator = $this->filterProvider->provide(
|
|
|
- new GetCollection,
|
|
|
- [],
|
|
|
- ['source' => null, 'args' => $filterArgs, 'info' => null]
|
|
|
- );
|
|
|
-
|
|
|
- foreach ($filterPaginator as $index => $attribute) {
|
|
|
- $edge = new CategoryFilterEdgeDto;
|
|
|
- $edge->cursor = base64_encode((string) $index);
|
|
|
- $edge->node = $attribute;
|
|
|
- $result->available_filters[] = $edge;
|
|
|
- }
|
|
|
-
|
|
|
- // 3. Applied filters — resolve the client's current selection into a
|
|
|
- // structured list (code + label + option ids + option labels).
|
|
|
- $result->applied_filters = $this->buildAppliedFilters($filters);
|
|
|
-
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
@@ -151,138 +118,4 @@ class CategoryProductsResolver implements QueryItemResolverInterface
|
|
|
|
|
|
return is_array($filter) ? $filter : [];
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- * Build the structured "applied filters" list from the raw filter map.
|
|
|
- *
|
|
|
- * @param array<string, mixed> $filters
|
|
|
- * @return list<AppliedFilterDto>
|
|
|
- */
|
|
|
- private function buildAppliedFilters(array $filters): array
|
|
|
- {
|
|
|
- if (empty($filters)) {
|
|
|
- return [];
|
|
|
- }
|
|
|
-
|
|
|
- // Non-attribute keys handled specially (price range) or ignored
|
|
|
- // (already folded into the product query / pagination).
|
|
|
- $ignored = ['category_id', 'type', 'sku', 'new', 'featured', 'pageSize', 'first', 'last', 'after', 'before'];
|
|
|
-
|
|
|
- $price = [];
|
|
|
- if (isset($filters['price_from'])) {
|
|
|
- $price['from'] = (float) $filters['price_from'];
|
|
|
- unset($filters['price_from']);
|
|
|
- }
|
|
|
- if (isset($filters['price_to'])) {
|
|
|
- $price['to'] = (float) $filters['price_to'];
|
|
|
- unset($filters['price_to']);
|
|
|
- }
|
|
|
-
|
|
|
- $applied = [];
|
|
|
-
|
|
|
- foreach ($filters as $code => $spec) {
|
|
|
- if (in_array($code, $ignored, true)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- $values = $this->extractValues($spec);
|
|
|
- if (empty($values)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- $applied[] = $this->makeFilterDto($code, $values);
|
|
|
- }
|
|
|
-
|
|
|
- if (! empty($price)) {
|
|
|
- $dto = new AppliedFilterDto;
|
|
|
- $dto->code = 'price';
|
|
|
-
|
|
|
- $priceAttribute = DB::table('attributes')->where('code', 'price')->first();
|
|
|
- $dto->label = $priceAttribute?->admin_name ?? 'Price';
|
|
|
-
|
|
|
- $dto->values = [];
|
|
|
- $dto->value_labels = [];
|
|
|
-
|
|
|
- if (isset($price['from'])) {
|
|
|
- $dto->values[] = (string) $price['from'];
|
|
|
- $dto->value_labels[] = (string) $price['from'];
|
|
|
- }
|
|
|
-
|
|
|
- if (isset($price['to'])) {
|
|
|
- $dto->values[] = (string) $price['to'];
|
|
|
- $dto->value_labels[] = (string) $price['to'];
|
|
|
- }
|
|
|
-
|
|
|
- $applied[] = $dto;
|
|
|
- }
|
|
|
-
|
|
|
- return $applied;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Extract the selected option id(s) from a filter spec. Accepts:
|
|
|
- * "2", 2, ["2","3"], {"match": "2"}, {"match": "2,3"}
|
|
|
- *
|
|
|
- * @return list<string>
|
|
|
- */
|
|
|
- private function extractValues(mixed $spec): array
|
|
|
- {
|
|
|
- $raw = $spec;
|
|
|
-
|
|
|
- if (is_array($spec)) {
|
|
|
- if (isset($spec['match'])) {
|
|
|
- $raw = $spec['match'];
|
|
|
- } elseif (array_is_list($spec)) {
|
|
|
- $raw = $spec;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (is_array($raw)) {
|
|
|
- $values = [];
|
|
|
- foreach ($raw as $v) {
|
|
|
- foreach (explode(',', (string) $v) as $part) {
|
|
|
- $part = trim($part);
|
|
|
- if ($part !== '') {
|
|
|
- $values[] = $part;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return array_values(array_unique($values));
|
|
|
- }
|
|
|
-
|
|
|
- $values = array_filter(array_map('trim', explode(',', (string) $raw)));
|
|
|
-
|
|
|
- return array_values(array_unique($values));
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Build an AppliedFilterDto with the attribute label and option labels
|
|
|
- * resolved from the database.
|
|
|
- *
|
|
|
- * @param list<string> $values
|
|
|
- */
|
|
|
- private function makeFilterDto(string $code, array $values): AppliedFilterDto
|
|
|
- {
|
|
|
- $dto = new AppliedFilterDto;
|
|
|
- $dto->code = $code;
|
|
|
- $dto->values = $values;
|
|
|
-
|
|
|
- $attribute = DB::table('attributes')->where('code', $code)->first();
|
|
|
- if ($attribute) {
|
|
|
- $dto->label = $attribute->admin_name;
|
|
|
-
|
|
|
- $optionLabels = DB::table('attribute_options')
|
|
|
- ->where('attribute_id', $attribute->id)
|
|
|
- ->whereIn('id', $values)
|
|
|
- ->pluck('admin_name', 'id');
|
|
|
-
|
|
|
- $dto->value_labels = [];
|
|
|
- foreach ($values as $value) {
|
|
|
- $dto->value_labels[] = $optionLabels[$value] ?? $value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return $dto;
|
|
|
- }
|
|
|
}
|