Skip to content

feat(doctrine): standalone Date/Exists filters, ComparisonFilter [between], deprecate RangeFilter#8351

Merged
soyuka merged 3 commits into
api-platform:mainfrom
soyuka:filter-survivor-standalone
Jun 29, 2026
Merged

feat(doctrine): standalone Date/Exists filters, ComparisonFilter [between], deprecate RangeFilter#8351
soyuka merged 3 commits into
api-platform:mainfrom
soyuka:filter-survivor-standalone

Conversation

@soyuka

@soyuka soyuka commented Jun 25, 2026

Copy link
Copy Markdown
Member
Q A
Branch? main (5.0)
Bug fix? no
New feature? yes
Deprecations? yes
Tickets -
License MIT
Doc PR -

Targets 5.0. Completes the standalone-filter work: the three filters that still extended the deprecated AbstractFilter are decoupled from it, and RangeFilter is folded into ComparisonFilter.

ExistsFilter standalone (ORM + ODM)

Both stop extending AbstractFilter. The inherited helpers (constructor, property accessors, isPropertyEnabled, name-converter handling, logger) are inlined. apply() still reads its value from $context['filters'], so the legacy declaration paths (Operation::$filters services, #[ApiFilter]) — which never produce a Parameter — keep working until 6.0. The ORM filter reads class metadata from $queryBuilder->getEntityManager() instead of the injected ManagerRegistry; ODM keeps the registry since its aggregation Builder exposes no DocumentManager. ManagerRegistryAwareInterface is retained for BC injection and the deprecated getDescription().

ComparisonFilter gains [between], RangeFilter deprecated

ComparisonFilter now parses [between]=X..Y alongside gt/gte/lt/lte/ne, emitting a single BETWEEN clause on ORM (X..X collapses to =) and the native gte/lte pair on ODM. ExactFilter gains the matching BETWEEN branch on the ORM path; it only fires for the between operator, leaving normal usage untouched.

This gives ComparisonFilter full parity with RangeFilter, whose operators are now a strict subset. RangeFilter is therefore deprecated (removed in 6.0; codemod rewrites it to ComparisonFilter). The [between] URL syntax is preserved, so there is no consumer-facing breaking change to the query API.

DateFilter standalone (ORM + ODM)

The last of the three survivors stops extending AbstractFilter. It stays a genuine survivor rather than a ComparisonFilter overlay: its date semantics — isDateField, \DateTime/\DateTimeImmutable coercion by column immutability, typed setParameter, and INCLUDE_NULL_* null-management — are not expressible through ComparisonFilter. The AbstractFilter::apply() loop is reintroduced locally (driving filterProperty over $context['filters']), and the inherited helpers are inlined as above.

Tests

ORM functional suites green locally (ExistsFilterTest, DateFilterTest, ComparisonFilterTest, RangeFilterTest — 102 tests). ODM covered by CI.

@soyuka
soyuka force-pushed the filter-survivor-standalone branch from e2abf4f to d245ec2 Compare June 29, 2026 09:00
soyuka added 2 commits June 29, 2026 13:29
Both ORM and ODM ExistsFilter stop extending the deprecated AbstractFilter
(5.0 survivor rewrite). The change is structural: apply() still reads its
value from $context['filters'] so the legacy declaration paths
(Operation::$filters services, #[ApiFilter]) — which never produce a
Parameter — keep working until 6.0.

The concrete helpers previously inherited from AbstractFilter (ctor,
properties accessors, isPropertyEnabled, name-converter handling, logger)
are inlined; ManagerRegistryAwareInterface is kept for BC injection and the
deprecated getDescription(). The ORM filter now reads class metadata from
$queryBuilder->getEntityManager() instead of the injected ManagerRegistry,
so the active filter path no longer depends on it; ODM keeps the registry
since its aggregation Builder exposes no DocumentManager (consistent with
the canonical ExactFilter).
ComparisonFilter now supports the full range syntax: alongside gt/gte/lt/lte/ne
it parses [between]=X..Y, emitting a single BETWEEN clause on ORM (so the SQL
optimizer treats it as a bounded range; X..X collapses to =) and the native
gte/lte pair on ODM. ExactFilter gains the matching BETWEEN branch for the ORM
path; the branch only fires for the 'between' operator, leaving normal usage
untouched.

This gives ComparisonFilter full parity with RangeFilter, whose operators are
now a strict subset. RangeFilter is therefore redundant and reclassified from
"5.0 overlay survivor" to doomed: deprecated now, removed in 6.0, codemod
rewrites it to ComparisonFilter. The [between] URL syntax is preserved by
ComparisonFilter, so there is no consumer-facing breaking change. The
new-style FilteredRangeParameter fixtures (ORM + ODM) move to
ComparisonFilter, which then serves as the functional spec for [between].
@soyuka
soyuka force-pushed the filter-survivor-standalone branch 3 times, most recently from c395c51 to 855bf91 Compare June 29, 2026 13:54
DateFilter (ORM + ODM) stops extending the deprecated AbstractFilter, the
last of the three 5.0 survivors (after ExistsFilter). It stays a genuine
survivor rather than a ComparisonFilter overlay: its date semantics —
isDateField, \DateTime/\DateTimeImmutable coercion by column immutability,
typed setParameter, and the INCLUDE_NULL_* null-management — are not
expressible through ComparisonFilter.

The decouple is structural: the AbstractFilter::apply() loop is reintroduced
as a local apply() (driving filterProperty over $context['filters'], the
universal value source for both legacy and QueryParameter declarations), and
the inherited helpers (ctor, properties accessors, isPropertyEnabled,
name-converter handling, logger) are inlined. ManagerRegistryAwareInterface
is kept for BC injection and the deprecated getDescription(); the ORM filter
reads class metadata from $queryBuilder->getEntityManager() so the active
path no longer depends on the registry, while ODM keeps it (its aggregation
Builder exposes no DocumentManager).
@soyuka
soyuka force-pushed the filter-survivor-standalone branch from 855bf91 to aabd7e6 Compare June 29, 2026 14:16
@soyuka
soyuka merged commit d37a753 into api-platform:main Jun 29, 2026
91 of 97 checks passed
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