Skip to content

3.0: negotiate request-aware JSON and HTML responses #8

Closed

Description

@coisa

Status and delivery

Implemented in PR 3, with the owner authorizing merge after a satisfactory final review. The 2026-10-28 review and current-head results below replace the older readiness snapshots retained in the historical details. The owner has instructed the primary agent to execute the merge if the final review and current-head checks remain satisfactory; no further owner-review decision is being left pending here. This issue remains open until PR 3's native closing link takes effect. Merging a separate generated version PR or publishing a release requires its own authorization.

Problem and approved design

A cached response service must select a representation from each current request without storing that request or reporting the failure again.

  • Every renderer implements createResponseFromThrowable(Throwable $throwable, ServerRequestInterface $request): ResponseInterface with a required request argument. Update custom implementations and direct callers in this major.
  • A single ErrorHandlerServiceProvider(bool $debug = false, bool $cli = false) supplies NegotiatingThrowableResponseFactory. Applications normally use container($application, ErrorHandlerServiceProvider::class); the separate JSON/Whoops providers are removed.
  • The negotiator receives a generic fallback and a lazy media-type map. Only the selected renderer is resolved. The current Accept header selects text/plain, application/json, application/problem+json or text/html.
  • Positive quality/specificity determines selection; explicit q=0 exclusions remain excluded. Absent or unsupported positive preferences use the generic error body rather than a new 406. Wildcard ties follow the configured order, with generic before JSON before HTML. Preserve existing Vary fields and add Accept.
  • Symfony HTML is the default for both public and diagnostic HTML. Public mode omits private exception details. Trusted application configuration enables debug; neither Accept nor SAPI detection enables diagnostics or forces HTML.
  • JSON uses a configured Symfony SerializerInterface with ProblemNormalizer and JsonEncoder. Both JSON media types have Problem Details fields type: about:blank, PSR-17 title, mapped status, and public detail: Request failed. Direct JSON returns application/problem+json; negotiation retains the selected supported JSON media type. Debug uses the original message as detail and adds class/trace without argument values or objects. Serialization failures propagate without HTML fallback.
  • Optional Whoops remains a public concrete adapter, selected only by an explicit application override of the HTML candidate. Installing it never changes defaults. Preserve its lazy PrettyPageHandler configuration service and sequential-page/native-source boundaries.
  • Preserve one original private report before pure rendering, unchanged collaborator failures, fresh middleware scopes and inert construction. A JSON-only request must never resolve HTML/Whoops or select a CLI renderer.

Separate approved terminal integration

SymfonyCliThrowableHandler(?OutputInterface $output = null) formats terminal diagnostics through Symfony CliErrorRenderer. It writes to stderr by default, or raw Console output/error output when explicitly supplied. Provider cli: true opts into aggregate membership; a custom ThrowableHandlerInterface override remains authoritative. Console is optional. This adapter installs no callbacks, chooses no SAPI or exit status and does not participate in HTTP negotiation. A Console application chooses one exception-rendering owner.

The CLI adapter is an independently reviewable part of the major implementation, not a prerequisite for selecting an HTTP representation. Cross-cutting delivery and the event-recipe dependency refresh are tracked by 26; report-once adaptation remains 95.

Final review evidence (2026-10-28)

Candidate: 5744e12bbfdc8cbbbced8fa6b90fbc3b20590ff2, reconciled with master 09d15d11d7af4779c332e000ac084ede1d037ad0.

  • Local PHP 8.5.12: formatting, PHPCS, 492 tests / 3,009 assertions and maximum PHPStan pass; 27 bounded example scenarios and the source-matched site pass.
  • The independent review identified a negotiated HTML charset mismatch. The source factory and explicit Whoops map now advertise their actual UTF-8 encoding; regression coverage verifies HTML UTF-8 preferences, exclusions and selected media parameters. The focused regression set passes 39 tests / 292 assertions.
  • Fresh PHP CI, documentation, style and Changelog results refer to this candidate. Dependency audit reports 0 advisories for the resolved graph. The PHP workflow's 14 jobs pass, and exact-head feedback also succeeds. The site verifies 15 excerpts, 83 links, 30 anchors and 19 recipes; the 85-file archive gate passes. These results do not promise every possible dependency combination.
  • The coverage policy, source scope, diagnostic privacy, one-report boundary and failure propagation were preserved. No tag/release or Pages publication was performed.

Completion criteria

  • Approve the required request contract, single provider, Symfony HTML, Problem Details, explicit Whoops and terminal opt-in direction.
  • Implement and test request/current-Throwable identity, Accept preferences/exclusions, lazy resolution, public/debug output, optional-package boundaries and one-report cleanup.
  • Align PHPDoc, complete recipes, guides, site, consumer skill and the independent major fragment.
  • Verify the final charset regression fix and all checks at the exact candidate head recorded above.
  • Merge PR 3 after the satisfactory final review; allow the native closing link to close this issue.
Earlier post-3.0 proposal (superseded by the approved major scope)

Status and delivery boundary

Later 3.x design work, deliberately outside PR 3 and the 2.x drain. JSON/Whoops rendering and independent profiles already shipped in 2.5.0 through 92/96. Their major lifecycle adaptation belongs to 95. This issue owns only reusable request/Accept-based selection; no implementation API has been approved.

Problem and decision needed

The current renderer contract accepts only Throwable. Applications can explicitly choose a renderer now, but reusable per-request selection needs a reviewed boundary and lifetime.

Compare an additive request-aware capability with application-owned per-request composition. An existing-interface break requires a declared major. Do not add an optional request parameter and assume old implementations remain compatible, duplicate exception middleware, cache a current request in a singleton, or use a superglobal registry.

Approve these independent choices before coding:

  1. The smallest public request-aware API and lifetime, preserving custom factories.
  2. Trusted development authorization, separate from representation preference: Accept must never enable diagnostics.
  3. Preference/fallback/failure/cache/Vary policy, including absent, wildcard-only, malformed and unsupported Accept.

Completion criteria

  • Approve the API/lifetime and policy above with concrete call examples.
  • Preserve original-once reporting, collaborator failures, optional Whoops and no-dependency/default consumers under the selected lifecycle.
  • Test HTML/JSON preference, quality/specificity, explicit exclusions, development on/off, current request data under reuse and exact status/content/cache/Vary behavior.
  • Align PHPDoc, meaningful tests, runnable recipes, applicable HTTP/provider/migration guides, site, consumer skill and an independently classified release note.

Proceed as a later incremental feature after the coherent major. Theoretical additive 2.x compatibility does not override that chosen priority or approve the API. Owner review/merge/publication remain separate decisions.

Protocol and integration limits

RFC 9110 Accept quality/specificity and q=0 exclusions need real cases; text/html;q=0 remains excluded despite a wildcard. The RFC does not require HTML as this library's absent/malformed-header fallback.

Whoops is a development renderer, not comprehensive redaction or a guarantee for arbitrary concurrent shared pages. Its native source-read correction was independently delivered upstream and adopted by 2.x 121; negotiation does not solve source reading, native container fallback or development authorization.

Activity

  1. changed the title [-][RFC / Architecture] Integrate opt-in development HTML error pages with request negotiation[/-] [+]Define explicit request-aware error response negotiation[/+] on Oct 17, 2026
  2. added this to the 3.x follow-ups milestone on Oct 17, 2026
  3. changed the title [-]Define explicit request-aware error response negotiation[/-] [+]Define request-aware error response negotiation after 3.0[/+] on Oct 17, 2026
  4. changed the title [-]Define request-aware error response negotiation after 3.0[/-] [+]Design request-aware response negotiation after 3.0[/+] on Oct 20, 2026
  5. modified the milestones: 3.x follow-ups, 3.0 on Oct 21, 2026
  6. changed the title [-]Design request-aware response negotiation after 3.0[/-] [+]3.0: negotiate request-aware JSON and HTML responses[/+] on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions