Skip to content

Add typed Filter builder for collection filtering#6

Merged
loevgaard merged 2 commits into
1.xfrom
add-typed-filter-builder
Jun 11, 2026
Merged

Add typed Filter builder for collection filtering#6
loevgaard merged 2 commits into
1.xfrom
add-typed-filter-builder

Conversation

@loevgaard

Copy link
Copy Markdown
Member

Summary

Replaces hand-rolled filter strings like

$options->withFilter(sprintf(
    'lastUpdated$gte:%s',
    $since->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d\TH:i:s\Z'),
));

with a typed builder:

$options->withFilter(Filter::gte('lastUpdated', $since));
  • Setono\Economic\Request\Filter (final readonly, \Stringable, Identifier-style private constructor): static factories eq/ne/gt/gte/lt/lte (string|int|float|bool|\DateTimeInterface|null), like (* wildcard preserved), in/nin (non-empty list, max 200 elements per e-conomic's cap, null elements render as $null:), and raw() as the verbatim escape hatch for anything the factories can't express.
  • Special characters in values ($ ( ) * , [ ]) are $-escaped automatically via single-pass strtr; null renders as $null:; any DateTimeInterface is converted to UTC and formatted Y-m-d\TH:i:s\Z without mutating the input; non-finite floats are rejected.
  • ->and()/->or() parenthesize every composite operand (receiver included) since e-conomic doesn't document $and:/$or: precedence; raw() filters are treated as composite when combined.
  • CollectionRequestOptions narrows to ?Filter (constructor and withFilter()) — raw strings are no longer accepted. Pre-1.0 break (latest tag is v1.0.0-alpha); hand-written expressions go through Filter::raw().
  • README, openspec/specs/endpoint-api/spec.md, and CLAUDE.md updated to the new API.

Test plan

  • composer phpunit — 232 tests, 785 assertions, including a new end-to-end test proving the unencoded-expression + RFC 3986 query-encoding layering through ScriptedHttpClient
  • composer analyse — PHPStan level max, clean
  • composer check-style — clean
  • vendor/bin/rector --dry-run — clean
  • vendor/bin/infection — MSI 17 points above the configured minimum

Replace raw filter strings with a Filter value object: one static
factory per e-conomic operator (eq/ne/gt/gte/lt/lte/like/in/nin),
->and()/->or() combinators with safe parenthesization, automatic
$-escaping of special characters, DateTimeInterface-to-UTC rendering,
and Filter::raw() as the verbatim escape hatch.

CollectionRequestOptions::withFilter() and the constructor now accept
only ?Filter (pre-1.0 break; latest tag is v1.0.0-alpha).
__toString() delegates to it, and call sites use the nullsafe-friendly
?->toString() (CollectionRequestOptions::toArray(), tests). Also drops
the .env ignore block again.
@loevgaard loevgaard merged commit 51e07e2 into 1.x Jun 11, 2026
7 checks passed
@loevgaard loevgaard deleted the add-typed-filter-builder branch June 11, 2026 10:22
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