Negotiate error handler content type on every request - #3464
Conversation
The ErrorHandler cached the content type negotiated from the first request's Accept header and reused it for the lifetime of the handler instance. Applications sharing one handler across requests (long-running worker runtimes such as Swoole, RoadRunner or Laravel Octane) kept serving error responses in the first request's format regardless of later clients' Accept headers. Recompute the content type from each request unless forceContentType() was called, which keeps its existing override semantics.
|
Thanks for the PR. I think the bigger question here is whether Slim should support reusing the same application object graph across multiple requests at all. Slim traditionally follows PHP shared-nothing request lifecycle. Long-running workers like RoadRunner, Swoole or Octane change that model and can keep the same objects alive between requests. If we want to officially support that lifecycle, changing only If we want to support persistent workers, I think we need to look at this more broadly and decide what kind of request reuse Slim should support. If this is not a lifecycle Slim intends to support, then I don't think we should start fixing individual cases for it here. Also this patch changes the behavior of @akrabat What do you think? |
|
The The other fields on
This PR doesn't try to make Slim a worker framework. It splits "forced" from "already negotiated" so the documented force API still works when the handler instance outlives one request. |
|
Seems like a logical change to me. We used to determine content type on each invoke before forceContentType() was introduced, so we regressed that when #2751 was merged. Admittedly, given that that was 7 years ago, it's not been a problem :) As far as supporting long-running PHP runtimes, I think that if we can make Slim do so without breaking BC, that's a good thing to aim for, and we should do it piecemeal with small reviewable changes wherever possible. |
I'm happy with this PR once this change is done. |
On 4.x, null is the unforced sentinel, so forceContentType(null) re-enables Accept-header negotiation. Keep that: a null argument clears $isContentTypeForced instead of pinning negotiation off.
|
Done. |
The ErrorHandler caches the content type negotiated from the first request's Accept header (
$this->contentTypeis set only when currently null) and reuses it for the lifetime of the handler instance. Applications that share oneErrorHandleracross requests, such as long-running worker runtimes (Swoole, RoadRunner, Laravel Octane) where the container-built handler persists, keep serving error responses in the first request's negotiated format regardless of later clients' Accept headers. A single JSON client pins every subsequent browser or XML client to JSON error pages until process restart.This change recomputes the content type from each incoming request. The existing override semantics are preserved: once
forceContentType()is called, the forced value wins for all subsequent requests.