Skip to content

Commit 9661ee1

Browse files
committed
Add missing tests for the resolving callbacks on the container
+ bug fix related to multiple calls to "resolving" and "afterResolving" callbacks
1 parent d329797 commit 9661ee1

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed

src/Illuminate/Container/Container.php

Lines changed: 34 additions & 0 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
*
@@ -222,6 +229,7 @@ public function isAlias($name)
222229
public function bind($abstract, $concrete = null, $shared = false)
223230
{
224231
$this->dropStaleInstances($abstract);
232+
$this->decideAboutConcrete($abstract, $concrete);
225233

226234
// If no concrete type was given, we will simply set the concrete type to the
227235
// abstract type. After that, the concrete type to be registered as shared
@@ -1045,6 +1053,12 @@ protected function fireAfterResolvingCallbacks($abstract, $object)
10451053
*/
10461054
protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
10471055
{
1056+
// We don't fire "resolving" callbacks for interface bound with class path syntax
1057+
// since the callback will fire later, when the bounded concrete is resolving.
1058+
if ($this->isAnInterfaceBoundedWithClassName($abstract)) {
1059+
return [];
1060+
}
1061+
10481062
$results = [];
10491063

10501064
foreach ($callbacksPerType as $type => $callbacks) {
@@ -1269,4 +1283,24 @@ public function __set($key, $value)
12691283
{
12701284
$this[$key] = $value;
12711285
}
1286+
1287+
/**
1288+
* Determine if the abstract is both an interface and is bounded to a class path (not a closure).
1289+
*
1290+
* @param string $abstract
1291+
* @return bool
1292+
*/
1293+
protected function isAnInterfaceBoundedWithClassName($abstract)
1294+
{
1295+
return interface_exists($abstract) && array_key_exists($abstract, $this->boundToClassName);
1296+
}
1297+
1298+
protected function decideAboutConcrete($abstract, $concrete)
1299+
{
1300+
if ($concrete instanceof Closure) {
1301+
unset($this->boundToClassName[$abstract]);
1302+
} else {
1303+
$this->boundToClassName[$abstract] = null;
1304+
}
1305+
}
12721306
}

tests/Container/ContainerTest.php

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,283 @@ 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 testAfterResolvingCallbacksAreCalledOnceForImplementation()
1044+
{
1045+
$container = new Container;
1046+
1047+
$callCounter = 0;
1048+
$container->afterResolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1049+
$callCounter++;
1050+
});
1051+
1052+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1053+
1054+
$container->make(ContainerImplementationStub::class);
1055+
$this->assertEquals(1, $callCounter);
1056+
1057+
$container->make(IContainerContractStub::class);
1058+
$this->assertEquals(2, $callCounter);
1059+
}
1060+
1061+
public function testResolvingCallbacksAreCalledOnceForSingletonConcretes()
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, ContainerImplementationStub::class);
1071+
$container->bind(ContainerImplementationStub::class);
1072+
1073+
$container->make(ContainerImplementationStub::class);
1074+
$this->assertEquals(1, $callCounter);
1075+
1076+
$container->make(ContainerImplementationStub::class);
1077+
$this->assertEquals(2, $callCounter);
1078+
1079+
$container->make(IContainerContractStub::class);
1080+
$this->assertEquals(3, $callCounter);
1081+
}
1082+
1083+
public function testResolvingCallbacksCanStillBeAddedAfterTheFirstResolution()
1084+
{
1085+
$container = new Container;
1086+
1087+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1088+
1089+
$container->make(ContainerImplementationStub::class);
1090+
1091+
$callCounter = 0;
1092+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1093+
$callCounter++;
1094+
});
1095+
1096+
$container->make(ContainerImplementationStub::class);
1097+
$this->assertEquals(1, $callCounter);
1098+
}
1099+
1100+
public function testResolvingCallbacksAreCanceledWhenInterfaceGetsBoundToSomeOtherConcrete()
1101+
{
1102+
$container = new Container;
1103+
1104+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1105+
1106+
$callCounter = 0;
1107+
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
1108+
$callCounter++;
1109+
});
1110+
1111+
$container->make(IContainerContractStub::class);
1112+
$this->assertEquals(1, $callCounter);
1113+
1114+
$container->bind(IContainerContractStub::class, ContainerImplementationStubTwo::class);
1115+
$container->make(IContainerContractStub::class);
1116+
$this->assertEquals(1, $callCounter);
1117+
}
1118+
1119+
public function testResolvingCallbacksAreCalledOnceForStringAbstractions()
1120+
{
1121+
$container = new Container;
1122+
1123+
$callCounter = 0;
1124+
$container->resolving('foo', function ($some) use (&$callCounter) {
1125+
$callCounter++;
1126+
});
1127+
1128+
$container->bind('foo', ContainerImplementationStub::class);
1129+
1130+
$container->make('foo');
1131+
$this->assertEquals(1, $callCounter);
1132+
1133+
$container->make('foo');
1134+
$this->assertEquals(2, $callCounter);
1135+
}
1136+
1137+
public function testResolvingCallbacksAreCalledOnceForImplementation2()
1138+
{
1139+
$container = new Container;
1140+
1141+
$callCounter = 0;
1142+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1143+
$callCounter++;
1144+
});
1145+
1146+
$container->bind(IContainerContractStub::class, function () {
1147+
return new ContainerImplementationStub;
1148+
});
1149+
1150+
$container->make(IContainerContractStub::class);
1151+
$this->assertEquals(1, $callCounter);
1152+
1153+
$container->make(ContainerImplementationStub::class);
1154+
$this->assertEquals(2, $callCounter);
1155+
1156+
$container->make(ContainerImplementationStub::class);
1157+
$this->assertEquals(3, $callCounter);
1158+
1159+
$container->make(IContainerContractStub::class);
1160+
$this->assertEquals(4, $callCounter);
1161+
}
1162+
1163+
public function testRebindingDoesNotAffectResolvingCallbacks()
1164+
{
1165+
$container = new Container;
1166+
1167+
$callCounter = 0;
1168+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1169+
$callCounter++;
1170+
});
1171+
1172+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1173+
$container->bind(IContainerContractStub::class, function () {
1174+
return new ContainerImplementationStub;
1175+
});
1176+
1177+
$container->make(IContainerContractStub::class);
1178+
$this->assertEquals(1, $callCounter);
1179+
1180+
$container->make(ContainerImplementationStub::class);
1181+
$this->assertEquals(2, $callCounter);
1182+
1183+
$container->make(ContainerImplementationStub::class);
1184+
$this->assertEquals(3, $callCounter);
1185+
1186+
$container->make(IContainerContractStub::class);
1187+
$this->assertEquals(4, $callCounter);
1188+
}
1189+
1190+
public function testRebindingDoesNotAffectMultipleResolvingCallbacks()
1191+
{
1192+
$container = new Container;
1193+
1194+
$callCounter = 0;
1195+
1196+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1197+
$callCounter++;
1198+
});
1199+
1200+
$container->resolving(ContainerImplementationStubTwo::class, function ($some) use (&$callCounter) {
1201+
$callCounter++;
1202+
});
1203+
1204+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1205+
1206+
// it should call the callback for interface
1207+
$container->make(IContainerContractStub::class);
1208+
$this->assertEquals(1, $callCounter);
1209+
1210+
// it should call the callback for interface
1211+
$container->make(ContainerImplementationStub::class);
1212+
$this->assertEquals(2, $callCounter);
1213+
1214+
// should call the callback for the interface it implements
1215+
// plus the callback for ContainerImplementationStubTwo.
1216+
$container->make(ContainerImplementationStubTwo::class);
1217+
$this->assertEquals(4, $callCounter);
1218+
}
1219+
1220+
public function testResolvingCallbacksAreCalledForInterfaces()
1221+
{
1222+
$container = new Container;
1223+
1224+
$callCounter = 0;
1225+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1226+
$callCounter++;
1227+
});
1228+
1229+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1230+
1231+
$container->make(IContainerContractStub::class);
1232+
1233+
$this->assertEquals(1, $callCounter);
1234+
}
1235+
1236+
public function testResolvingCallbacksAreCalledForConcretesWhenAttachedOnInterface()
1237+
{
1238+
$container = new Container;
1239+
1240+
$callCounter = 0;
1241+
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
1242+
$callCounter++;
1243+
});
1244+
1245+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1246+
1247+
$container->make(IContainerContractStub::class);
1248+
$this->assertEquals(1, $callCounter);
1249+
1250+
$container->make(ContainerImplementationStub::class);
1251+
$this->assertEquals(2, $callCounter);
1252+
}
1253+
1254+
public function testResolvingCallbacksAreCalledForConcretesWhenAttachedOnConcretes()
1255+
{
1256+
$container = new Container;
1257+
1258+
$callCounter = 0;
1259+
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
1260+
$callCounter++;
1261+
});
1262+
1263+
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
1264+
1265+
$container->make(IContainerContractStub::class);
1266+
$this->assertEquals(1, $callCounter);
1267+
1268+
$container->make(ContainerImplementationStub::class);
1269+
$this->assertEquals(2, $callCounter);
1270+
}
1271+
1272+
public function testResolvingCallbacksAreCalledForConcretesWithNoBinding()
1273+
{
1274+
$container = new Container;
1275+
1276+
$callCounter = 0;
1277+
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
1278+
$callCounter++;
1279+
});
1280+
1281+
$container->make(ContainerImplementationStub::class);
1282+
$this->assertEquals(1, $callCounter);
1283+
$container->make(ContainerImplementationStub::class);
1284+
$this->assertEquals(2, $callCounter);
1285+
}
1286+
1287+
public function testResolvingCallbacksAreCalledForInterFacesWithNoBinding()
1288+
{
1289+
$container = new Container;
1290+
1291+
$callCounter = 0;
1292+
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
1293+
$callCounter++;
1294+
});
1295+
1296+
$container->make(ContainerImplementationStub::class);
1297+
$this->assertEquals(1, $callCounter);
1298+
$container->make(ContainerImplementationStub::class);
1299+
$this->assertEquals(2, $callCounter);
1300+
}
1301+
10251302
public function testMakeWithMethodIsAnAliasForMakeMethod()
10261303
{
10271304
$mock = $this->getMockBuilder(Container::class)

0 commit comments

Comments
 (0)