Framework: Contextual binding does not work

Created on 20 Sep 2017  路  13Comments  路  Source: laravel/framework

  • Laravel Version: 5.5.9
  • PHP Version: 7.1.4
  • Database Driver & Version: Mysql 8.0

Description:

When you use contextual binding to resolve class dependencies in controllers you got the error:

[Illuminate\Contracts\Container\BindingResolutionException]  
  Target [App\Core\Contracts\RepositoryInterface] is not in    
  stantiable while building [\App\Domains\Brands\Controller    
  s\BrandsController].

Steps To Reproduce:

  • I have created a controller that depends of _RepositoryInterface_;

  • Create the _RepositoryInterface_;

  • Create _BrandsRepository_ that implements the _RepositoryInterface_

namespace App\Domains\Brands\Controllers;
class BrandsController extends Controller
{
    public function __construct(RepositoryInterface $repository) {}
}

namespace App\Core\Contracts;
interface RepositoryInterface {}

namespace App\Domains\Brands\Repositories;
class BrandsRepository implements RepositoryInterface {}

In my service provider I have:

namespace App\Domains\Brands\Providers;

use App\Core\Contracts\RepositoryInterface;
use App\Domains\Brands\Controllers\BrandsController;
use App\Domains\Brands\Repositories\BrandsRepository;
use Illuminate\Support\ServiceProvider;

class BrandsServiceProvider extends ServiceProvider
{
     /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->when(BrandsController::class)
            ->needs(RepositoryInterface::class)
            ->give(BrandsRepository::class);
    }
}

If I directly bind the repository it works, like this:

namespace App\Domains\Brands\Providers;

use App\Core\Contracts\RepositoryInterface;
use App\Domains\Brands\Controllers\BrandsController;
use App\Domains\Brands\Repositories\BrandsRepository;
use Illuminate\Support\ServiceProvider;

class BrandsServiceProvider extends ServiceProvider
{
     /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(RepositoryInterface::class, BrandsRepository::class);
    }
}

But of course it is not what I want, because in my others controllers domains, like customers, I will receive the _BrandsRepository_ instead _CustomersRepository_ for example.

Most helpful comment

I have found my error, in the routes I was declaring the route wrongly:

Route::get('foo', '\App\Http\Controllers\TestController@index');

It worked on Laravel 5.3.

If I change to the standard way it works normally:

Route::get('foo', 'TestController@index');

I guess the modification in L5.4 has caused it. The changes in the _Container_ class has turn binding classes into the container with leading slashes no longer supported, described in Binding Classes With Leading Slashes section on documentation

All 13 comments

But of course it is not what I want

Actually the 2nd way is what you need and enough for your usecase.

@Dylan-DPC I don't think so.

If I keep two services providers binding the same _RepositoryInterface_ with two differents repositories the second to be loaded will receive the correct repository, but the first one will receive the same, using my use case, the _BrandsController_ will be injected with _CustomersRepository_ instead the _BrandsRepository_.

The only way to have this case correct is using contextual binding, as describe in the (Laravel documentation)

I just tried the following in a fresh install and it works correctly:

// AppServiceProvider
public function register()
{
    $this->app
         ->when(UserController::class)
         ->needs(RepositoryInterface::class)
         ->give(UserRepository::class);

    $this->app
         ->when(ProfileController::class)
         ->needs(RepositoryInterface::class)
         ->give(ProfileRepository::class);
}
// routes/web.php
Route::get('/user', 'UserController@test');
Route::get('/profile', 'ProfileController@test');

@devcircus I did exactly the same, the fresh installation of Laravel 5.5 and it does not work for me, you can check my test here.
I really don't know I am doing wrong.

I can't replicate this either.

@themsaid Did you use my sample project linked above to try reproduce?

I have tested the exactly same thing on Laravel 5.4 and 5.3. The same code works on 5.3 but doesn't on 5.4 and 5.5.

You guys can check here the L5.3 works well.

Will clone and check out your repo. Very interested in this. If you figure it out, please follow up here.

@luciano-jr I don't understand anything in your project, it's always better to include steps of replication clearly in the issue.

I followed the steps you put here and I couldn't regenerate the issue.

screen shot 2017-09-21 at 4 18 00 pm

How can I make this fail? It currently works and I get Yes it works when I run the TestController@index action.

I cloned your project and everything works. I didn't change anything. Just added a route to hit "someMethod" in Foo controller.

Ouput when I hit that endpoint is:

"foobar"

SomeConcrete {#142}

I have found my error, in the routes I was declaring the route wrongly:

Route::get('foo', '\App\Http\Controllers\TestController@index');

It worked on Laravel 5.3.

If I change to the standard way it works normally:

Route::get('foo', 'TestController@index');

I guess the modification in L5.4 has caused it. The changes in the _Container_ class has turn binding classes into the container with leading slashes no longer supported, described in Binding Classes With Leading Slashes section on documentation

Was this page helpful?
0 / 5 - 0 ratings