diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 25b2764c5ad8..90e06780d644 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -739,7 +739,7 @@ protected function getConcrete($abstract) * Get the contextual concrete binding for the given abstract. * * @param string $abstract - * @return \Closure|string|null + * @return \Closure|string|array|null */ protected function getContextualConcrete($abstract) { @@ -870,9 +870,18 @@ protected function resolveDependencies(array $dependencies) // If the class is null, it means the dependency is a string or some other // primitive type which we can not resolve since it is not a class and // we will just bomb out with an error since we have no-where to go. - $results[] = is_null($dependency->getClass()) + $result = is_null($dependency->getClass()) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); + + // If this dependency is variadic the result will be an array but they will + // need to be unrolled so they look like additional results instead of a + // single result representing an array of items. + if ($dependency->isVariadic()) { + $results = array_merge($results, $result); + } else { + $results[] = $result; + } } return $results; @@ -944,7 +953,28 @@ protected function resolvePrimitive(ReflectionParameter $parameter) protected function resolveClass(ReflectionParameter $parameter) { try { - return $this->make($parameter->getClass()->name); + // The default case is handled early and quickly so more complicated + // checks that apply only for variadic calls only run in cases + // where the parameter is actually variadic. + if (! $parameter->isVariadic()) { + return $this->make($parameter->getClass()->name); + } + + $abstract = $this->getAlias($parameter->getClass()->name); + $concrete = $this->getContextualConcrete($abstract); + + // If the concrete for a variadic is not an array we can treat it + // like other parameters and will assume making the parameter + // will result in an array. + if (! is_array($concrete)) { + return $this->make($parameter->getClass()->name); + } + + // If the concrete for a variadic is an array, we call resolve on + // each value in the concrete array. + return array_map(function ($abstract) { + return $this->resolve($abstract); + }, $concrete); } // If we can not resolve the class instance, we will check to see if the value @@ -955,6 +985,15 @@ protected function resolveClass(ReflectionParameter $parameter) return $parameter->getDefaultValue(); } + // The "default value" for variadic can never be null but PHP does not + // treat these parameters as having a default value. By returning an + // empty array for variadic values we can better emulate PHP + // runtime which will always result in an empty array if + // no values are present. + if ($parameter->isVariadic()) { + return []; + } + throw $e; } } diff --git a/src/Illuminate/Container/ContextualBindingBuilder.php b/src/Illuminate/Container/ContextualBindingBuilder.php index a52db5d5047c..c69cede7acd9 100644 --- a/src/Illuminate/Container/ContextualBindingBuilder.php +++ b/src/Illuminate/Container/ContextualBindingBuilder.php @@ -57,7 +57,7 @@ public function needs($abstract) /** * Define the implementation for the contextual binding. * - * @param \Closure|string $implementation + * @param \Closure|string|array $implementation * @return void */ public function give($implementation) diff --git a/tests/Container/ContextualBindingTest.php b/tests/Container/ContextualBindingTest.php index a8654c795578..2e23d770ee71 100644 --- a/tests/Container/ContextualBindingTest.php +++ b/tests/Container/ContextualBindingTest.php @@ -231,6 +231,76 @@ public function testContextualBindingWorksForNestedOptionalDependencies() ); $this->assertInstanceOf(ContainerContextImplementationStubTwo::class, $resolvedInstance->implTwo->impl); } + + public function testContextualBindingWorksForVariadicDependencies() + { + $container = new Container; + + $container->when(ContainerTestContextInjectVariadic::class)->needs(IContainerContextContractStub::class)->give(function ($c) { + return [ + $c->make(ContainerContextImplementationStub::class), + $c->make(ContainerContextImplementationStubTwo::class), + ]; + }); + + $resolvedInstance = $container->make(ContainerTestContextInjectVariadic::class); + + $this->assertCount(2, $resolvedInstance->stubs); + $this->assertInstanceOf(ContainerContextImplementationStub::class, $resolvedInstance->stubs[0]); + $this->assertInstanceOf(ContainerContextImplementationStubTwo::class, $resolvedInstance->stubs[1]); + } + + public function testContextualBindingWorksForVariadicDependenciesWithNothingBound() + { + $container = new Container; + + $resolvedInstance = $container->make(ContainerTestContextInjectVariadic::class); + + $this->assertCount(0, $resolvedInstance->stubs); + } + + public function testContextualBindingWorksForVariadicAfterNonVariadicDependencies() + { + $container = new Container; + + $container->when(ContainerTestContextInjectVariadicAfterNonVariadic::class)->needs(IContainerContextContractStub::class)->give(function ($c) { + return [ + $c->make(ContainerContextImplementationStub::class), + $c->make(ContainerContextImplementationStubTwo::class), + ]; + }); + + $resolvedInstance = $container->make(ContainerTestContextInjectVariadicAfterNonVariadic::class); + + $this->assertCount(2, $resolvedInstance->stubs); + $this->assertInstanceOf(ContainerContextImplementationStub::class, $resolvedInstance->stubs[0]); + $this->assertInstanceOf(ContainerContextImplementationStubTwo::class, $resolvedInstance->stubs[1]); + } + + public function testContextualBindingWorksForVariadicAfterNonVariadicDependenciesWithNothingBound() + { + $container = new Container; + + $resolvedInstance = $container->make(ContainerTestContextInjectVariadicAfterNonVariadic::class); + + $this->assertCount(0, $resolvedInstance->stubs); + } + + public function testContextualBindingWorksForVariadicDependenciesWithoutFactory() + { + $container = new Container; + + $container->when(ContainerTestContextInjectVariadic::class)->needs(IContainerContextContractStub::class)->give([ + ContainerContextImplementationStub::class, + ContainerContextImplementationStubTwo::class, + ]); + + $resolvedInstance = $container->make(ContainerTestContextInjectVariadic::class); + + $this->assertCount(2, $resolvedInstance->stubs); + $this->assertInstanceOf(ContainerContextImplementationStub::class, $resolvedInstance->stubs[0]); + $this->assertInstanceOf(ContainerContextImplementationStubTwo::class, $resolvedInstance->stubs[1]); + } } interface IContainerContextContractStub @@ -238,6 +308,11 @@ interface IContainerContextContractStub // } +class ContainerContextNonContractStub +{ + // +} + class ContainerContextImplementationStub implements IContainerContextContractStub { // @@ -309,3 +384,25 @@ public function __construct(ContainerTestContextInjectOne $inner = null) $this->inner = $inner; } } + +class ContainerTestContextInjectVariadic +{ + public $stubs; + + public function __construct(IContainerContextContractStub ...$stubs) + { + $this->stubs = $stubs; + } +} + +class ContainerTestContextInjectVariadicAfterNonVariadic +{ + public $other; + public $stubs; + + public function __construct(ContainerContextNonContractStub $other, IContainerContextContractStub ...$stubs) + { + $this->other = $other; + $this->stubs = $stubs; + } +}