yuriitatur/search

A simple framework/db agnostic api search package

Maintainers

Package info

bitbucket.org/yurii_tatur/search

pkg:composer/yuriitatur/search

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

v1.0.0 2025-08-09 21:33 UTC

This package is auto-updated.

Last update: 2026-04-12 21:26:03 UTC


README

Quality Gate Status Coverage

Search module

A framework-agnostic PHP 8.2+ library for building filterable, sortable, and paginated API queries. It validates incoming request options, composes a QueryBuilder, and executes the query — all driven by a set of composable filters you define.

Installation

composer require yuriitatur/search

Core concepts

Seeker

Seeker is the main entry point. It wires together a validator, a query composer, an executor, and a map of filters:

$seeker = new Seeker(
    new RequestValidator(Validation::createValidator()),
    new QueryComposer,
    new PaginatorQueryExecutor($repository),
    DefaultFilters::get()
);

$result = $seeker->seek($request->query->all());

seek() validates the options against the registered filters, builds a QueryBuilder, executes it, and returns an IteratorAggregate result.

Filters

Filters implement FilterInterface:

interface FilterInterface
{
    public function applyFilter(QueryBuilder $builder, mixed $value): void;

    /** @return Constraint[] */
    public function getValidationRules(): array;
}

Each filter both mutates the query and declares its own Symfony Validator constraints. If any option fails validation a ValidationException is thrown before the query is built.

Built-in filters

DefaultFilters::get(array $mergeFilters = [], int $defaultLimit = 100) returns a ready-made filter map:

KeyFilterBehaviour
limitLimitFilterSets result limit (default 100)
paginationPaginationFilterOffset-based page navigation, shares the same LimitFilter instance
allNoLimitRemoves the limit
cursorCursorFilterEnables cursor-based pagination
idPositiveIntegerFilterFilters by a single positive integer id
idsInConcatenatedFilterFilters by a comma-separated list of ids
sortMultiValueFilter(OrderByFilter)Accepts one or more column:asc\|desc values

Additional filters available to use directly:

  • LikeFilter($field, $minLength) — full-text LIKE search on a field
  • EnumFilter($field, $enum) — restricts a field to a backed enum's values
  • LimitedStringFilter($field, $maxLength) — string equality with a length cap
  • CompositeFilter — groups multiple filters under a single key
  • ConcatenatedMultiValueFilter — parses a comma-separated string into multiple filter values

Pass any overrides or additions as the first argument to DefaultFilters::get():

DefaultFilters::get([
    'name'   => new LikeFilter('name'),
    'status' => new EnumFilter('status', StatusEnum::class),
])

Sorting

The sort key accepts column:asc or column:desc. Pass multiple values as an array for multi-column sorting:

$seeker->seek(['sort' => ['created_at:desc', 'name:asc']]);

QueryComposer defaults

QueryComposer applies a default limit (100), offset (0), and sort (id desc) when those are not set by any filter. Override via constructor:

new QueryComposer(defaultLimit: 50, defaultSortColumn: 'created_at', defaultSortDirection: 'asc')

Executors

Offset pagination — PaginatorQueryExecutor

Returns a PaginatorResult with data, totalCount, and helpers like getCurrentPage(), getLastPage(), getPerPage().

$executor = new PaginatorQueryExecutor($repository);

Cursor pagination — CursorQueryExecutor

Returns a CursorResult with data, currentCursor, nextCursor, and previousCursor. Cursor values are opaque strings. The previous cursor is computed lazily and cached.

$executor = new CursorQueryExecutor($repository, $cache, $cursorQueryBuilder, $extractor);

Auto-routing — CombinedExecutor

Routes to CursorQueryExecutor when a cursor option is present in the query, otherwise falls back to PaginatorQueryExecutor. This lets a single endpoint support both pagination styles.

$executor = new CombinedExecutor($paginatorExecutor, $cursorExecutor);

CursorResult and PaginatorResult both implement IteratorAggregate, so the result can always be iterated directly regardless of which executor was used.

Cursor hydrators

CursorQueryExecutor needs to extract the sort-column value from result entities. Three hydrator implementations are provided:

ClassRequires
PropertyAccessCursorHydratorsymfony/property-access
SymfonySerializerCursorHydratorsymfony/serializer
JmsSerializerCursorHydratorjms/serializer

Laravel integration

A Laravel service provider is auto-discovered from composer.json. It registers Symfony Validator, PropertyAccessCursorHydrator as the default hydrator, and a Symfony Cache adapter. No manual registration is needed.

Testing

composer test

License

This code is under MIT license, read more in the LICENSE file.