[8.x] Resolving non-instantiables corrupts Container::$with #36212
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I ran into this issue when a constructor dependency is non-instantiable (such as an interface) and a following dependency is provided as a contextual property. My use case was when using an OpenAPI generated library. The class is essentially:
This needs a Guzzlehttp client, a configuration object for request settings (HTTP host, auth data, etc), and a few other parameters. I didn't need to customize the Guzzlehttp client, however I was using the container's
extend
function to configure theConfiguration
object:This all worked as expected, so far.
In my application, I have another class that consumes a 3rd party API via the
OpenApi
class, essentially:When resolving the
Consumer
class, I usually allow the container to resolve theRepository
class for me, but there are some instances that I need to provide it a certain setup. I do this like so:This is where I found the issue. When resolving the
OpenApi
class, the container correctly lists outClientInterface
,Configuration
,HeaderSelector
, and$host_index
as dependencies, and it resolves them. However, becauseClientInterface
is not instantiable (\ReflectionClass::isInstantiable()
is used onsrc/Illuminate/Container/Container.php:838
) it throws an\Illuminate\Contracts\Container\BindingResolutionException
, which is handled atsrc/Illuminate/Container/Container.php:986
. Because theClientInterface
has a default value ofnull
, the container returns that value and continues on its merry way.The problem is that it does not clean up the
Container::$with
property correctly. When a dependency properly resolves, it eventually usesarray_pop($this->with);
as it climbs up the recursion. When the\Illuminate\Contracts\Container\BindingResolutionException
occurs, it doesn't clean up the$with
property. Then, when resolving subsequent parameters of the constructor (in this example, trying to resolveRepository
forConsumer
), the$with
property has the wrong data and it fails to use the provided$repository
object.This PR provides a fix as well as a test that passes with the fix and fails without it.
As a side-effect, it also leaves 1 additional element in the
$with
array when all resolution is complete every time this occurs (probably not very frequently in the life-cycle of a request). And as far as I can tell, this doesn't impact any future resolutions, since each resolution just appends to the end of the$with
array rather than reading anything already there.