Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cursor Pagination Bundle Laravel Package

e2k/cursor-pagination-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation

    composer require e2k/cursor-pagination-bundle
    

    Add to config/bundles.php:

    E2k\CursorPaginationBundle\CursorPaginationBundle::class => ['all' => true],
    
  2. First Use Case: Basic Cursor Pagination Inject CursorQueryFactory into your repository/service:

    use E2k\CursorPaginationBundle\Pagination\CursorQueryFactory;
    
    class InvoiceRepository {
        public function __construct(private CursorQueryFactory $cursorQueryFactory) {}
    }
    
  3. Define Cursor Fields Specify fields for cursor-based pagination (e.g., id, createdAt):

    $query = $this->cursorQueryFactory
        ->create(Invoice::class, 'i')
        ->addCursorField(new CursorFieldDefinition('id', 'i.id', 'string'));
    
  4. Fetch Paginated Results

    $result = $query->paginate($cursor, $limit);
    // Returns CursorResult with data, next/prev cursors, and metadata
    

Where to Look First

  • Bundle Docs: README for DSL syntax.
  • Query Factory: CursorQueryFactory for building queries.
  • Field Definitions: CursorFieldDefinition and FieldDefinition for configuring pagination/filters.

Implementation Patterns

Workflows

  1. Repository Integration Use repositories to encapsulate cursor logic:

    class InvoiceRepository {
        public function findPaginated(
            string $cursor = null,
            int $limit = 20,
            ?string $status = null
        ): CursorResult {
            $query = $this->cursorQueryFactory
                ->create(Invoice::class, 'i')
                ->addCursorField(new CursorFieldDefinition('id', 'i.id', 'string'));
    
            if ($status) {
                $query->addFilter('status', $status);
            }
    
            return $query->paginate($cursor, $limit);
        }
    }
    
  2. Controller Layer Pass cursor/limit from request and return JSON:

    public function index(Request $request): JsonResponse {
        $cursor = $request->query->get('cursor');
        $limit = (int)$request->query->get('limit', 20);
        $status = $request->query->get('status');
    
        $result = $this->invoiceRepository->findPaginated($cursor, $limit, $status);
    
        return $this->json([
            'data' => $result->getItems(),
            'next_cursor' => $result->getNextCursor(),
            'prev_cursor' => $result->getPreviousCursor(),
        ]);
    }
    
  3. Filter DSL Use rich filter expressions (e.g., eq, gt, in):

    $query->addFilter('createdAt', '>', new \DateTime('2023-01-01'));
    $query->addFilter('status', 'in', ['pending', 'paid']);
    

Integration Tips

  • Doctrine ORM: Works seamlessly with Doctrine entities.
  • APIs: Ideal for GraphQL/REST APIs needing efficient pagination.
  • Testing: Mock CursorQueryFactory to test repositories:
    $this->createMock(CursorQueryFactory::class)
         ->method('create')
         ->willReturn($mockQuery);
    

Gotchas and Tips

Pitfalls

  1. Cursor Field Order

    • Cursor fields are evaluated lexicographically (alphabetical order by field name).
    • Ensure primary cursor fields (e.g., id) are defined before secondary fields (e.g., createdAt):
      // Correct: id (string) sorts before createdAt (datetime)
      ->addCursorField(new CursorFieldDefinition('id', 'i.id', 'string'))
      ->addCursorField(new CursorFieldDefinition('createdAt', 'i.createdAt', 'datetime'))
      
  2. Filter Performance

    • Complex filters (e.g., LIKE, JSON operations) may not leverage database indexes.
    • Prefer simple filters (=, >, IN) for optimal performance.
  3. Empty Cursors

    • Passing null as the initial cursor fetches the first page.
    • Invalid cursors (e.g., malformed strings) throw exceptions. Validate client-side.
  4. Case Sensitivity

    • String comparisons (e.g., status) are case-sensitive by default.
    • Use LOWER() in field definitions for case-insensitive filters:
      ->addFilterableField(new FieldDefinition('status', 'LOWER(i.status.value)'))
      

Debugging

  • Query Logging: Enable Doctrine logging to inspect generated SQL:
    $query->getQueryBuilder()->getQuery()->getSQL();
    
  • Cursor Validation: Check cursor strings for encoding issues (e.g., base64 decode failures).

Extension Points

  1. Custom Field Types Extend FieldDefinition for unsupported types (e.g., arrays):

    class ArrayFieldDefinition extends FieldDefinition {
        public function __construct(string $name, string $path, string $type = 'array') {
            parent::__construct($name, $path, $type);
        }
    }
    
  2. Query Builder Hooks Override CursorQueryFactory to modify queries:

    class CustomCursorQueryFactory extends CursorQueryFactory {
        protected function buildQueryBuilder(): QueryBuilder {
            $qb = parent::buildQueryBuilder();
            $qb->andWhere('i.deletedAt IS NULL'); // Soft-delete filter
            return $qb;
        }
    }
    
  3. Cursor Encoding Customize cursor serialization (e.g., JSON vs. base64):

    $cursor = $result->getNextCursor(); // Default: base64-encoded
    $decoded = base64_decode($cursor);
    

Config Quirks

  • Bundle Prefix: No configurable prefix; uses e2k_cursor_pagination by default.
  • Field Naming: Use dot notation for nested fields (e.g., user.name).
  • Limit Defaults: Set a default limit in the factory if needed:
    $query->setLimit($limit ?? 20);
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours