Skip to content

Commit bebbf49

Browse files
[12.x] Fix usage of Scoped and Singleton on interfaces (#56620)
* failing test * Update Container.php * fix container binding * style * remove unnecessary coalescence * remove unnecessary * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 0d09b30 commit bebbf49

File tree

2 files changed

+84
-25
lines changed

2 files changed

+84
-25
lines changed

src/Illuminate/Container/Container.php

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -281,21 +281,36 @@ public function isShared($abstract)
281281
return false;
282282
}
283283

284-
$reflection = new ReflectionClass($abstract);
285-
286-
if (! empty($reflection->getAttributes(Singleton::class))) {
287-
return true;
284+
if (($scopedType = $this->getScopedTyped(new ReflectionClass($abstract))) === null) {
285+
return false;
288286
}
289287

290-
if (! empty($reflection->getAttributes(Scoped::class))) {
288+
if ($scopedType === 'scoped') {
291289
if (! in_array($abstract, $this->scopedInstances, true)) {
292290
$this->scopedInstances[] = $abstract;
293291
}
292+
}
294293

295-
return true;
294+
return true;
295+
}
296+
297+
/**
298+
* Determine if a ReflectionClass has scoping attributes applied.
299+
*
300+
* @param ReflectionClass<object> $reflection
301+
* @return "singleton"|"scoped"|null
302+
*/
303+
protected function getScopedTyped(ReflectionClass $reflection): ?string
304+
{
305+
if (! empty($reflection->getAttributes(Singleton::class))) {
306+
return 'singleton';
296307
}
297308

298-
return false;
309+
if (! empty($reflection->getAttributes(Scoped::class))) {
310+
return 'scoped';
311+
}
312+
313+
return null;
299314
}
300315

301316
/**
@@ -983,34 +998,34 @@ protected function getConcrete($abstract)
983998
return $abstract;
984999
}
9851000

986-
$attributes = [];
987-
988-
try {
989-
$attributes = (new ReflectionClass($abstract))->getAttributes(Bind::class);
990-
} catch (ReflectionException) {
991-
}
992-
993-
$this->checkedForAttributeBindings[$abstract] = true;
994-
995-
if ($attributes === []) {
996-
return $abstract;
997-
}
998-
999-
return $this->getConcreteBindingFromAttributes($abstract, $attributes);
1001+
return $this->getConcreteBindingFromAttributes($abstract);
10001002
}
10011003

10021004
/**
10031005
* Get the concrete binding for an abstract from the Bind attribute.
10041006
*
10051007
* @param string $abstract
1006-
* @param array<int, \ReflectionAttribute<Bind>> $reflectedAttributes
10071008
* @return mixed
10081009
*/
1009-
protected function getConcreteBindingFromAttributes($abstract, $reflectedAttributes)
1010+
protected function getConcreteBindingFromAttributes($abstract)
10101011
{
1012+
$this->checkedForAttributeBindings[$abstract] = true;
1013+
1014+
try {
1015+
$reflected = new ReflectionClass($abstract);
1016+
} catch (ReflectionException) {
1017+
return $abstract;
1018+
}
1019+
1020+
$bindAttributes = $reflected->getAttributes(Bind::class);
1021+
1022+
if ($bindAttributes === []) {
1023+
return $abstract;
1024+
}
1025+
10111026
$concrete = $maybeConcrete = null;
10121027

1013-
foreach ($reflectedAttributes as $reflectedAttribute) {
1028+
foreach ($bindAttributes as $reflectedAttribute) {
10141029
$instance = $reflectedAttribute->newInstance();
10151030

10161031
if ($instance->environments === ['*']) {
@@ -1034,7 +1049,11 @@ protected function getConcreteBindingFromAttributes($abstract, $reflectedAttribu
10341049
return $abstract;
10351050
}
10361051

1037-
$this->bind($abstract, $concrete);
1052+
match ($this->getScopedTyped($reflected)) {
1053+
'scoped' => $this->scoped($abstract, $concrete),
1054+
'singleton' => $this->singleton($abstract, $concrete),
1055+
null => $this->bind($abstract, $concrete),
1056+
};
10381057

10391058
return $this->bindings[$abstract]['concrete'];
10401059
}

tests/Container/ContainerTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,30 @@ public function testNoMatchingEnvironmentAndNoWildcardThrowsBindingResolutionExc
875875
$container->make(ProdEnvOnlyInterface::class);
876876
}
877877

878+
public function testScopedSingletonWithBind()
879+
{
880+
$container = new Container;
881+
$container->resolveEnvironmentUsing(fn ($environments) => true);
882+
883+
$original = $container->make(IsScoped::class);
884+
$new = $container->make(IsScoped::class);
885+
886+
$this->assertSame($original, $new);
887+
$container->forgetScopedInstances();
888+
$this->assertNotSame($original, $container->make(IsScoped::class));
889+
}
890+
891+
public function testSingletonWithBind()
892+
{
893+
$container = new Container;
894+
$container->resolveEnvironmentUsing(fn ($environments) => true);
895+
896+
$original = $container->make(IsSingleton::class);
897+
$new = $container->make(IsSingleton::class);
898+
899+
$this->assertSame($original, $new);
900+
}
901+
878902
// public function testContainerCanCatchCircularDependency()
879903
// {
880904
// $this->expectException(\Illuminate\Contracts\Container\CircularDependencyException::class);
@@ -1131,3 +1155,19 @@ class OriginalConcrete implements OverrideInterface
11311155
class AltConcrete implements OverrideInterface
11321156
{
11331157
}
1158+
1159+
#[Bind(IsScopedConcrete::class)]
1160+
#[Scoped]
1161+
interface IsScoped
1162+
{
1163+
}
1164+
1165+
class IsScopedConcrete implements IsScoped
1166+
{
1167+
}
1168+
1169+
#[Bind(IsScopedConcrete::class)]
1170+
#[Singleton]
1171+
interface IsSingleton
1172+
{
1173+
}

0 commit comments

Comments
 (0)