Skip to content

Commit

Permalink
Merge branch 'container-variadic-support' of https://github.com/simen…
Browse files Browse the repository at this point in the history
…sen/laravel-framework into simensen-container-variadic-support
  • Loading branch information
taylorotwell committed Apr 20, 2020
2 parents d3bb329 + 11df2d7 commit 45519f2
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 4 deletions.
45 changes: 42 additions & 3 deletions src/Illuminate/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Illuminate/Container/ContextualBindingBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
97 changes: 97 additions & 0 deletions tests/Container/ContextualBindingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,88 @@ 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
{
//
}

class ContainerContextNonContractStub
{
//
}

class ContainerContextImplementationStub implements IContainerContextContractStub
{
//
Expand Down Expand Up @@ -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;
}
}

0 comments on commit 45519f2

Please sign in to comment.