Skip to content

[13.x] Prevent duplicate scoped instance registrations - #61251

Merged
taylorotwell merged 1 commit into
laravel:13.xfrom
jordanhavard:fix/container-scoped-duplicate-instances
Aug 20, 2026
Merged

taylorotwell merged 1 commit into
laravel:13.xfrom
jordanhavard:fix/container-scoped-duplicate-instances

Conversation

@jordanhavard

Copy link
Copy Markdown
Contributor

Container::scoped() appends to $scopedInstances unconditionally, so registering the same abstract more than once records it more than once.

$container->scoped('foo', fn () => new stdClass);
$container->scoped('foo', fn () => new stdClass);

// $scopedInstances is now ['foo', 'foo']

scopedIf() avoids this while the abstract stays bound, but not once the binding is removed: offsetUnset() clears bindings, instances and resolved and leaves scopedInstances alone, so scopedIf() sees the binding as gone and re-registers through scoped(). Anywhere that happens repeatedly in a long-lived process, the array grows without bound and every forgetScopedInstances() walks the duplicates.

resolve() already guards the equivalent append for the #[Scoped] attribute, added in #56334. This applies the same guard to scoped(). Behaviour is unchanged, since forgetScopedInstances() unsets an instance by abstract and unsetting the same one twice was already a no-op.

`Container::scoped()` appends to `$scopedInstances` unconditionally, so an
abstract registered more than once is recorded more than once and every later
`forgetScopedInstances()` walks the duplicates.

In a long-lived process this is unbounded. Removing a scoped binding and
letting it be registered again is the ordinary way to rebuild one, because
`Container::offsetUnset()` clears `bindings`, `instances` and `resolved` but
not `scopedInstances`, so `scopedIf()` sees the binding gone and re-registers
through `scoped()`. Each cycle adds another entry that is never removed.

`Container::resolve()` already guards the equivalent append for the `Scoped`
attribute, added in laravel#56334. This applies the same guard on the main path.

Deduplicating is behaviour-preserving: `forgetScopedInstances()` unsets an
instance by abstract, so unsetting the same abstract twice was already a
no-op.
@taylorotwell
taylorotwell merged commit d7ffd7d into laravel:13.x Aug 20, 2026
56 checks passed
@jordanhavard
jordanhavard deleted the fix/container-scoped-duplicate-instances branch August 20, 2026 10:02
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.

2 participants