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

[5.7] Track abstracts being resolved, fire resolving events once #23701

Closed
wants to merge 6 commits into from
Closed
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
32 changes: 31 additions & 1 deletion src/Illuminate/Container/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ class Container implements ArrayAccess, ContainerContract
*/
protected $buildStack = [];

/**
* The stack of de-aliased abstractions currently being resolved.
*
* @var array
*/
protected $resolveStack = [];

/**
* The parameter override stack.
*
Expand Down Expand Up @@ -624,6 +631,8 @@ protected function resolve($abstract, $parameters = [])
{
$abstract = $this->getAlias($abstract);

$this->resolveStack[] = $abstract;

$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
Expand All @@ -632,6 +641,8 @@ protected function resolve($abstract, $parameters = [])
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
array_pop($this->resolveStack);

return $this->instances[$abstract];
}

Expand Down Expand Up @@ -670,6 +681,7 @@ protected function resolve($abstract, $parameters = [])
$this->resolved[$abstract] = true;

array_pop($this->with);
array_pop($this->resolveStack);

return $object;
}
Expand Down Expand Up @@ -1036,7 +1048,7 @@ protected function getCallbacksForType($abstract, $object, array $callbacksPerTy
$results = [];

foreach ($callbacksPerType as $type => $callbacks) {
if ($type === $abstract || $object instanceof $type) {
if (! $this->pendingInResolveStack($object) && ($type === $abstract || $object instanceof $type)) {
$results = array_merge($results, $callbacks);
}
}
Expand All @@ -1058,6 +1070,24 @@ protected function fireCallbackArray($object, array $callbacks)
}
}

/**
* Check if object or a generalisation is pending in the resolve stack.
*
* @param object $object
*
* @return bool
*/
protected function pendingInResolveStack($object)
{
foreach ($this->resolveStack as $resolving) {
if ($resolving !== end($this->resolveStack) && $object instanceof $resolving) {
return true;
}
}

return false;
}

/**
* Get the container's bindings.
*
Expand Down
93 changes: 93 additions & 0 deletions tests/Container/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,64 @@ public function testResolvingCallbacksAreCalledForType()
$this->assertEquals('taylor', $instance->name);
}

public function testResolvingCallbacksAreCalledOnceForImplementations()
{
$container = new Container;
$resolving_contract_invocations = 0;
$after_resolving_contract_invocations = 0;
$resolving_implementation_invocations = 0;
$after_resolving_implementation_invocations = 0;

$container->resolving(IContainerContractStub::class, function () use (&$resolving_contract_invocations) {
$resolving_contract_invocations++;
});
$container->afterResolving(IContainerContractStub::class, function () use (&$after_resolving_contract_invocations) {
$after_resolving_contract_invocations++;
});
$container->resolving(ContainerImplementationStub::class, function () use (&$resolving_implementation_invocations) {
$resolving_implementation_invocations++;
});
$container->afterResolving(ContainerImplementationStub::class, function () use (&$after_resolving_implementation_invocations) {
$after_resolving_implementation_invocations++;
});
$container->bind(IContainerContractStub::class, ContainerImplementationStub::class);
$container->make(IContainerContractStub::class);

$this->assertEquals(1, $resolving_contract_invocations);
$this->assertEquals(1, $after_resolving_contract_invocations);
$this->assertEquals(1, $resolving_implementation_invocations);
$this->assertEquals(1, $after_resolving_implementation_invocations);
}

public function testResolvingCallbacksAreCalledForNestedDependencies()
{
$container = new Container;
$resolving_dependency_interface_invocations = 0;
$resolving_dependency_implementation_invocations = 0;
$resolving_dependent_invocations = 0;

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

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

$container->resolving(ContainerImplementationStub::class, function () use (&$resolving_dependency_implementation_invocations) {
$resolving_dependency_implementation_invocations++;
});

$container->resolving(ContainerNestedDependentStubTwo::class, function () use (&$resolving_dependent_invocations) {
$resolving_dependent_invocations++;
});

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

$this->assertEquals(4, $resolving_dependency_interface_invocations);
$this->assertEquals(4, $resolving_dependency_implementation_invocations);
$this->assertEquals(2, $resolving_dependent_invocations);
}

public function testUnsetRemoveBoundInstances()
{
$container = new Container;
Expand Down Expand Up @@ -918,6 +976,19 @@ public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases()
$this->assertEquals('taylor', $instance->name);
}

public function testInterfaceResolvingCallbacksShouldBeFiredWhenCalledWithAliases()
{
$container = new Container;
$container->alias(IContainerContractStub::class, 'foo');
$container->resolving(IContainerContractStub::class, function ($object) {
return $object->name = 'taylor';
});
$container->bind('foo', ContainerImplementationStub::class);
$instance = $container->make('foo');

$this->assertEquals('taylor', $instance->name);
}

public function testMakeWithMethodIsAnAliasForMakeMethod()
{
$mock = $this->getMockBuilder(Container::class)
Expand Down Expand Up @@ -1070,6 +1141,18 @@ public function __construct(IContainerContractStub $impl)
}
}

class ContainerDependentStubTwo
{
public $implA;
public $implB;

public function __construct(IContainerContractStub $implA, IContainerContractStub $implB)
{
$this->implA = $implA;
$this->implB = $implB;
}
}

class ContainerNestedDependentStub
{
public $inner;
Expand All @@ -1080,6 +1163,16 @@ public function __construct(ContainerDependentStub $inner)
}
}

class ContainerNestedDependentStubTwo
{
public $inner;

public function __construct(ContainerDependentStubTwo $inner)
{
$this->inner = $inner;
}
}

class ContainerDefaultValueStub
{
public $stub;
Expand Down