feat(container): add scoped bindings - #2279
Open
osbre wants to merge 1 commit into
Open
Conversation
osbre
requested review from
aidan-casey,
brendt and
innocenzi
as code owners
September 6, 2026 12:37
Benchmark ResultsComparison of Open to see the benchmark results
Generated by phpbench against commit a9fdc72 |
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.
Adds a new
scopedlifetime (Container::scoped()and#[Scoped]) for dependencies that live for a single request or lifecycle, replacing the manualRouterResetworkaround from #2278.Problem
Tempest previously supported only transient and singleton lifetimes. State that is valid for a single request or job - such as
MatchedRoute, tenant contexts, or authenticated users - had no native lifetime representation.This resulted in cross-request leaks or clumsy workarounds, such as
RouterResetand customResettableimplementations, forcing every package holding per-request state to write its own manual cleanup logic.Solution
Container::reset()is called.#[Scoped]is supported everywhere#[Singleton]is: on autowired classes, on initializer classes, and oninitialize()methods, including the tagged variant#[Scoped(tag: 'web')].singleton()clears its scoped registration, so an explicit singleton always survives a reset.container:showlists scoped bindings under their own section, instead of mixing them into the singletons.MatchRouteMiddlewarenow bindsMatchedRouteas scoped, andRouterResethas been deleted.docs/1-essentials/05-container.md.Design Choices
scoped()and#[Scoped]declarations rather than hiding behavior behind boolean flags on singleton methods.Container::reset()is still only triggered viaFrameworkKernel::shutdown(). This PR introduces the binding mechanism, leaving automatic scope creation per request/job for a future update. This is also whyMatchRouteMiddlewarekeeps theunregister()call from fix(router): clear the matched route between requests #2278: two requests within a single test share a container without a reset in between.Tests
Added 8 tests in
ContainerTestcovering:#[Scoped]attribute#[Scoped]initializerPlus one integration test asserting
container:showlists scoped bindings separately.