Skip to content

feat(state): cache hydra documentation and entrypoint responses#7929

Open
abderrahimghazali wants to merge 1 commit intoapi-platform:mainfrom
abderrahimghazali:feature/cacheable-documentation
Open

feat(state): cache hydra documentation and entrypoint responses#7929
abderrahimghazali wants to merge 1 commit intoapi-platform:mainfrom
abderrahimghazali:feature/cacheable-documentation

Conversation

@abderrahimghazali
Copy link
Copy Markdown

@abderrahimghazali abderrahimghazali commented Apr 26, 2026

Summary

Adds HTTP caching headers (ETag + revalidation Cache-Control) to the API documentation (/docs.{_format}) and entrypoint (/{index}.{_format}) routes so clients can skip re-downloading the body when nothing has changed.

Fixes #4074.

Why

These two endpoints are hit on every page load by API Platform Admin and any other client that parses the Hydra documentation. The response is often hundreds of KB (one user reports 280 KB / 1.3 s in #4074) and the content rarely changes between requests. Today there is no cache validator at all, so every fetch re-transfers the full payload.

What changes

  • Adds ApiPlatform\State\Processor\CacheableDocumentationProcessor, a state processor that decorates api_platform.state_processor.documentation (the processor used exclusively by EntrypointAction and DocumentationAction).
  • The processor:
    • Sets a strong ETag (md5 of the response body).
    • Sets Cache-Control: public, max-age=0, must-revalidate so clients always revalidate but can serve from local cache while doing so.
    • Calls Response::isNotModified() which short-circuits to a 304 Not Modified (with empty body) when the client sends a matching If-None-Match.
  • Wired in src/Symfony/Bundle/Resources/config/symfony/events.php as a decorator at priority 300 (outermost, so it sees the final serialized Response). The pattern mirrors the existing AddLinkHeaderProcessor.
  • The processor is a no-op for non-Response returns, non-200 responses, or empty bodies — and because it only decorates the documentation processor, it cannot affect any non-documentation route.

This follows the maintainer's suggestion in #4074 ("we must set etags and basic HTTP cache header on these endpoints").

Why a state processor (not a kernel listener)

API Platform 4.x routes documentation through the same state-processor pipeline as resources (documentation.serialize at 200, documentation.write at 100). A processor decorator is the idiomatic extension point — it scopes naturally to the two documentation actions (no route-name sniffing on kernel.response), composes with the stopwatch, and matches the precedent set by AddLinkHeaderProcessor.

Tests

New ApiPlatform\State\Tests\Processor\CacheableDocumentationProcessorTest covers:

  • ETag + Cache-Control headers (public, must-revalidate, max-age=0) are set on a 200 response.
  • If-None-Match matching the computed ETag yields a 304 response with an empty body.
  • Pass-through when the decorated processor returns something that isn't a Response.
  • No-op for non-200 responses.
  • No-op when the response body is empty.
  • Headers still applied when no request is present in the context.

Test plan

  • ./vendor/bin/phpunit --filter CacheableDocumentationProcessorTest (6/6, 18 assertions)
  • Full src/State suite (60/60, 216 assertions)
  • php -l on all changed files

Adds an ETag and revalidating Cache-Control headers to the api_doc and
api_entrypoint routes so clients (Admin UI, generated clients, schema
parsers) can avoid re-downloading the full documentation when nothing
changed. Implemented as a state processor decorating
api_platform.state_processor.documentation, mirroring the existing
AddLinkHeaderProcessor pattern. The processor computes a strong ETag
from the response body and delegates 304 handling to
Response::isNotModified(), which respects the client's If-None-Match
header.

Refs api-platform#4074
@abderrahimghazali abderrahimghazali force-pushed the feature/cacheable-documentation branch from 24a98c2 to 81420a8 Compare April 26, 2026 00:14
@abderrahimghazali abderrahimghazali changed the title feat(symfony): cache hydra documentation and entrypoint responses feat(state): cache hydra documentation and entrypoint responses Apr 26, 2026
@abderrahimghazali
Copy link
Copy Markdown
Author

The two CI failures (PHPStan (PHP 8.5) and PHP CS Fixer (PHP 8.3)) appear to be pre-existing, project-wide drift unrelated to this PR — both reproduce on other recent branches:

  • PHPStan: 2 errors in src/Mcp/Server/Handler.php:77 (Mcp\Schema\Request\CallToolRequest::$arguments) and Doctrine\ORM\Mapping\AssociationMapping::$targetEntity — neither file is touched here. The same 2 errors fail on job 72904777924 (refactor-use-trait, Apr 24).
  • PHP-CS-Fixer: Found 2501 of 2504 files that can be fixed — the latest fixer (3.95.x) wants declare(strict_types=1); removed across the codebase. Same job reports Found 2517 of 2520 files on job 72904777936 (same Apr 24 branch). Last successful main CI was Apr 2.

For this PR specifically, the 3 files I touched are flagged only for that same declare(strict_types=1); removal — I kept the declare to match the convention of the 6,000+ other files in the repo. Happy to remove it from my new files if maintainers want me to track the project-wide fixer migration here, but didn't want to bundle that into the PR unsolicited.

Tests for the new processor pass: ./vendor/bin/phpunit --filter CacheableDocumentationProcessorTest (6/6, 18 assertions); full src/State suite 60/60.

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.

Caching of hydra:ApiDocumentation and Entrypoint

1 participant