Skip to content

Commit da25f0a

Browse files
committed
Add missing tests for the resolving callbacks on the container
+ bug fix related to multiple calls to resolving callbacks
1 parent d329797 commit da25f0a

File tree

2 files changed

+228
-4
lines changed

2 files changed

+228
-4
lines changed

src/Illuminate/Container/Container.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ class Container implements ArrayAccess, ContainerContract
133133
*/
134134
protected $afterResolvingCallbacks = [];
135135

136+
/**
137+
* All the abstract keys which are bound with class names.
138+
*
139+
* @var array
140+
*/
141+
protected $boundToClassName = [];
142+
136143
/**
137144
* Define a contextual binding.
138145
*
@@ -234,6 +241,7 @@ public function bind($abstract, $concrete = null, $shared = false)
234241
// bound into this container to the abstract type and we will just wrap it
235242
// up inside its own Closure to give us more convenience when extending.
236243
if (! $concrete instanceof Closure) {
244+
$this->boundToClassName[$abstract] = null;
237245
$concrete = $this->getClosure($abstract, $concrete);
238246
}
239247

@@ -1011,9 +1019,13 @@ protected function fireResolvingCallbacks($abstract, $object)
10111019
{
10121020
$this->fireCallbackArray($object, $this->globalResolvingCallbacks);
10131021

1014-
$this->fireCallbackArray(
1015-
$object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
1016-
);
1022+
// We do not fire "resolving" callbacks for interface bound with class path syntax
1023+
// since the callback will fire later, when the bounded class will is resolving
1024+
if (! $this->interfaceBoundedToClass($abstract)) {
1025+
$this->fireCallbackArray(
1026+
$object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
1027+
);
1028+
}
10171029

10181030
$this->fireAfterResolvingCallbacks($abstract, $object);
10191031
}
@@ -1137,7 +1149,7 @@ public function forgetExtenders($abstract)
11371149
*/
11381150
protected function dropStaleInstances($abstract)
11391151
{
1140-
unset($this->instances[$abstract], $this->aliases[$abstract]);
1152+
unset($this->instances[$abstract], $this->aliases[$abstract], $this->boundToClassName[$abstract]);
11411153
}
11421154

11431155
/**
@@ -1269,4 +1281,15 @@ public function __set($key, $value)
12691281
{
12701282
$this[$key] = $value;
12711283
}
1284+
1285+
/**
1286+
* Determine if the abstract is both an interface and is bounded to a class path (not a closure).
1287+
*
1288+
* @param string $abstract
1289+
* @return bool
1290+
*/
1291+
protected function interfaceBoundedToClass($abstract)
1292+
{
1293+
return interface_exists($abstract) && array_key_exists($abstract, $this->boundToClassName);
1294+
}
12721295
}

tests/Container/ContainerTest.php

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,207 @@ public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases()
10221022
$this->assertEquals('taylor', $instance->name);
10231023
}
10241024

1025+
public function testResolvingCallbacksAreCalledOnceForImplementation()
1026+
{
1027+
$container = new Container;
1028+
1029+
$callCounter = 0;
1030+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1031+
$callCounter++;
1032+
});
1033+
1034+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1035+
1036+
$container->make(ContainerImplementationStub::class);
1037+
$this->assertEquals(1, $callCounter);
1038+
1039+
$container->make(ContainerImplementationStub::class);
1040+
$this->assertEquals(2, $callCounter);
1041+
}
1042+
1043+
public function testResolvingCallbacksAreCalledOnceForStringAbstractions()
1044+
{
1045+
$container = new Container;
1046+
1047+
$callCounter = 0;
1048+
$container->resolving('foo', function ($some) use (&$callCounter) {
1049+
$callCounter++;
1050+
});
1051+
1052+
$container->bind('foo', ContainerImplementationStub::class);
1053+
1054+
$container->make('foo');
1055+
$this->assertEquals(1, $callCounter);
1056+
1057+
$container->make('foo');
1058+
$this->assertEquals(2, $callCounter);
1059+
}
1060+
1061+
public function testResolvingCallbacksAreCalledOnceForImplementation2()
1062+
{
1063+
$container = new Container;
1064+
1065+
$callCounter = 0;
1066+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1067+
$callCounter++;
1068+
});
1069+
1070+
$container->bind(IContainerContractStub::class, function () {
1071+
return new ContainerImplementationStub;
1072+
});
1073+
1074+
$container->make(IContainerContractStub::class);
1075+
$this->assertEquals(1, $callCounter);
1076+
1077+
$container->make(ContainerImplementationStub::class);
1078+
$this->assertEquals(2, $callCounter);
1079+
1080+
$container->make(ContainerImplementationStub::class);
1081+
$this->assertEquals(3, $callCounter);
1082+
1083+
$container->make(IContainerContractStub::class);
1084+
$this->assertEquals(4, $callCounter);
1085+
}
1086+
1087+
public function testRebindingDoesNotAffectResolvingCallbacks()
1088+
{
1089+
$container = new Container;
1090+
1091+
$callCounter = 0;
1092+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1093+
$callCounter++;
1094+
});
1095+
1096+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1097+
$container->bind(IContainerContractStub::class, function () {
1098+
return new ContainerImplementationStub;
1099+
});
1100+
1101+
$container->make(IContainerContractStub::class);
1102+
$this->assertEquals(1, $callCounter);
1103+
1104+
$container->make(ContainerImplementationStub::class);
1105+
$this->assertEquals(2, $callCounter);
1106+
1107+
$container->make(ContainerImplementationStub::class);
1108+
$this->assertEquals(3, $callCounter);
1109+
1110+
$container->make(IContainerContractStub::class);
1111+
$this->assertEquals(4, $callCounter);
1112+
}
1113+
1114+
public function testRebindingDoesNotAffectMultipleResolvingCallbacks()
1115+
{
1116+
$container = new Container;
1117+
1118+
$callCounter = 0;
1119+
1120+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1121+
$callCounter++;
1122+
});
1123+
1124+
$container->resolving(ContainerImplementationStubTwo::class, function ($some) use (&$callCounter) {
1125+
$callCounter++;
1126+
});
1127+
1128+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1129+
1130+
// it should call the callback for interface
1131+
$container->make(IContainerContractStub::class);
1132+
$this->assertEquals(1, $callCounter);
1133+
1134+
// it should call the callback for interface
1135+
$container->make(ContainerImplementationStub::class);
1136+
$this->assertEquals(2, $callCounter);
1137+
1138+
// should call the callback for the interface it implements
1139+
// plus the callback for ContainerImplementationStubTwo.
1140+
$container->make(ContainerImplementationStubTwo::class);
1141+
$this->assertEquals(4, $callCounter);
1142+
}
1143+
1144+
public function testResolvingCallbacksAreCalledForInterfaces()
1145+
{
1146+
$container = new Container;
1147+
1148+
$callCounter = 0;
1149+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1150+
$callCounter++;
1151+
});
1152+
1153+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1154+
1155+
$container->make(IContainerContractStub::class);
1156+
1157+
$this->assertEquals(1, $callCounter);
1158+
}
1159+
1160+
public function testResolvingCallbacksAreCalledForConcretesWhenAttachedOnInterface()
1161+
{
1162+
$container = new Container;
1163+
1164+
$callCounter = 0;
1165+
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
1166+
$callCounter++;
1167+
});
1168+
1169+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1170+
1171+
$container->make(IContainerContractStub::class);
1172+
$this->assertEquals(1, $callCounter);
1173+
1174+
$container->make(ContainerImplementationStub::class);
1175+
$this->assertEquals(2, $callCounter);
1176+
}
1177+
1178+
public function testResolvingCallbacksAreCalledForConcretesWhenAttachedOnConcretes()
1179+
{
1180+
$container = new Container;
1181+
1182+
$callCounter = 0;
1183+
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
1184+
$callCounter++;
1185+
});
1186+
1187+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1188+
1189+
$container->make(IContainerContractStub::class);
1190+
$this->assertEquals(1, $callCounter);
1191+
1192+
$container->make(ContainerImplementationStub::class);
1193+
$this->assertEquals(2, $callCounter);
1194+
}
1195+
1196+
public function testResolvingCallbacksAreCalledForConcretesWithNoBinding()
1197+
{
1198+
$container = new Container;
1199+
1200+
$callCounter = 0;
1201+
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
1202+
$callCounter++;
1203+
});
1204+
1205+
$container->make(ContainerImplementationStub::class);
1206+
$this->assertEquals(1, $callCounter);
1207+
$container->make(ContainerImplementationStub::class);
1208+
$this->assertEquals(2, $callCounter);
1209+
}
1210+
1211+
public function testResolvingCallbacksAreCalledForInterFacesWithNoBinding()
1212+
{
1213+
$container = new Container;
1214+
1215+
$callCounter = 0;
1216+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1217+
$callCounter++;
1218+
});
1219+
1220+
$container->make(ContainerImplementationStub::class);
1221+
$this->assertEquals(1, $callCounter);
1222+
$container->make(ContainerImplementationStub::class);
1223+
$this->assertEquals(2, $callCounter);
1224+
}
1225+
10251226
public function testMakeWithMethodIsAnAliasForMakeMethod()
10261227
{
10271228
$mock = $this->getMockBuilder(Container::class)

0 commit comments

Comments
 (0)