Skip to content

Commit

Permalink
support contextual binding on first class callables
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorotwell committed Apr 3, 2023
1 parent cc3ccc3 commit de8d515
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 2 deletions.
21 changes: 19 additions & 2 deletions src/Illuminate/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionParameter;
use TypeError;

Expand Down Expand Up @@ -648,8 +649,8 @@ public function call($callback, array $parameters = [], $defaultMethod = null)
{
$pushedToBuildStack = false;

if (is_array($callback) && ! in_array(
$className = (is_string($callback[0]) ? $callback[0] : get_class($callback[0])),
if (($className = $this->getClassForCallable($callback)) && ! in_array(
$className,
$this->buildStack,
true
)) {
Expand All @@ -667,6 +668,22 @@ public function call($callback, array $parameters = [], $defaultMethod = null)
return $result;
}

/**
* Get the class name for the given callback, if one can be determined.
*
* @param callable|string $callback
* @return string|false
*/
protected function getClassForCallable($callback)
{
if (is_callable($callback) &&
! ($reflector = new ReflectionFunction($callback(...)))->isAnonymous()) {
return $reflector->getClosureScopeClass()->name ?? false;
}

return false;
}

/**
* Get a closure to resolve the given type from the container.
*
Expand Down
28 changes: 28 additions & 0 deletions tests/Container/ContextualBindingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,26 @@ public function testContextualBindingGivesValuesFromConfigArray()
$this->assertSame('hunter42', $resolvedInstance->settings['password']);
$this->assertSame('lumen', $resolvedInstance->settings['alias']);
}

public function testContextualBindingWorksForMethodInvocation()
{
$container = new Container;

$container
->when(ContainerTestContextInjectMethodArgument::class)
->needs(IContainerContextContractStub::class)
->give(ContainerContextImplementationStub::class);

$object = new ContainerTestContextInjectMethodArgument;

// array callable syntax...
$valueResolvedUsingArraySyntax = $container->call([$object, 'method']);
$this->assertInstanceOf(ContainerContextImplementationStub::class, $valueResolvedUsingArraySyntax);

// first class callable syntax...
$valueResolvedUsingFirstClassSyntax = $container->call($object->method(...));
$this->assertInstanceOf(ContainerContextImplementationStub::class, $valueResolvedUsingFirstClassSyntax);
}
}

interface IContainerContextContractStub
Expand Down Expand Up @@ -620,3 +640,11 @@ public function __construct($settings)
$this->settings = $settings;
}
}

class ContainerTestContextInjectMethodArgument
{
public function method(IContainerContextContractStub $dependency)
{
return $dependency;
}
}

0 comments on commit de8d515

Please sign in to comment.