From 30201efdbff678678feba7dd967f04b9cfb91d17 Mon Sep 17 00:00:00 2001 From: imanghafoori Date: Mon, 31 Dec 2018 12:46:05 +0330 Subject: [PATCH] Add missing tests for the resolving callbacks on the container + bug fix related to multiple calls to resolving callbacks --- src/Illuminate/Container/Container.php | 27 +++- tests/Container/ContainerTest.php | 183 +++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 0f1becdfdc28..95730d30675c 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -133,6 +133,13 @@ class Container implements ArrayAccess, ContainerContract */ protected $afterResolvingCallbacks = []; + /** + * All the abstract keys which are bound with class names. + * + * @var array + */ + protected $boundToClassName = []; + /** * Define a contextual binding. * @@ -234,6 +241,7 @@ public function bind($abstract, $concrete = null, $shared = false) // bound into this container to the abstract type and we will just wrap it // up inside its own Closure to give us more convenience when extending. if (! $concrete instanceof Closure) { + $this->boundToClassName[$abstract] = null; $concrete = $this->getClosure($abstract, $concrete); } @@ -1011,9 +1019,11 @@ protected function fireResolvingCallbacks($abstract, $object) { $this->fireCallbackArray($object, $this->globalResolvingCallbacks); - $this->fireCallbackArray( - $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) - ); + if (! $this->boundToClassName($abstract)) { + $this->fireCallbackArray( + $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) + ); + } $this->fireAfterResolvingCallbacks($abstract, $object); } @@ -1137,7 +1147,7 @@ public function forgetExtenders($abstract) */ protected function dropStaleInstances($abstract) { - unset($this->instances[$abstract], $this->aliases[$abstract]); + unset($this->instances[$abstract], $this->aliases[$abstract], $this->boundToClassName[$abstract]); } /** @@ -1269,4 +1279,13 @@ public function __set($key, $value) { $this[$key] = $value; } + + /** + * @param $abstract + * @return bool + */ + protected function boundToClassName($abstract) + { + return interface_exists($abstract) && array_key_exists($abstract, $this->boundToClassName); + } } diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index d54d71dc000b..2b19d0fcb14b 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -1022,6 +1022,189 @@ public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases() $this->assertEquals('taylor', $instance->name); } + public function testResolvingCallbacksAreCalledOnceForImplementation() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function ($some) 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 testResolvingCallbacksAreCalledOnceForImplementation2() + { + $container = new Container; + + $callCounter = 0; + $container->resolving(IContainerContractStub::class, function ($some) 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 ($some) 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 testRebindingDoesNotAffectMultipleResolvingCallbacks() + { + $container = new Container; + + $callCounter = 0; + + $container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) { + $callCounter++; + }); + + $container->resolving(ContainerImplementationStubTwo::class, function ($some) 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 ($some) 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 ($some) 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 ($some) 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 ($some) 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 ($some) 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)