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

Container having trouble resolving services: interface + binding for concrete class #31588

Closed
KamilKopaczyk opened this issue Feb 25, 2020 · 7 comments

Comments

@KamilKopaczyk
Copy link

  • Laravel Version: 5.8.0 and above (including current 6.16.0)
  • PHP Version: 7.3 (i don't think it matters in this case)
  • Database Driver & Version: none required

Description:

Hi there,
In some circumstances, for some reason, container is having problem with resolving services.
If a service (concrete) implements an interface (abstract) and this service (concrete) requires additional service provider (e.g. when value from config is injected to class), you may end up with Unresolvable dependency resolving error. It happens when both binding abstract interface to concrete class and injecting dependencies is split between two deferred service providers.

Interesting part: automatic injection of such interface works for console commands, but does not work for controllers (not sure, but there might be more contexts affected)

Steps To Reproduce:

  1. clone https://github.com/rkazaniszyn/laravel-deferred-providers-bug
  2. run composer install
  3. run php artisan command:test # It should work fine
  4. run php artisan serve do a request to "/test" endpoint # Ends up with HTTP 500; logs contain "Unresolvable dependency resolving" error
@driesvints
Copy link
Member

Hey there,

Can you first please try one of the support channels below? If you can actually identify this as a bug, feel free to report back and I'll gladly help you out and re-open this issue.

Thanks!

@KamilKopaczyk
Copy link
Author

@driesvints i went to IRC channel and asked for advice, as you suggested, just in case. We could not find a working solution and someone even mentioned:

looks like changing this Container::make() override to override Container::getConcrete() instead and do the deferred loading there would work better https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Application.php#L768-L777

I think it might be worthwhile to look into this issue. Please let me know what you think.
Thanks!

@driesvints
Copy link
Member

You're not binding the scalar dependency here with binding a primitive: https://github.com/rkazaniszyn/laravel-deferred-providers-bug/blob/master/app/Providers/SampleInterfaceProvider.php#L19

https://laravel.com/docs/6.x/container#binding-basics

@KamilKopaczyk
Copy link
Author

Yes i know, i did it on purpose to illustrate the problem. Primitive binding isn't there, because this provider only provides SampleInterface, not the concrete implementation of this interface.
I know how to work around this problem, but i wanted to provide an example.

Reasons why i reported this and got back to you:

  1. It did worked in previous versions (< 5.7)
  2. It does not work in the newer and current version (>= 5.8)
  3. Service resolving behaviour is different depending on the context - it does work in CLI, but does not in HTTP

Looks like this change might be the case: d0a7adb#diff-52441e04e14c52275cd5d09e8f958981L264

@KamilKopaczyk KamilKopaczyk changed the title Application having trouble resolving services (interface + binding for concrete class) Container having trouble resolving services: interface + binding for concrete class Feb 26, 2020
@lprzybylek
Copy link
Contributor

lprzybylek commented Feb 26, 2020

I think it is indeed a bug. It also worked for me in 5.7 and stopped working in 5.8.

Moreover, if you try to inject implementation first and then interface, it works. If you switch order and want to inject something by interface first, it does not work. Containter behaves different depending on order of resolved classes, which is even more dangerous, because it "sometimes" works and could be easily missed while testing.

The problem is probably caused by this line:
https://github.com/illuminate/container/blob/b42e5ef939144b77f78130918da0ce2d9ee16574/Container.php#L264

In 5.7 it used $container->make() (https://github.com/illuminate/container/blob/8c3a75e464d59509ae88db152cab61a3f115b9ec/Container.php#L264) which executes method from Illuminate\Foundation\Application and therefore loads deferred providers. Changing it to $container->resolve() executes method from Illuminate\Container\Container, which does not know anything about deferred providers.

Reverting this line to state from 5.7 ($container->make()) seems to fix the issue.

@lukaszwit
Copy link

Something that worked till 5.7 and stoped worked on 5.8 (without mention anywhere) looks like bug to me.

lprzybylek added a commit to lprzybylek/framework that referenced this issue Feb 27, 2020
This PR brings back behaviour from Laravel 5.7 when you could use deferred provides to bind an interface to implementation even when implementation had its own deferred provider. Changing `resolve` method to `make` results in calling `Illuminate\Foundation\Application::make` method which loads deferred providers. Calling `resolve` method calls `Illuminate\Container\Container::resolve` which does not load deferred providers. 

PR is the effect of discussion in laravel#31588
@taylorotwell
Copy link
Member

Submit a PR with a failing test to prove it is a bug in the container.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants