ContainerTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <?php
  2. namespace Illuminate\Tests\Container;
  3. use Illuminate\Container\Container;
  4. use Illuminate\Container\EntryNotFoundException;
  5. use Illuminate\Contracts\Container\BindingResolutionException;
  6. use Illuminate\Contracts\Container\CircularDependencyException;
  7. use PHPUnit\Framework\TestCase;
  8. use Psr\Container\ContainerExceptionInterface;
  9. use stdClass;
  10. use TypeError;
  11. class ContainerTest extends TestCase
  12. {
  13. protected function tearDown(): void
  14. {
  15. Container::setInstance(null);
  16. }
  17. public function testContainerSingleton()
  18. {
  19. $container = Container::setInstance(new Container);
  20. $this->assertSame($container, Container::getInstance());
  21. Container::setInstance(null);
  22. $container2 = Container::getInstance();
  23. $this->assertInstanceOf(Container::class, $container2);
  24. $this->assertNotSame($container, $container2);
  25. }
  26. public function testClosureResolution()
  27. {
  28. $container = new Container;
  29. $container->bind('name', function () {
  30. return 'Taylor';
  31. });
  32. $this->assertSame('Taylor', $container->make('name'));
  33. }
  34. public function testBindIfDoesntRegisterIfServiceAlreadyRegistered()
  35. {
  36. $container = new Container;
  37. $container->bind('name', function () {
  38. return 'Taylor';
  39. });
  40. $container->bindIf('name', function () {
  41. return 'Dayle';
  42. });
  43. $this->assertSame('Taylor', $container->make('name'));
  44. }
  45. public function testBindIfDoesRegisterIfServiceNotRegisteredYet()
  46. {
  47. $container = new Container;
  48. $container->bind('surname', function () {
  49. return 'Taylor';
  50. });
  51. $container->bindIf('name', function () {
  52. return 'Dayle';
  53. });
  54. $this->assertSame('Dayle', $container->make('name'));
  55. }
  56. public function testSingletonIfDoesntRegisterIfBindingAlreadyRegistered()
  57. {
  58. $container = new Container;
  59. $container->singleton('class', function () {
  60. return new stdClass;
  61. });
  62. $firstInstantiation = $container->make('class');
  63. $container->singletonIf('class', function () {
  64. return new ContainerConcreteStub;
  65. });
  66. $secondInstantiation = $container->make('class');
  67. $this->assertSame($firstInstantiation, $secondInstantiation);
  68. }
  69. public function testSingletonIfDoesRegisterIfBindingNotRegisteredYet()
  70. {
  71. $container = new Container;
  72. $container->singleton('class', function () {
  73. return new stdClass;
  74. });
  75. $container->singletonIf('otherClass', function () {
  76. return new ContainerConcreteStub;
  77. });
  78. $firstInstantiation = $container->make('otherClass');
  79. $secondInstantiation = $container->make('otherClass');
  80. $this->assertSame($firstInstantiation, $secondInstantiation);
  81. }
  82. public function testSharedClosureResolution()
  83. {
  84. $container = new Container;
  85. $container->singleton('class', function () {
  86. return new stdClass;
  87. });
  88. $firstInstantiation = $container->make('class');
  89. $secondInstantiation = $container->make('class');
  90. $this->assertSame($firstInstantiation, $secondInstantiation);
  91. }
  92. public function testScopedClosureResolution()
  93. {
  94. $container = new Container;
  95. $container->scoped('class', function () {
  96. return new stdClass;
  97. });
  98. $firstInstantiation = $container->make('class');
  99. $secondInstantiation = $container->make('class');
  100. $this->assertSame($firstInstantiation, $secondInstantiation);
  101. }
  102. public function testScopedClosureResets()
  103. {
  104. $container = new Container;
  105. $container->scoped('class', function () {
  106. return new stdClass;
  107. });
  108. $firstInstantiation = $container->make('class');
  109. $container->forgetScopedInstances();
  110. $secondInstantiation = $container->make('class');
  111. $this->assertNotSame($firstInstantiation, $secondInstantiation);
  112. }
  113. public function testAutoConcreteResolution()
  114. {
  115. $container = new Container;
  116. $this->assertInstanceOf(ContainerConcreteStub::class, $container->make(ContainerConcreteStub::class));
  117. }
  118. public function testSharedConcreteResolution()
  119. {
  120. $container = new Container;
  121. $container->singleton(ContainerConcreteStub::class);
  122. $var1 = $container->make(ContainerConcreteStub::class);
  123. $var2 = $container->make(ContainerConcreteStub::class);
  124. $this->assertSame($var1, $var2);
  125. }
  126. public function testScopedConcreteResolutionResets()
  127. {
  128. $container = new Container;
  129. $container->scoped(ContainerConcreteStub::class);
  130. $var1 = $container->make(ContainerConcreteStub::class);
  131. $container->forgetScopedInstances();
  132. $var2 = $container->make(ContainerConcreteStub::class);
  133. $this->assertNotSame($var1, $var2);
  134. }
  135. public function testBindFailsLoudlyWithInvalidArgument()
  136. {
  137. $this->expectException(TypeError::class);
  138. $container = new Container;
  139. $concrete = new ContainerConcreteStub;
  140. $container->bind(ContainerConcreteStub::class, $concrete);
  141. }
  142. public function testAbstractToConcreteResolution()
  143. {
  144. $container = new Container;
  145. $container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
  146. $class = $container->make(ContainerDependentStub::class);
  147. $this->assertInstanceOf(ContainerImplementationStub::class, $class->impl);
  148. }
  149. public function testNestedDependencyResolution()
  150. {
  151. $container = new Container;
  152. $container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
  153. $class = $container->make(ContainerNestedDependentStub::class);
  154. $this->assertInstanceOf(ContainerDependentStub::class, $class->inner);
  155. $this->assertInstanceOf(ContainerImplementationStub::class, $class->inner->impl);
  156. }
  157. public function testContainerIsPassedToResolvers()
  158. {
  159. $container = new Container;
  160. $container->bind('something', function ($c) {
  161. return $c;
  162. });
  163. $c = $container->make('something');
  164. $this->assertSame($c, $container);
  165. }
  166. public function testArrayAccess()
  167. {
  168. $container = new Container;
  169. $container['something'] = function () {
  170. return 'foo';
  171. };
  172. $this->assertTrue(isset($container['something']));
  173. $this->assertSame('foo', $container['something']);
  174. unset($container['something']);
  175. $this->assertFalse(isset($container['something']));
  176. }
  177. public function testAliases()
  178. {
  179. $container = new Container;
  180. $container['foo'] = 'bar';
  181. $container->alias('foo', 'baz');
  182. $container->alias('baz', 'bat');
  183. $this->assertSame('bar', $container->make('foo'));
  184. $this->assertSame('bar', $container->make('baz'));
  185. $this->assertSame('bar', $container->make('bat'));
  186. }
  187. public function testAliasesWithArrayOfParameters()
  188. {
  189. $container = new Container;
  190. $container->bind('foo', function ($app, $config) {
  191. return $config;
  192. });
  193. $container->alias('foo', 'baz');
  194. $this->assertEquals([1, 2, 3], $container->make('baz', [1, 2, 3]));
  195. }
  196. public function testBindingsCanBeOverridden()
  197. {
  198. $container = new Container;
  199. $container['foo'] = 'bar';
  200. $container['foo'] = 'baz';
  201. $this->assertSame('baz', $container['foo']);
  202. }
  203. public function testBindingAnInstanceReturnsTheInstance()
  204. {
  205. $container = new Container;
  206. $bound = new stdClass;
  207. $resolved = $container->instance('foo', $bound);
  208. $this->assertSame($bound, $resolved);
  209. }
  210. public function testBindingAnInstanceAsShared()
  211. {
  212. $container = new Container;
  213. $bound = new stdClass;
  214. $container->instance('foo', $bound);
  215. $object = $container->make('foo');
  216. $this->assertSame($bound, $object);
  217. }
  218. public function testResolutionOfDefaultParameters()
  219. {
  220. $container = new Container;
  221. $instance = $container->make(ContainerDefaultValueStub::class);
  222. $this->assertInstanceOf(ContainerConcreteStub::class, $instance->stub);
  223. $this->assertSame('taylor', $instance->default);
  224. }
  225. public function testUnsetRemoveBoundInstances()
  226. {
  227. $container = new Container;
  228. $container->instance('object', new stdClass);
  229. unset($container['object']);
  230. $this->assertFalse($container->bound('object'));
  231. }
  232. public function testBoundInstanceAndAliasCheckViaArrayAccess()
  233. {
  234. $container = new Container;
  235. $container->instance('object', new stdClass);
  236. $container->alias('object', 'alias');
  237. $this->assertTrue(isset($container['object']));
  238. $this->assertTrue(isset($container['alias']));
  239. }
  240. public function testReboundListeners()
  241. {
  242. unset($_SERVER['__test.rebind']);
  243. $container = new Container;
  244. $container->bind('foo', function () {
  245. //
  246. });
  247. $container->rebinding('foo', function () {
  248. $_SERVER['__test.rebind'] = true;
  249. });
  250. $container->bind('foo', function () {
  251. //
  252. });
  253. $this->assertTrue($_SERVER['__test.rebind']);
  254. }
  255. public function testReboundListenersOnInstances()
  256. {
  257. unset($_SERVER['__test.rebind']);
  258. $container = new Container;
  259. $container->instance('foo', function () {
  260. //
  261. });
  262. $container->rebinding('foo', function () {
  263. $_SERVER['__test.rebind'] = true;
  264. });
  265. $container->instance('foo', function () {
  266. //
  267. });
  268. $this->assertTrue($_SERVER['__test.rebind']);
  269. }
  270. public function testReboundListenersOnInstancesOnlyFiresIfWasAlreadyBound()
  271. {
  272. $_SERVER['__test.rebind'] = false;
  273. $container = new Container;
  274. $container->rebinding('foo', function () {
  275. $_SERVER['__test.rebind'] = true;
  276. });
  277. $container->instance('foo', function () {
  278. //
  279. });
  280. $this->assertFalse($_SERVER['__test.rebind']);
  281. }
  282. public function testInternalClassWithDefaultParameters()
  283. {
  284. $this->expectException(BindingResolutionException::class);
  285. $this->expectExceptionMessage('Unresolvable dependency resolving [Parameter #0 [ <required> $first ]] in class Illuminate\Tests\Container\ContainerMixedPrimitiveStub');
  286. $container = new Container;
  287. $container->make(ContainerMixedPrimitiveStub::class, []);
  288. }
  289. public function testBindingResolutionExceptionMessage()
  290. {
  291. $this->expectException(BindingResolutionException::class);
  292. $this->expectExceptionMessage('Target [Illuminate\Tests\Container\IContainerContractStub] is not instantiable.');
  293. $container = new Container;
  294. $container->make(IContainerContractStub::class, []);
  295. }
  296. public function testBindingResolutionExceptionMessageIncludesBuildStack()
  297. {
  298. $this->expectException(BindingResolutionException::class);
  299. $this->expectExceptionMessage('Target [Illuminate\Tests\Container\IContainerContractStub] is not instantiable while building [Illuminate\Tests\Container\ContainerDependentStub].');
  300. $container = new Container;
  301. $container->make(ContainerDependentStub::class, []);
  302. }
  303. public function testBindingResolutionExceptionMessageWhenClassDoesNotExist()
  304. {
  305. $this->expectException(BindingResolutionException::class);
  306. $this->expectExceptionMessage('Target class [Foo\Bar\Baz\DummyClass] does not exist.');
  307. $container = new Container;
  308. $container->build('Foo\Bar\Baz\DummyClass');
  309. }
  310. public function testForgetInstanceForgetsInstance()
  311. {
  312. $container = new Container;
  313. $containerConcreteStub = new ContainerConcreteStub;
  314. $container->instance(ContainerConcreteStub::class, $containerConcreteStub);
  315. $this->assertTrue($container->isShared(ContainerConcreteStub::class));
  316. $container->forgetInstance(ContainerConcreteStub::class);
  317. $this->assertFalse($container->isShared(ContainerConcreteStub::class));
  318. }
  319. public function testForgetInstancesForgetsAllInstances()
  320. {
  321. $container = new Container;
  322. $containerConcreteStub1 = new ContainerConcreteStub;
  323. $containerConcreteStub2 = new ContainerConcreteStub;
  324. $containerConcreteStub3 = new ContainerConcreteStub;
  325. $container->instance('Instance1', $containerConcreteStub1);
  326. $container->instance('Instance2', $containerConcreteStub2);
  327. $container->instance('Instance3', $containerConcreteStub3);
  328. $this->assertTrue($container->isShared('Instance1'));
  329. $this->assertTrue($container->isShared('Instance2'));
  330. $this->assertTrue($container->isShared('Instance3'));
  331. $container->forgetInstances();
  332. $this->assertFalse($container->isShared('Instance1'));
  333. $this->assertFalse($container->isShared('Instance2'));
  334. $this->assertFalse($container->isShared('Instance3'));
  335. }
  336. public function testContainerFlushFlushesAllBindingsAliasesAndResolvedInstances()
  337. {
  338. $container = new Container;
  339. $container->bind('ConcreteStub', function () {
  340. return new ContainerConcreteStub;
  341. }, true);
  342. $container->alias('ConcreteStub', 'ContainerConcreteStub');
  343. $container->make('ConcreteStub');
  344. $this->assertTrue($container->resolved('ConcreteStub'));
  345. $this->assertTrue($container->isAlias('ContainerConcreteStub'));
  346. $this->assertArrayHasKey('ConcreteStub', $container->getBindings());
  347. $this->assertTrue($container->isShared('ConcreteStub'));
  348. $container->flush();
  349. $this->assertFalse($container->resolved('ConcreteStub'));
  350. $this->assertFalse($container->isAlias('ContainerConcreteStub'));
  351. $this->assertEmpty($container->getBindings());
  352. $this->assertFalse($container->isShared('ConcreteStub'));
  353. }
  354. public function testResolvedResolvesAliasToBindingNameBeforeChecking()
  355. {
  356. $container = new Container;
  357. $container->bind('ConcreteStub', function () {
  358. return new ContainerConcreteStub;
  359. }, true);
  360. $container->alias('ConcreteStub', 'foo');
  361. $this->assertFalse($container->resolved('ConcreteStub'));
  362. $this->assertFalse($container->resolved('foo'));
  363. $container->make('ConcreteStub');
  364. $this->assertTrue($container->resolved('ConcreteStub'));
  365. $this->assertTrue($container->resolved('foo'));
  366. }
  367. public function testGetAlias()
  368. {
  369. $container = new Container;
  370. $container->alias('ConcreteStub', 'foo');
  371. $this->assertSame('ConcreteStub', $container->getAlias('foo'));
  372. }
  373. public function testItThrowsExceptionWhenAbstractIsSameAsAlias()
  374. {
  375. $this->expectException('LogicException');
  376. $this->expectExceptionMessage('[name] is aliased to itself.');
  377. $container = new Container;
  378. $container->alias('name', 'name');
  379. }
  380. public function testContainerGetFactory()
  381. {
  382. $container = new Container;
  383. $container->bind('name', function () {
  384. return 'Taylor';
  385. });
  386. $factory = $container->factory('name');
  387. $this->assertEquals($container->make('name'), $factory());
  388. }
  389. public function testMakeWithMethodIsAnAliasForMakeMethod()
  390. {
  391. $mock = $this->getMockBuilder(Container::class)
  392. ->onlyMethods(['make'])
  393. ->getMock();
  394. $mock->expects($this->once())
  395. ->method('make')
  396. ->with(ContainerDefaultValueStub::class, ['default' => 'laurence'])
  397. ->willReturn(new stdClass);
  398. $result = $mock->makeWith(ContainerDefaultValueStub::class, ['default' => 'laurence']);
  399. $this->assertInstanceOf(stdClass::class, $result);
  400. }
  401. public function testResolvingWithArrayOfParameters()
  402. {
  403. $container = new Container;
  404. $instance = $container->make(ContainerDefaultValueStub::class, ['default' => 'adam']);
  405. $this->assertSame('adam', $instance->default);
  406. $instance = $container->make(ContainerDefaultValueStub::class);
  407. $this->assertSame('taylor', $instance->default);
  408. $container->bind('foo', function ($app, $config) {
  409. return $config;
  410. });
  411. $this->assertEquals([1, 2, 3], $container->make('foo', [1, 2, 3]));
  412. }
  413. public function testResolvingWithUsingAnInterface()
  414. {
  415. $container = new Container;
  416. $container->bind(IContainerContractStub::class, ContainerInjectVariableStubWithInterfaceImplementation::class);
  417. $instance = $container->make(IContainerContractStub::class, ['something' => 'laurence']);
  418. $this->assertSame('laurence', $instance->something);
  419. }
  420. public function testNestedParameterOverride()
  421. {
  422. $container = new Container;
  423. $container->bind('foo', function ($app, $config) {
  424. return $app->make('bar', ['name' => 'Taylor']);
  425. });
  426. $container->bind('bar', function ($app, $config) {
  427. return $config;
  428. });
  429. $this->assertEquals(['name' => 'Taylor'], $container->make('foo', ['something']));
  430. }
  431. public function testNestedParametersAreResetForFreshMake()
  432. {
  433. $container = new Container;
  434. $container->bind('foo', function ($app, $config) {
  435. return $app->make('bar');
  436. });
  437. $container->bind('bar', function ($app, $config) {
  438. return $config;
  439. });
  440. $this->assertEquals([], $container->make('foo', ['something']));
  441. }
  442. public function testSingletonBindingsNotRespectedWithMakeParameters()
  443. {
  444. $container = new Container;
  445. $container->singleton('foo', function ($app, $config) {
  446. return $config;
  447. });
  448. $this->assertEquals(['name' => 'taylor'], $container->make('foo', ['name' => 'taylor']));
  449. $this->assertEquals(['name' => 'abigail'], $container->make('foo', ['name' => 'abigail']));
  450. }
  451. public function testCanBuildWithoutParameterStackWithNoConstructors()
  452. {
  453. $container = new Container;
  454. $this->assertInstanceOf(ContainerConcreteStub::class, $container->build(ContainerConcreteStub::class));
  455. }
  456. public function testCanBuildWithoutParameterStackWithConstructors()
  457. {
  458. $container = new Container;
  459. $container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
  460. $this->assertInstanceOf(ContainerDependentStub::class, $container->build(ContainerDependentStub::class));
  461. }
  462. public function testContainerKnowsEntry()
  463. {
  464. $container = new Container;
  465. $container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
  466. $this->assertTrue($container->has(IContainerContractStub::class));
  467. }
  468. public function testContainerCanBindAnyWord()
  469. {
  470. $container = new Container;
  471. $container->bind('Taylor', stdClass::class);
  472. $this->assertInstanceOf(stdClass::class, $container->get('Taylor'));
  473. }
  474. public function testContainerCanDynamicallySetService()
  475. {
  476. $container = new Container;
  477. $this->assertFalse(isset($container['name']));
  478. $container['name'] = 'Taylor';
  479. $this->assertTrue(isset($container['name']));
  480. $this->assertSame('Taylor', $container['name']);
  481. }
  482. public function testUnknownEntryThrowsException()
  483. {
  484. $this->expectException(EntryNotFoundException::class);
  485. $container = new Container;
  486. $container->get('Taylor');
  487. }
  488. public function testBoundEntriesThrowsContainerExceptionWhenNotResolvable()
  489. {
  490. $this->expectException(ContainerExceptionInterface::class);
  491. $container = new Container;
  492. $container->bind('Taylor', IContainerContractStub::class);
  493. $container->get('Taylor');
  494. }
  495. public function testContainerCanResolveClasses()
  496. {
  497. $container = new Container;
  498. $class = $container->get(ContainerConcreteStub::class);
  499. $this->assertInstanceOf(ContainerConcreteStub::class, $class);
  500. }
  501. // public function testContainerCanCatchCircularDependency()
  502. // {
  503. // $this->expectException(CircularDependencyException::class);
  504. // $container = new Container;
  505. // $container->get(CircularAStub::class);
  506. // }
  507. }
  508. class CircularAStub
  509. {
  510. public function __construct(CircularBStub $b)
  511. {
  512. //
  513. }
  514. }
  515. class CircularBStub
  516. {
  517. public function __construct(CircularCStub $c)
  518. {
  519. //
  520. }
  521. }
  522. class CircularCStub
  523. {
  524. public function __construct(CircularAStub $a)
  525. {
  526. //
  527. }
  528. }
  529. class ContainerConcreteStub
  530. {
  531. //
  532. }
  533. interface IContainerContractStub
  534. {
  535. //
  536. }
  537. class ContainerImplementationStub implements IContainerContractStub
  538. {
  539. //
  540. }
  541. class ContainerImplementationStubTwo implements IContainerContractStub
  542. {
  543. //
  544. }
  545. class ContainerDependentStub
  546. {
  547. public $impl;
  548. public function __construct(IContainerContractStub $impl)
  549. {
  550. $this->impl = $impl;
  551. }
  552. }
  553. class ContainerNestedDependentStub
  554. {
  555. public $inner;
  556. public function __construct(ContainerDependentStub $inner)
  557. {
  558. $this->inner = $inner;
  559. }
  560. }
  561. class ContainerDefaultValueStub
  562. {
  563. public $stub;
  564. public $default;
  565. public function __construct(ContainerConcreteStub $stub, $default = 'taylor')
  566. {
  567. $this->stub = $stub;
  568. $this->default = $default;
  569. }
  570. }
  571. class ContainerMixedPrimitiveStub
  572. {
  573. public $first;
  574. public $last;
  575. public $stub;
  576. public function __construct($first, ContainerConcreteStub $stub, $last)
  577. {
  578. $this->stub = $stub;
  579. $this->last = $last;
  580. $this->first = $first;
  581. }
  582. }
  583. class ContainerInjectVariableStub
  584. {
  585. public $something;
  586. public function __construct(ContainerConcreteStub $concrete, $something)
  587. {
  588. $this->something = $something;
  589. }
  590. }
  591. class ContainerInjectVariableStubWithInterfaceImplementation implements IContainerContractStub
  592. {
  593. public $something;
  594. public function __construct(ContainerConcreteStub $concrete, $something)
  595. {
  596. $this->something = $something;
  597. }
  598. }