Skip to content

fix(hydra): emit hydra:next and hydra:previous on empty cursor-paginated collections#7961

Open
guillaumedelre wants to merge 3 commits intoapi-platform:4.3from
guillaumedelre:feature/issue-7953
Open

fix(hydra): emit hydra:next and hydra:previous on empty cursor-paginated collections#7961
guillaumedelre wants to merge 3 commits intoapi-platform:4.3from
guillaumedelre:feature/issue-7953

Conversation

@guillaumedelre
Copy link
Copy Markdown

Q A
Branch? main
Bug fix? yes
New feature? no
Deprecations? no
Issues Closes #7953
License MIT

What's in this PR?

When a cursor-paginated collection returns no items (e.g. a range filter excludes all rows), hydra:view was emitted with only @id and @type. The hydra:next and hydra:previous links were silently dropped, leaving clients with no way to navigate without out-of-band knowledge of the cursor field and items-per-page value.

This was a regression from pre-4.x behavior, verified by the Behat scenario "Cursor-based pagination with range filtered items" in features/hydra/collection.feature.

Root cause

PartialCollectionViewNormalizer::populateDataWithCursorBasedPagination() builds navigation links by reading the first and last objects of the collection. When the collection is empty, both current() and end() return false and both branches are skipped.

Fix

When the collection is empty and the request URL already contains a cursor filter parameter for a field declared in paginationViaCursor, synthesize the navigation links directly from the URL parameters:

  • hydra:next: invert the cursor operator (gtlt), keep the value
  • hydra:previous: keep the operator, shift the value by items_per_page

If no cursor filter is present in the URL (e.g. a plain request to /resources that returns nothing), the behavior is unchanged: no links are emitted.

Tests

  • PartialCollectionViewNormalizerTest::testNormalizeWithCursorBasedPaginationEmptyCollection — unit test, synthesized URLs verified against expected values
  • CursorPaginationEmptyCollectionTest::testEmptyCollectionWithCursorFilterHasNavigationLinks — functional test with Doctrine/SQLite, 10 fixtures inserted, empty result forced via id[gt]=10

Full Hydra component suite: 51 tests / 202 assertions, all green (3 PHPUnit notices are pre-existing).

@guillaumedelre
Copy link
Copy Markdown
Author

Hey @soyuka @dunglas @alanpoulain, this PR fixes #7953 — happy to adjust if anything looks off.

@soyuka
Copy link
Copy Markdown
Member

soyuka commented May 11, 2026

can you target 4.3? The issue in behat is because of the wrong use of filters I'll try to take a look shortly

@guillaumedelre guillaumedelre changed the base branch from main to 4.3 May 11, 2026 07:46
@guillaumedelre
Copy link
Copy Markdown
Author

@soyuka i have just changed the target to 4.3

…ted collections

When a cursor-paginated collection returns no items and the request URL
already contains a cursor filter (e.g. id[gt]=10), navigation links were
silently omitted because populateDataWithCursorBasedPagination() relies on
reading the first and last objects of the collection.

Synthesize the links from the URL parameters instead:
- hydra:next: invert the cursor operator (gt -> lt), keep the value
- hydra:previous: keep the operator, shift the value by items_per_page

If no cursor filter is present in the URL, the behavior is unchanged.

Fixes api-platform#7953

Signed-off-by: Guillaume Delré <delre.guillaume@gmail.com>
@soyuka soyuka force-pushed the feature/issue-7953 branch from 6244b35 to ad606ca Compare May 11, 2026 08:03
soyuka added 2 commits May 11, 2026 10:31
| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Tickets       | refs api-platform#7953
| License       | MIT
| Doc PR        | ∅

Replace #[ApiFilter(RangeFilter)] / #[ApiFilter(OrderFilter)] with
QueryParameter + ComparisonFilter(ExactFilter) + SortFilter, so the new
regression fixture does not ship usages slated for deprecation in 4.4
and removal in 6.0 per TODO-filters.md.
| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Tickets       | refs api-platform#7953
| License       | MIT
| Doc PR        | ∅

Entity\Dummy and Entity\Issue7953\Dummy both auto-derive table name
"Dummy", so SchemaTool::createSchema throws TableAlreadyExists and
cascades 500s through every later behat scenario.
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.

[Hydra] Empty cursor-paginated collection omits hydra:next and hydra:previous

2 participants