Skip to content

Commit

Permalink
Add missing tests for the resolving callbacks on the container
Browse files Browse the repository at this point in the history
+ bug fix related to multiple calls to "resolving" and "afterResolving" callbacks
  • Loading branch information
imanghafoori1 committed Jan 1, 2019
1 parent d329797 commit fda65f3
Show file tree
Hide file tree
Showing 2 changed files with 335 additions and 1 deletion.
41 changes: 40 additions & 1 deletion src/Illuminate/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ class Container implements ArrayAccess, ContainerContract
*/
protected $afterResolvingCallbacks = [];

/**
* All the abstract keys which are bound with class names.
*
* @var array
*/
protected $boundToClassName = [];

/**
* Define a contextual binding.
*
Expand Down Expand Up @@ -222,6 +229,7 @@ public function isAlias($name)
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
$this->decideAboutConcreteType($abstract, $concrete);

// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
Expand Down Expand Up @@ -674,7 +682,11 @@ protected function resolve($abstract, $parameters = [])
$this->instances[$abstract] = $object;
}

$this->fireResolvingCallbacks($abstract, $object);
// We don't fire "resolving" callbacks for interface bound with class path syntax
// since the callback will fire later, when the bounded concrete is resolving.
if (! $this->isAnInterfaceBoundedWithClassName($abstract)) {
$this->fireResolvingCallbacks($abstract, $object);
}

// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
Expand Down Expand Up @@ -1269,4 +1281,31 @@ public function __set($key, $value)
{
$this[$key] = $value;
}

/**
* Determine if the abstract is both an interface and is bounded to a class path (not a closure).
*
* @param string $abstract
* @return bool
*/
protected function isAnInterfaceBoundedWithClassName($abstract)
{
return interface_exists($abstract) && array_key_exists($abstract, $this->boundToClassName);
}

/**
* Detects what type of concrete is provided for the abstract
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
protected function decideAboutConcreteType($abstract, $concrete)
{
if ($concrete instanceof Closure) {
unset($this->boundToClassName[$abstract]);
} else {
$this->boundToClassName[$abstract] = null;
}
}
}
295 changes: 295 additions & 0 deletions tests/Container/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,301 @@ public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases()
$this->assertEquals('taylor', $instance->name);
}

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

$callCounter = 0;
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(1, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);
}

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

$callCounter = 0;
$container->resolving(function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(1, $callCounter);

$container->make(IContainerContractStub::class);
$this->assertEquals(2, $callCounter);
}

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

$callCounter = 0;
$container->afterResolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(1, $callCounter);

$container->make(IContainerContractStub::class);
$this->assertEquals(2, $callCounter);
}

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

$callCounter = 0;
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
$container->bind(ContainerImplementationStub::class);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(1, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);

$container->make(IContainerContractStub::class);
$this->assertEquals(3, $callCounter);
}

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

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$container->make(ContainerImplementationStub::class);

$callCounter = 0;
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->make(ContainerImplementationStub::class);
$this->assertEquals(1, $callCounter);
}

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

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$callCounter = 0;
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->make(IContainerContractStub::class);
$this->assertEquals(1, $callCounter);

$container->bind(IContainerContractStub::class, ContainerImplementationStubTwo::class);
$container->make(IContainerContractStub::class);
$this->assertEquals(1, $callCounter);
}

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

$callCounter = 0;
$container->resolving('foo', function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind('foo', ContainerImplementationStub::class);

$container->make('foo');
$this->assertEquals(1, $callCounter);

$container->make('foo');
$this->assertEquals(2, $callCounter);
}

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

$callCounter = 0;
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, function () {
return new ContainerImplementationStub;
});

$container->make(IContainerContractStub::class);
$this->assertEquals(1, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(3, $callCounter);

$container->make(IContainerContractStub::class);
$this->assertEquals(4, $callCounter);
}

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

$callCounter = 0;
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
$container->bind(IContainerContractStub::class, function () {
return new ContainerImplementationStub;
});

$container->make(IContainerContractStub::class);
$this->assertEquals(1, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(3, $callCounter);

$container->make(IContainerContractStub::class);
$this->assertEquals(4, $callCounter);
}

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

$callCounter = 0;

$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->resolving(ContainerImplementationStubTwo::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

// it should call the callback for interface
$container->make(IContainerContractStub::class);
$this->assertEquals(1, $callCounter);

// it should call the callback for interface
$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);

// should call the callback for the interface it implements
// plus the callback for ContainerImplementationStubTwo.
$container->make(ContainerImplementationStubTwo::class);
$this->assertEquals(4, $callCounter);
}

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

$callCounter = 0;
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$container->make(IContainerContractStub::class);

$this->assertEquals(1, $callCounter);
}

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

$callCounter = 0;
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$container->make(IContainerContractStub::class);
$this->assertEquals(1, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);
}

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

$callCounter = 0;
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);

$container->make(IContainerContractStub::class);
$this->assertEquals(1, $callCounter);

$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);
}

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

$callCounter = 0;
$container->resolving(ContainerImplementationStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->make(ContainerImplementationStub::class);
$this->assertEquals(1, $callCounter);
$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);
}

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

$callCounter = 0;
$container->resolving(IContainerContractStub::class, function ($some) use (&$callCounter) {
$callCounter++;
});

$container->make(ContainerImplementationStub::class);
$this->assertEquals(1, $callCounter);
$container->make(ContainerImplementationStub::class);
$this->assertEquals(2, $callCounter);
}

public function testMakeWithMethodIsAnAliasForMakeMethod()
{
$mock = $this->getMockBuilder(Container::class)
Expand Down

0 comments on commit fda65f3

Please sign in to comment.