Skip to content

Flush scoped() container bindings between requests in LaravelHttpServer - #234

Open
onlime wants to merge 1 commit into
pestphp:4.xfrom
onlime:fix/flush-scoped-instances-between-requests
Open

Flush scoped() container bindings between requests in LaravelHttpServer#234
onlime wants to merge 1 commit into
pestphp:4.xfrom
onlime:fix/flush-scoped-instances-between-requests

Conversation

@onlime

@onlime onlime commented Jul 17, 2026

Copy link
Copy Markdown

Problem

LaravelHttpServer serves every request of a browser test from one long-lived container — it calls $kernel->handle() / $kernel->terminate() per request, but never $app->forgetScopedInstances(). Since scoped() bindings are only released by that call, a scoped() service resolved in one request survives into the next and behaves like a singleton().

Every other Laravel runtime releases them per unit of work:

  • FPM builds a fresh container per request;
  • Octane flushes scoped instances between requests;
  • even the framework's queue worker does it between jobs (Illuminate\Queue\QueueServiceProvider calls $app->forgetScopedInstances()).

The in-process test server is the only place where a scoped() service leaks — so browser tests exercise a lifetime that exists in no production runtime.

Impact

Symptoms look like flaky, order-dependent tests rather than a container issue, and bite hardest right after an auth change mid-test (login, logout, impersonation): the request after taking an impersonation can resolve the previous user's cached, request-scoped context service and act on the wrong user/team. The failure is silent and only surfaces once an app registers a scoped() binding.

Change

Flush scoped instances at the start of each request, before $kernel->handle(), mirroring Octane:

app()->forgetScopedInstances();

One line, no API change, and a no-op for apps without scoped() bindings.

Test

Added to tests/Unit/Drivers/Laravel/LaravelHttpServerTest.php: register a scoped() binding, visit() a route that renders the resolved object's hash twice, assert the two differ. Fails on 4.x without the change (same instance returned), passes with it.


Targeted at 4.x; if 5.x already flushes here, this is only needed for the 4.x line.

LaravelHttpServer reuses one long-lived container for every request of a browser test, but never calls Application::forgetScopedInstances() — the only thing that releases scoped() bindings. So a scoped() service resolved in one request leaks into the next and behaves like a singleton, unlike every real runtime: FPM builds a fresh container per request, Octane flushes scoped instances between requests, and even the queue worker does so between jobs. Symptoms look like flaky, order-dependent tests (stale user/team context, wrong locale) and bite hardest right after an auth change mid-test such as an impersonation.

Flush scoped instances at the start of each request, before Kernel::handle(), mirroring Octane.
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.

1 participant