Repository navigation
fix: forget scoped instances between requests in LaravelHttpServer - #295
Merged
Merged
Conversation
Member
|
Could you put scoped-instance cleanup in a finally block covering request handling, termination, and streamed content? I reproduced a leak when a stream callback resolves a scoped binding and then throws: the response returns 500, but the instance remains cached because execution never reaches forgetScopedInstancesSince(). Please add a regression test for this path. |
The server handles every request of a test on the same container and never forgets its scoped instances, so a scoped() binding resolved in one request is reused by the next one, like a singleton. After each request, forget the scoped instances it resolved and clear the facade cache, as the queue worker does between jobs. Instances the test bound before the request, such as a mock, stay bound, as they do with $this->get(). Fixes pestphp/pest#1986
ChrisAbner
force-pushed
the
fix/flush-scoped-instances
branch
from
October 27, 2026 21:28
8a47949 to
37ddb38
Compare
Contributor
Author
|
Done in 37ddb38. The cleanup now runs in a |
Member
|
Thanks |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LaravelHttpServer::handleRequest()serves every request of a browser test on the test's own container and never forgets its scoped instances, so ascoped()binding resolved in one request is reused by the next one, like a singleton. This is what pestphp/pest#1986 hit with Inertia'sSsrState, and it affects any scoped service, including Laravel's ownContext. After each request, this forgets the scoped instances that request resolved and clears the facade cache, as the queue worker does between jobs. Fixes pestphp/pest#1986.#234 by @onlime proposed
app()->forgetScopedInstances()at the start of each request. It targets4.x, no longer applies on5.x, and has two gaps. It also drops what the test bound beforevisit(): a$this->mock()on a scoped binding never reaches the request, though it does with$this->get(). And a scoped service used through a facade, such asContext, still leaks through the facade's cached instance. So I compare the container's scoped instances before and after the request and forget only the ones the request created. Laravel has no public getter for which bindings are scoped, soscopedInstances()reads them through a closure bound to the container, using the same logic asContainer::forgetScopedInstances(). The forget runs in afinallyaround handling, terminate and the streamed content, so it also happens when any of them throws.Verification. I added six tests to
LaravelHttpServerTest.php:hits: 2, binding built oncehits: 1both times, built twiceforgetScopedInstances()alone it fails)Context::add()in one request, read in the nextterminating()callback resolves a scoped binding, then throwspest --parallel)The suite,
rector --dry-run,pint --test,phpstan, profanity and type coverage (100%) pass on both PHP 8.4 and 8.5, with Laravel 13.38 and Playwright 1.63 (Chromium). I also checked these cases with throwaway tests that are not in the PR. A streamed response, ascoped()declared with a typed closure,$this->mock()through Mockery, a facade mock on a scoped accessor, a mock bound between two visits, andHttp::fake()after the facade cache is cleared all behave as expected.4.xhas the same bug. Ported by hand, the first version of this change passes the first four tests there on Laravel 12.70.I also checked pestphp/pest#1986's own case with the real inertia-laravel
SsrState(SSR on, a fake gateway in place of the Node server): on5.xwith inertia-laravel 3.1.1, the secondvisit()in a test renders the first page's HTML, and with this change it renders the right one. inertia-laravel 3.5.2 (inertiajs/inertia-laravel#924) resets that cached response itself, so on newer versions the Inertia symptom is gone either way. The scoped instances, and with themContext, still leak without this change.Not covered: a scoped instance the test resolves itself before
visit()counts as the test's own, so it stays shared across requests, as with$this->get(). After a request, the test body gets a fresh instance rather than the one the request used.