Skip to content

Add configurable pagination policies to filtered queries - #88

Open
kettasoft wants to merge 2 commits into
masterfrom
feat/pagination-policy
Open

kettasoft wants to merge 2 commits into
masterfrom
feat/pagination-policy

Conversation

@kettasoft

Copy link
Copy Markdown
Owner

Summary

Add a centralized pagination policy for filtered queries while preserving Laravel's familiar paginate(), simplePaginate(), and cursorPaginate() APIs.

Problem

Filterable previously forwarded pagination calls directly to Eloquent and exposed a legacy paginate_limit setting through the model trait. That behavior had several limitations:

  • the setting acted as a default rather than a real maximum;
  • request-provided page sizes were not consistently resolved;
  • explicit and request-provided values had no shared upper bound;
  • applications had to repeat page-size validation in controllers;
  • the model trait coupled direct Eloquent pagination to Filterable configuration;
  • paginate(), simplePaginate(), and cursorPaginate() had no unified policy.

What changed

Central pagination policy

Introduce PaginationPolicy to resolve page sizes using this priority:

  1. the explicit $perPage argument;
  2. the configured request query parameter;
  3. the configured default page size.

Every resolved value remains subject to the effective maximum.

Laravel-compatible pagination calls

The existing API remains unchanged:

Post::filter()->paginate();
Post::filter()->simplePaginate();
Post::filter()->cursorPaginate();

Explicit arguments still take precedence over request input:

Post::filter()->paginate(50);

If the effective maximum is 100, paginate(500) is clamped to 100 or rejected according to the configured overflow behavior.

Global configuration

'pagination' => [
    'parameter' => 'per_page',
    'default' => 15,
    'max' => 100,
    'overflow' => 'clamp',
],

Supported overflow modes:

  • clamp: cap oversized values at the configured maximum;
  • reject: throw InvalidPageSizeException.

Malformed, zero, and negative page sizes are rejected in both modes.

The legacy paginate_limit value remains supported as the default when an application still uses an older published configuration.

Filter-level and runtime overrides

A filter class can define a reusable policy:

class PostFilter extends Filterable
{
    protected $pagination = [
        'default' => 25,
        'max' => 200,
        'overflow' => 'reject',
    ];
}

Endpoints can override selected values at runtime:

Post::filter()
    ->paginationPolicy(maxPerPage: 500)
    ->paginate();

Runtime settings override filter-class settings, which override package configuration. Omitted values continue to inherit.

Raw builder behavior

Pagination called directly on a Filterable instance still applies the policy when shouldReturnQueryBuilder() is enabled:

Post::filter()
    ->shouldReturnQueryBuilder()
    ->paginate();

An Eloquent builder obtained through an explicit apply() call is intentionally outside Filterable's execution boundary:

$builder = Post::filter()
    ->shouldReturnQueryBuilder()
    ->apply();

$builder->paginate(500); // Native Eloquent behavior.

Direct Eloquent queries that do not pass through filter() are also unchanged.

Compatibility details

  • preserves positional and named Laravel pagination arguments;
  • preserves paginator return types;
  • preserves closure-based $perPage values accepted by paginate();
  • normalizes pagination arguments before cache-key generation;
  • preserves the effective policy when an Invoker is serialized;
  • applies consistently across every Filterable engine.

Documentation

  • Added a dedicated Pagination Policy guide.
  • Added Pagination to the documentation navigation.
  • Updated the root README, Filterable API reference, facade reference, AI assistant instructions, repository agent rules, and changelog.

Tests

Coverage includes:

  • request, explicit, and default page-size precedence;
  • clamp and reject overflow modes;
  • malformed and non-positive values;
  • package, filter-class, and runtime configuration;
  • all three Laravel pagination methods;
  • positional and named arguments;
  • paginate() closures;
  • shouldReturnQueryBuilder() behavior;
  • direct Eloquent queries;
  • legacy configuration compatibility;
  • Invoker serialization;
  • facade access.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant