Skip to content

Commit 85fe83c

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 85fe83c

File tree

2 files changed

+335
-1
lines changed

2 files changed

+335
-1
lines changed

src/Illuminate/Container/Container.php

Lines changed: 40 additions & 1 deletion
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->decideAboutConcreteType($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
@@ -674,7 +682,11 @@ protected function resolve($abstract, $parameters = [])
674682
$this->instances[$abstract] = $object;
675683
}
676684

677-
$this->fireResolvingCallbacks($abstract, $object);
685+
// We don't fire "resolving" callbacks for interface bound with class path syntax
686+
// since the callback will fire later, when the bounded concrete is resolving.
687+
if (! $this->isAnInterfaceBoundedWithClassName($abstract)) {
688+
$this->fireResolvingCallbacks($abstract, $object);
689+
}
678690

679691
// Before returning, we will also set the resolved flag to "true" and pop off
680692
// the parameter overrides for this build. After those two things are done
@@ -1269,4 +1281,31 @@ 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 isAnInterfaceBoundedWithClassName($abstract)
1292+
{
1293+
return interface_exists($abstract) && array_key_exists($abstract, $this->boundToClassName);
1294+
}
1295+
1296+
/**
1297+
* Detects what type of concrete is provided for the abstract.
1298+
*
1299+
* @param string $abstract
1300+
* @param \Closure|string|null $concrete
1301+
* @return void
1302+
*/
1303+
protected function decideAboutConcreteType($abstract, $concrete)
1304+
{
1305+
if ($concrete instanceof Closure) {
1306+
unset($this->boundToClassName[$abstract]);
1307+
} else {
1308+
$this->boundToClassName[$abstract] = null;
1309+
}
1310+
}
12721311
}

tests/Container/ContainerTest.php

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

0 commit comments

Comments
 (0)