Framework: Can't use contextual binding without default bind

Created on 29 Jul 2016  路  7Comments  路  Source: laravel/framework

I have a situation where I have a interface that a class in each module need to implement, I don't have a default module, so, my idea is to only use the contextual binding, see code:

        $this->app->when('ModuleXController')
            ->needs('GlobalXContract')
            ->give('ConcretClassModuleX');

But I'm facing the Target GlobalXContract is not instantiable while building ModuleXController

This works when I do something like:

        $this->app->bind('GlobalXContract', 'ConcretClassDefault');
        $this->app->when('ModuleXController')
            ->needs('GlobalXContract')
            ->give('ConcretClassModuleX');

My question is, is it not more interesting to make optional the default binding when using contextual binding ?

Regards.

needs more info

Most helpful comment

Ayt, after digging through the container, the concrete class can't be found because Laravel's container relies on the end of the $buildStack property. To keep it short, if the dependency doesn't have an entry in the $buildStack (ex. injecting the dependency through the method) then container can't find the contextual binding.

Solution:
keep your dependency injections on the constructor :/

All 7 comments

I wonder if you're still facing this behaviour on 5.3, can you please update?

@themsaid tested on v5.3.10 and everything ok, firstly I test in v5.2 (latest)

馃憤馃徏 thank you for updating us :)

Experiencing this on 5.5.* :/
a bit different though, the contextual bind is being ignored and the concrete class from the normal bind is being used regardless

The service provider:

        $this->app->bind(DummyInterface::class, DummyInterfaceDefaultImpl::class);
        $this->app
                ->when(ContextualTestController::class)
                ->needs(DummyInterface::class)
                ->give(DummyInterfaceContextualImpl::class);

Default implementation of an interface with saySomething() method:

class DummyInterfaceDefaultImpl implements DummyInterface
{
    public function saySomething()
    {
        return 'Hey, i\'m from default implementation';
    }
}

A separate implementation:

class DummyInterfaceContextualImpl implements DummyInterface
{
    public function saySomething()
    {
        return 'Hi, I\'m from context';
    }
}

Controller 1 (default)

class DefaultTestController extends Controller
{
    public function test(DummyInterface $dummy)
    {
        return $dummy->saySomething();
    }
}

Controller 2 (some specific context)

class ContextualTestController extends Controller
{
    public function test(DummyInterface $dummy)
    {
        return $dummy->saySomething();
    }
}

The routes:

Route::get('default-controller', 'Test\DefaultTestController@test');
Route::get('contextual-controller', 'Test\ContextualTestController@test');

Accessing /default-controller and /contextual-controller both display "Hey, i'm from default implementation"

Ayt, after digging through the container, the concrete class can't be found because Laravel's container relies on the end of the $buildStack property. To keep it short, if the dependency doesn't have an entry in the $buildStack (ex. injecting the dependency through the method) then container can't find the contextual binding.

Solution:
keep your dependency injections on the constructor :/

just for clarification, as someone might stumble upon this issue as I have myself.
This is still an existing issue, but in fact it is not a bug.
The thing is: contextual binding works only if it's used in constructor, dependency injection does not work in "late constructing" of a method.

The new proposal can be found here, tl;dr; it was rejected.
https://github.com/laravel/framework/pull/30542

Was this page helpful?
0 / 5 - 0 ratings