From d0a7adb751009fbc4e3af4fa88bfd8e438bab767 Mon Sep 17 00:00:00 2001 From: imanghafoori Date: Fri, 4 Jan 2019 10:42:46 +0330 Subject: [PATCH] Add missing tests for the resolving callbacks on the container + bug fix related to multiple calls to "resolving" and "afterResolving" callbacks --- src/Illuminate/Container/Container.php | 11 +- tests/Container/ContainerTest.php | 376 +++++++++++++++++++++++++ 2 files changed, 383 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 0f1becdfdc28..e1a1cfb22447 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -261,7 +261,8 @@ protected function getClosure($abstract, $concrete) return $container->build($concrete); } - return $container->make($concrete, $parameters); + // To prevent extra call to resolving callbacks, we make the object silently. + return $container->resolve($concrete, $parameters, true); }; } @@ -630,9 +631,10 @@ public function get($id) * * @param string $abstract * @param array $parameters + * @param bool $silent * @return mixed */ - protected function resolve($abstract, $parameters = []) + protected function resolve($abstract, $parameters = [], $silent = false) { $abstract = $this->getAlias($abstract); @@ -674,8 +676,9 @@ protected function resolve($abstract, $parameters = []) $this->instances[$abstract] = $object; } - $this->fireResolvingCallbacks($abstract, $object); - + if (! $silent) { + $this->fireResolvingCallbacks($abstract, $object); + } // Before returning, we will also set the resolved flag to "true" and pop off // the parameter overrides for this build. After those two things are done // we will be ready to return back the fully constructed class instance. diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index d54d71dc000b..ceea64717a71 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -1022,6 +1022,382 @@ public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases() $this->assertEquals('taylor', $instance->name); } + public function testResolvingCallbacksAreCalledOnceForImplementation() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + } + + public function testGlobalResolvingCallbacksAreCalledOnceForImplementation() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(IContainerContractStub::class); + $this->assertEquals(2, $callCounter); + } + + public function testAfterResolvingCallbacksAreCalledOnceForImplementation() + { + $container = new Container; + + $callCounter = 0; + $container->afterResolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(IContainerContractStub::class); + $this->assertEquals(2, $callCounter); + } + + public function testResolvingCallbacksAreCalledOnceForSingletonConcretes() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + $container->bind(ContainerImplementationStub::class); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + + $container->make(IContainerContractStub::class); + $this->assertEquals(3, $callCounter); + } + + public function testResolvingCallbacksCanStillBeAddedAfterTheFirstResolution() + { + $container = new Container; + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(ContainerImplementationStub::class); + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + } + + public function testResolvingCallbacksAreCanceledWhenInterfaceGetsBoundToSomeOtherConcrete() + { + $container = new Container; + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $callCounter = 0; + $container->resolving(ContainerImplementationStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + + $container->bind(IContainerContractStub::class, ContainerImplementationStubTwo::class); + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + } + + public function testResolvingCallbacksAreCalledOnceForStringAbstractions() + { + $container = new Container; + + $callCounter = 0; + $container->resolving('foo', function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind('foo', ContainerImplementationStub::class); + + $container->make('foo'); + $this->assertEquals(1, $callCounter); + + $container->make('foo'); + $this->assertEquals(2, $callCounter); + } + + public function testResolvingCallbacksForConcretesAreCalledOnceForStringAbstractions() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(ContainerImplementationStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind('foo', ContainerImplementationStub::class); + $container->bind('bar', ContainerImplementationStub::class); + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + + $container->make('foo'); + $this->assertEquals(2, $callCounter); + + $container->make('bar'); + $this->assertEquals(3, $callCounter); + + $container->make(IContainerContractStub::class); + $this->assertEquals(4, $callCounter); + } + + public function testResolvingCallbacksAreCalledOnceForImplementation2() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, function () { + return new ContainerImplementationStub; + }); + + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(3, $callCounter); + + $container->make(IContainerContractStub::class); + $this->assertEquals(4, $callCounter); + } + + public function testRebindingDoesNotAffectResolvingCallbacks() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + $container->bind(IContainerContractStub::class, function () { + return new ContainerImplementationStub; + }); + + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(3, $callCounter); + + $container->make(IContainerContractStub::class); + $this->assertEquals(4, $callCounter); + } + + public function testParametersPassedIntoResolvingCallbacks() + { + $container = new Container; + + $container->resolving(IContainerContractStub::class, function ($obj, $app) use ($container) { + $this->assertInstanceOf(IContainerContractStub::class, $obj); + $this->assertInstanceOf(ContainerImplementationStubTwo::class, $obj); + $this->assertSame($container, $app); + }); + + $container->afterResolving(IContainerContractStub::class, function ($obj, $app) use ($container) { + $this->assertInstanceOf(IContainerContractStub::class, $obj); + $this->assertInstanceOf(ContainerImplementationStubTwo::class, $obj); + $this->assertSame($container, $app); + }); + + $container->afterResolving(function ($obj, $app) use ($container) { + $this->assertInstanceOf(IContainerContractStub::class, $obj); + $this->assertInstanceOf(ContainerImplementationStubTwo::class, $obj); + $this->assertSame($container, $app); + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStubTwo::class); + $container->make(IContainerContractStub::class); + } + + public function testResolvingCallbacksAreCallWhenRebindHappenForResolvedAbstract() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + + $container->bind(IContainerContractStub::class, ContainerImplementationStubTwo::class); + $this->assertEquals(2, $callCounter); + + $container->make(ContainerImplementationStubTwo::class); + $this->assertEquals(3, $callCounter); + + $container->bind(IContainerContractStub::class, function () { + return new ContainerImplementationStubTwo(); + }); + $this->assertEquals(4, $callCounter); + + $container->make(IContainerContractStub::class); + $this->assertEquals(5, $callCounter); + } + + public function testRebindingDoesNotAffectMultipleResolvingCallbacks() + { + $container = new Container; + + $callCounter = 0; + + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->resolving(ContainerImplementationStubTwo::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + // it should call the callback for interface + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + + // it should call the callback for interface + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + + // should call the callback for the interface it implements + // plus the callback for ContainerImplementationStubTwo. + $container->make(ContainerImplementationStubTwo::class); + $this->assertEquals(4, $callCounter); + } + + public function testResolvingCallbacksAreCalledForInterfaces() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(IContainerContractStub::class); + + $this->assertEquals(1, $callCounter); + } + + public function testResolvingCallbacksAreCalledForConcretesWhenAttachedOnInterface() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(ContainerImplementationStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + } + + public function testResolvingCallbacksAreCalledForConcretesWhenAttachedOnConcretes() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(ContainerImplementationStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->make(IContainerContractStub::class); + $this->assertEquals(1, $callCounter); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + } + + public function testResolvingCallbacksAreCalledForConcretesWithNoBinding() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(ContainerImplementationStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + } + + public function testResolvingCallbacksAreCalledForInterFacesWithNoBinding() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function () use (&$callCounter) { + $callCounter++; + }); + + $container->make(ContainerImplementationStub::class); + $this->assertEquals(1, $callCounter); + $container->make(ContainerImplementationStub::class); + $this->assertEquals(2, $callCounter); + } + public function testMakeWithMethodIsAnAliasForMakeMethod() { $mock = $this->getMockBuilder(Container::class)