diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index b75e734b30d1..03fbd3979611 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -82,6 +82,13 @@ class Container implements ArrayAccess, ContainerContract */ protected $buildStack = []; + /** + * The stack of de-aliased abstractions currently being resolved. + * + * @var array + */ + protected $resolveStack = []; + /** * The parameter override stack. * @@ -624,6 +631,8 @@ protected function resolve($abstract, $parameters = []) { $abstract = $this->getAlias($abstract); + $this->resolveStack[] = $abstract; + $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); @@ -632,6 +641,8 @@ protected function resolve($abstract, $parameters = []) // just return an existing instance instead of instantiating new instances // so the developer can keep using the same objects instance every time. if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { + array_pop($this->resolveStack); + return $this->instances[$abstract]; } @@ -670,6 +681,7 @@ protected function resolve($abstract, $parameters = []) $this->resolved[$abstract] = true; array_pop($this->with); + array_pop($this->resolveStack); return $object; } @@ -1036,7 +1048,7 @@ protected function getCallbacksForType($abstract, $object, array $callbacksPerTy $results = []; foreach ($callbacksPerType as $type => $callbacks) { - if ($type === $abstract || $object instanceof $type) { + if (! $this->pendingInResolveStack($object) && ($type === $abstract || $object instanceof $type)) { $results = array_merge($results, $callbacks); } } @@ -1058,6 +1070,24 @@ protected function fireCallbackArray($object, array $callbacks) } } + /** + * Check if object or a generalisation is pending in the resolve stack. + * + * @param object $object + * + * @return bool + */ + protected function pendingInResolveStack($object) + { + foreach ($this->resolveStack as $resolving) { + if ($resolving !== end($this->resolveStack) && $object instanceof $resolving) { + return true; + } + } + + return false; + } + /** * Get the container's bindings. * diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php index 352e756f8d24..5a2d398a0b3d 100755 --- a/tests/Container/ContainerTest.php +++ b/tests/Container/ContainerTest.php @@ -378,6 +378,64 @@ public function testResolvingCallbacksAreCalledForType() $this->assertEquals('taylor', $instance->name); } + public function testResolvingCallbacksAreCalledOnceForImplementations() + { + $container = new Container; + $resolving_contract_invocations = 0; + $after_resolving_contract_invocations = 0; + $resolving_implementation_invocations = 0; + $after_resolving_implementation_invocations = 0; + + $container->resolving(IContainerContractStub::class, function () use (&$resolving_contract_invocations) { + $resolving_contract_invocations++; + }); + $container->afterResolving(IContainerContractStub::class, function () use (&$after_resolving_contract_invocations) { + $after_resolving_contract_invocations++; + }); + $container->resolving(ContainerImplementationStub::class, function () use (&$resolving_implementation_invocations) { + $resolving_implementation_invocations++; + }); + $container->afterResolving(ContainerImplementationStub::class, function () use (&$after_resolving_implementation_invocations) { + $after_resolving_implementation_invocations++; + }); + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + $container->make(IContainerContractStub::class); + + $this->assertEquals(1, $resolving_contract_invocations); + $this->assertEquals(1, $after_resolving_contract_invocations); + $this->assertEquals(1, $resolving_implementation_invocations); + $this->assertEquals(1, $after_resolving_implementation_invocations); + } + + public function testResolvingCallbacksAreCalledForNestedDependencies() + { + $container = new Container; + $resolving_dependency_interface_invocations = 0; + $resolving_dependency_implementation_invocations = 0; + $resolving_dependent_invocations = 0; + + $container->bind(IContainerContractStub::class, ContainerImplementationStub::class); + + $container->resolving(IContainerContractStub::class, function () use (&$resolving_dependency_interface_invocations) { + $resolving_dependency_interface_invocations++; + }); + + $container->resolving(ContainerImplementationStub::class, function () use (&$resolving_dependency_implementation_invocations) { + $resolving_dependency_implementation_invocations++; + }); + + $container->resolving(ContainerNestedDependentStubTwo::class, function () use (&$resolving_dependent_invocations) { + $resolving_dependent_invocations++; + }); + + $container->make(ContainerNestedDependentStubTwo::class); + $container->make(ContainerNestedDependentStubTwo::class); + + $this->assertEquals(4, $resolving_dependency_interface_invocations); + $this->assertEquals(4, $resolving_dependency_implementation_invocations); + $this->assertEquals(2, $resolving_dependent_invocations); + } + public function testUnsetRemoveBoundInstances() { $container = new Container; @@ -918,6 +976,19 @@ public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases() $this->assertEquals('taylor', $instance->name); } + public function testInterfaceResolvingCallbacksShouldBeFiredWhenCalledWithAliases() + { + $container = new Container; + $container->alias(IContainerContractStub::class, 'foo'); + $container->resolving(IContainerContractStub::class, function ($object) { + return $object->name = 'taylor'; + }); + $container->bind('foo', ContainerImplementationStub::class); + $instance = $container->make('foo'); + + $this->assertEquals('taylor', $instance->name); + } + public function testMakeWithMethodIsAnAliasForMakeMethod() { $mock = $this->getMockBuilder(Container::class) @@ -1070,6 +1141,18 @@ public function __construct(IContainerContractStub $impl) } } +class ContainerDependentStubTwo +{ + public $implA; + public $implB; + + public function __construct(IContainerContractStub $implA, IContainerContractStub $implB) + { + $this->implA = $implA; + $this->implB = $implB; + } +} + class ContainerNestedDependentStub { public $inner; @@ -1080,6 +1163,16 @@ public function __construct(ContainerDependentStub $inner) } } +class ContainerNestedDependentStubTwo +{ + public $inner; + + public function __construct(ContainerDependentStubTwo $inner) + { + $this->inner = $inner; + } +} + class ContainerDefaultValueStub { public $stub;