Skip to content

QueryFilter

Viames Marino edited this page Feb 23, 2026 · 2 revisions

Pair framework: QueryFilter

Pair\Api\QueryFilter applies REST-style filtering/sorting/pagination to ActiveRecord list endpoints.

It is used by CrudController to parse request parameters and transform them into Pair\Orm\Query constraints.

Supported query parameters

  • filter[field]=value
  • filter[field]=!value
  • filter[field]=>=N, <=N, >N, <N
  • filter[field]=v1,v2,v3 (IN)
  • filter[field]=null, !null
  • sort=field,-otherField
  • search=keyword
  • fields=field1,field2
  • include=rel1,rel2
  • page=N
  • perPage=N

Model config integration

Allowed filters/sorts/search fields come from model API config (ApiExposable pattern), for example:

  • filterable
  • sortable
  • searchable
  • includes
  • perPage
  • maxPerPage
  • defaultSort

Main methods

apply(): array

Returns:

  • query (Pair\Orm\Query)
  • page
  • perPage
  • fields
  • includes

count(): int

Builds count query with current filters/search (without sort/pagination) to compute pagination metadata.

Example query

GET /api/faqs?filter[status]=published&search=payment&sort=-created&page=2&perPage=20

Effects:

  • where status = published
  • full-text-like search on configured searchable fields
  • order by created DESC
  • page 2, size 20

Notes

  • Disallowed or unknown fields are ignored.
  • perPage is clamped to configured maximum.
  • Sparse fieldsets (fields) and includes (include) are parsed but actual response shaping depends on Resource/transform logic.

Frequent usage recipes

Full query example

GET /api/users?filter[active]=1&filter[groupId]=2,3&search=ada&sort=-createdAt,email&page=2&perPage=30&fields=id,email,active&include=group

Typical effects:

  • active = 1
  • group_id IN (2,3) (through model binds)
  • search across configured searchable fields
  • order by created_at DESC, then email ASC
  • page 2, size 30 (bounded by maxPerPage)
  • sparse output fields and allowed includes parsed

Typical model API config

public static function getApiConfig(): array
{
    return [
        'filterable' => ['id', 'email', 'active', 'groupId'],
        'sortable' => ['id', 'email', 'createdAt'],
        'searchable' => ['email', 'fullName'],
        'includes' => ['group'],
        'defaultSort' => '-id',
        'perPage' => 20,
        'maxPerPage' => 100,
    ];
}

Manual use outside CrudController

$request = new \Pair\Api\Request();
$filter = new \Pair\Api\QueryFilter(\App\Orm\User::class, $request, \App\Orm\User::getApiConfig());

$total = $filter->count();
$result = $filter->apply();

$query = $result['query'];
$rows = \Pair\Orm\Database::load($query->toSql(), $query->getBindings());

Common pitfalls

  • Expecting filters on fields not listed in filterable.
  • Assuming fields automatically enforces DB-level select (it is response shaping).
  • Forgetting bind names: filters use model property names, not raw DB column names.
  • Relying on sort input without whitelisting in sortable.

See also: API, Request, ApiResponse, ActiveRecord, Resource.

Clone this wiki locally