CursorAwareCollectionProvider.php 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. namespace Webkul\BagistoApi\State;
  3. use ApiPlatform\Laravel\Eloquent\Paginator;
  4. use ApiPlatform\Laravel\Eloquent\State\LinksHandler;
  5. use ApiPlatform\Metadata\Operation;
  6. use ApiPlatform\State\ProviderInterface;
  7. use Illuminate\Pagination\LengthAwarePaginator;
  8. /**
  9. * Generic cursor-aware collection provider for GraphQL queries.
  10. *
  11. * API Platform's default CollectionProvider uses page-based pagination
  12. * which ignores cursor arguments (after/before). This provider properly
  13. * decodes cursor arguments and applies offset-based pagination so that
  14. * Relay-style cursor pagination works correctly.
  15. *
  16. * Can be used by any model that needs working cursor pagination without
  17. * custom query logic.
  18. */
  19. class CursorAwareCollectionProvider implements ProviderInterface
  20. {
  21. public function __construct(
  22. private readonly LinksHandler $linksHandler,
  23. ) {}
  24. public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
  25. {
  26. $resourceClass = $operation->getClass();
  27. $model = new $resourceClass;
  28. $args = $context['args'] ?? [];
  29. $first = isset($args['first']) ? (int) $args['first'] : null;
  30. $last = isset($args['last']) ? (int) $args['last'] : null;
  31. $after = $args['after'] ?? null;
  32. $before = $args['before'] ?? null;
  33. $limit = $first ?? $last ?? 10;
  34. $offset = 0;
  35. if ($after) {
  36. $decoded = base64_decode($after, true);
  37. $offset = ctype_digit((string) $decoded) ? ((int) $decoded + 1) : 0;
  38. }
  39. if ($before) {
  40. $decoded = base64_decode($before, true);
  41. $cursor = ctype_digit((string) $decoded) ? (int) $decoded : 0;
  42. $offset = max(0, $cursor - $limit);
  43. }
  44. $query = $this->linksHandler->handleLinks(
  45. $model->newQuery(),
  46. $uriVariables,
  47. ['operation' => $operation, 'modelClass' => $operation->getClass()] + $context
  48. );
  49. $filters = $context['filters'] ?? [];
  50. foreach ($filters as $column => $value) {
  51. if ($model->getConnection()->getSchemaBuilder()->hasColumn($model->getTable(), $column)) {
  52. $query->where($column, $value);
  53. }
  54. }
  55. $total = (clone $query)->count();
  56. if ($offset > $total) {
  57. $offset = max(0, $total - $limit);
  58. }
  59. $items = $query
  60. ->offset($offset)
  61. ->limit($limit)
  62. ->get();
  63. $currentPage = $total > 0 ? (int) floor($offset / $limit) + 1 : 1;
  64. return new Paginator(
  65. new LengthAwarePaginator(
  66. $items,
  67. $total,
  68. $limit,
  69. $currentPage,
  70. ['path' => request()->url()]
  71. )
  72. );
  73. }
  74. }