Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] Add container support for variadic constructor arguments #32454

Merged
merged 1 commit into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}