Repository navigation
3.0: negotiate request-aware JSON and HTML responses #8
Description
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): ResponseInterfacewith a required request argument. Update custom implementations and direct callers in this major. - A single
ErrorHandlerServiceProvider(bool $debug = false, bool $cli = false)suppliesNegotiatingThrowableResponseFactory. Applications normally usecontainer($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
Acceptheader selectstext/plain,application/json,application/problem+jsonortext/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
SerializerInterfacewith ProblemNormalizer and JsonEncoder. Both JSON media types have Problem Details fieldstype: about:blank, PSR-17title, mappedstatus, and publicdetail: Request failed. Direct JSON returnsapplication/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:
- The smallest public request-aware API and lifetime, preserving custom factories.
- Trusted development authorization, separate from representation preference: Accept must never enable diagnostics.
- 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
[-][RFC / Architecture] Integrate opt-in development HTML error pages with request negotiation[/-][+]Define explicit request-aware error response negotiation[/+][-]Define explicit request-aware error response negotiation[/-][+]Define request-aware error response negotiation after 3.0[/+][-]Define request-aware error response negotiation after 3.0[/-][+]Design request-aware response negotiation after 3.0[/+][-]Design request-aware response negotiation after 3.0[/-][+]3.0: negotiate request-aware JSON and HTML responses[/+]