ProductRestProvider.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. <?php
  2. namespace Webkul\BagistoApi\State;
  3. use ApiPlatform\Metadata\Operation;
  4. /**
  5. * REST provider for product collection.
  6. *
  7. * Translates REST query params into the same $args shape ProductGraphQLProvider
  8. * consumes, then delegates to the parent. This keeps REST and GraphQL behaviour
  9. * identical for search, filter, sort, and pagination.
  10. *
  11. * Param mapping:
  12. * ?query=foo → args.query
  13. * ?sort=name-asc → args.sortKey=NAME, args.reverse=false
  14. * ?per_page=20&page=2 → args.first=20, args.after=base64(19)
  15. * ?type=configurable → args.filter.type
  16. * ?category_id=2 → args.filter.category_id
  17. * ?price=10,200 → args.filter.price_from=10, price_to=200
  18. * ?price_from=10&price_to=200 → args.filter.price_from, price_to
  19. * ?new=1 / ?featured=1 → args.filter.new / featured
  20. * ?color=3&size=6 → args.filter.color, size (filterable attributes)
  21. * ?filter={"color":{...}} → merged into args.filter
  22. * ?locale=en&channel=default → args.locale, args.channel
  23. * ?suggest=1 → args.suggest=true
  24. */
  25. class ProductRestProvider extends ProductGraphQLProvider
  26. {
  27. private const RESERVED_KEYS = [
  28. 'query', 'sort', 'order', 'page', 'per_page',
  29. 'locale', 'channel', 'filter', 'suggest',
  30. ];
  31. public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
  32. {
  33. $query = request()->query();
  34. $args = [];
  35. if (isset($query['query']) && $query['query'] !== '') {
  36. $args['query'] = (string) $query['query'];
  37. }
  38. if (isset($query['suggest'])) {
  39. $args['suggest'] = filter_var($query['suggest'], FILTER_VALIDATE_BOOLEAN);
  40. }
  41. [$sortKey, $reverse] = $this->parseSort($query['sort'] ?? null, $query['order'] ?? null);
  42. if ($sortKey !== null) {
  43. $args['sortKey'] = $sortKey;
  44. $args['reverse'] = $reverse;
  45. }
  46. $perPage = isset($query['per_page']) ? max(1, (int) $query['per_page']) : 30;
  47. $page = isset($query['page']) ? max(1, (int) $query['page']) : 1;
  48. $args['first'] = $perPage;
  49. if ($page > 1) {
  50. $args['after'] = base64_encode((string) (($page - 1) * $perPage - 1));
  51. }
  52. if (! empty($query['locale'])) {
  53. $args['locale'] = (string) $query['locale'];
  54. }
  55. if (! empty($query['channel'])) {
  56. $args['channel'] = (string) $query['channel'];
  57. }
  58. $filter = $this->buildFilter($query);
  59. if (! empty($filter)) {
  60. $args['filter'] = $filter;
  61. }
  62. return parent::provide($operation, $uriVariables, ['args' => $args]);
  63. }
  64. /**
  65. * Parse sort into GraphQL (sortKey, reverse) tuple.
  66. * Accepts compound `name-asc` or separate `sort=name&order=asc`.
  67. */
  68. private function parseSort(?string $sort, ?string $order): array
  69. {
  70. if (! $sort) {
  71. return [null, false];
  72. }
  73. $direction = $order ? strtolower($order) : null;
  74. if (str_contains($sort, '-')) {
  75. $pos = strrpos($sort, '-');
  76. $suffix = strtolower(substr($sort, $pos + 1));
  77. if (in_array($suffix, ['asc', 'desc'], true)) {
  78. $direction = $suffix;
  79. $sort = substr($sort, 0, $pos);
  80. }
  81. }
  82. return [strtoupper($sort), $direction === 'desc'];
  83. }
  84. /**
  85. * Build the filter array from REST query params.
  86. * Everything outside the reserved key list is treated as a filter.
  87. * A JSON `filter` param is merged on top.
  88. */
  89. private function buildFilter(array $query): array
  90. {
  91. $filter = [];
  92. foreach ($query as $key => $value) {
  93. if (in_array($key, self::RESERVED_KEYS, true)) {
  94. continue;
  95. }
  96. if ($value === '' || $value === null) {
  97. continue;
  98. }
  99. $filter[$key] = $value;
  100. }
  101. if (isset($filter['price']) && is_string($filter['price']) && str_contains($filter['price'], ',')) {
  102. [$from, $to] = array_pad(array_map('trim', explode(',', $filter['price'], 2)), 2, null);
  103. if ($from !== '' && $from !== null) {
  104. $filter['price_from'] = $from;
  105. }
  106. if ($to !== '' && $to !== null) {
  107. $filter['price_to'] = $to;
  108. }
  109. unset($filter['price']);
  110. }
  111. if (! empty($query['filter'])) {
  112. if (is_array($query['filter'])) {
  113. $filter = array_merge($filter, $query['filter']);
  114. } elseif (is_string($query['filter'])) {
  115. $decoded = json_decode($query['filter'], true);
  116. if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
  117. $filter = array_merge($filter, $decoded);
  118. }
  119. }
  120. }
  121. return $filter;
  122. }
  123. }