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.0] Added contextual binding on methods test #8906

Closed
wants to merge 1 commit into from
Closed

[5.0] Added contextual binding on methods test #8906

wants to merge 1 commit into from

Conversation

smallhadroncollider
Copy link

Contextual binding does not work on methods, only constructor functions.

I've added a (currently failing) test that checks that this works. (At least, I think that's what it's testing, I don't fully understand the Container class, so apologies if the test is wrong/stupid.)

@taylorotwell
Copy link
Member

Hmm, I don't think I ever intended it to really work on methods, although it is an interesting use case.

@smallhadroncollider
Copy link
Author

I've come across a situation where I feel like it would be the cleanest way to handle things: http://stackoverflow.com/questions/30477611/laravel-5-0-application-structure

Perhaps there's a better way of handling that specific scenario, but I still feel like it could be useful. If it was an easy change then it would be great to have.

@GrahamCampbell GrahamCampbell changed the title added contextual binding on methods test [5.0] Added contextual binding on methods test May 28, 2015
@taylorotwell
Copy link
Member

Going to close this as it was never really an intended use case.

@ghost
Copy link

ghost commented Jul 17, 2015

@taylorotwell Common use case would be now with laravel 5.1 where are self handling jobs.
Now you can't pass interfaces into job _constructor methods, as you could with command handlers, because you pass input params directly into jobs, and now you need to pass interfaces into job's handle method. If i use interfaces with multiple implementations and pass it into job's handle method it wont be resolved.

This Example wont work, because there is no Contextual binding for methods:

<?php

use App\Jobs\Job;
use Illuminate\Contracts\Bus\SelfHandling;

class StripeSampleJob extends Job implements SelfHandling
{
    public $inputParam1;

    public $inputParam2

    public function __construct($inputParam1, $inputParam2)
    {
        $this->inputParam1 = $inputParam1
        $this->inputParam2 = $inputParam2
    }

    public function handle(PaymentInterface $paymentInterface)
    {
        return $this->paymentInterface->doSomething($this->inputParam1, $inputParam2)
    }
}



$this->app->when(StripeSampleJob::class)
          ->needs(PaymentInterface::class)
          ->give(StripeInterface::class);

 $this->app->when(PaypalSampleJob::class)
          ->needs(PaymentInterface::class)
          ->give(PaypalInterface::class);

@Shkeats
Copy link

Shkeats commented Jul 26, 2016

@taylorotwell I agree with @ddctd143 that as self-handling jobs are now pushed as the norm it should be possible to do contextual dependency injection into the handle method. The current setup is strange because one type of binding works while another doesn't. It's not expected behavior as a user. The same sentiment can be found here #6177

@taylorotwell
Copy link
Member

Yes I can definitely see a use case there. How hard is it to get the contextual binding to work at a method level?

@Shkeats
Copy link

Shkeats commented Jul 27, 2016

@taylorotwell My understanding of the internals of Illuminate/Container isn't good enough to give you a straight answer on that. However, I can outline two types of behavior, the first being what might be the expected behavior now and the second probably being more complex to implement but a feature for the future.

1 Class based method contextual binding: i.e, A class is bound to a particular concrete implementation and methods on that class are all given the same injection if they ask for it.

$app->when(MyClass::class)->needs(MyServiceInterface::class)->give(MyService:class);

MyClass@method(MyServiceInterface $myService) gets MyService injected.

This is more or less as we understand the current binding system. To enable this I assume the Container needs to be modified so that it checks the contextual binding map for a method's parent class when it resolves the dependencies for that method.

2 Class@method based method contextual binding: i.e, A Class@method is bound to a particular concrete implementation and that specific method is given that dependency when it asks for it.

$app->when(MyClass::class . '@someMethod')->needs(MyServiceInterface::class)->give(MyService:class);

This would allow another method on a particular class to have the same dependency hinted but get a different implementation. More of an edge use case than the first but not beyond the realms of imagination.

@RDelorier
Copy link
Contributor

RDelorier commented Aug 25, 2016

@taylorotwell @Shkeats I may be a little out of my league here but would it make sense to provide something like withContext on the container that would just push the class onto the build stack?

Container.php

    public function withContext($context, $callback)
    {
        $this->buildStack[] = $context;
        $value = $this->call($callback);
        array_pop($this->buildStack);

        return $value;
    }

Then anywhere that should support contextual method binding could wrap their calls.

examples

Container.php

    protected function getMethodDependencies($callback, array $parameters = [])
    {
        $dependencies = [];
        $reflector = $this->getCallReflector($callback);

        $this->withContext($reflector->class, function () use ($reflector) {
            foreach ($reflector->getParameters() as $parameter) {
                $this->addDependencyForCallParameter($parameter, $parameters, $dependencies);
            }
        })

        return array_merge($dependencies, $parameters);
    }

RouteDependencyResolverTrait.php

    protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
    {
        if (! method_exists($instance, $method)) {
            return $parameters;
        }

        return $this->container
            ->withContext(get_class($instance), function () use ($instance, $method) {
                $this->resolveMethodDependencies(
                    $parameters, new ReflectionMethod($instance, $method)
                )
            });
    }

@nkrh
Copy link

nkrh commented Jul 28, 2017

Not working on 5.1.46

@donkooijman
Copy link

donkooijman commented Sep 14, 2017

This feature could be useful for contextual binding in the handle method of jobs. @taylorotwell

@userforce
Copy link

Contextual binding per method initialization can make an implementation explicit and cognitively clear. I had the intention to fraction my requests implementation into few contracts, each per action type: UpdateRequestContract, SearchRequestContract, SomeGenericActionRequestContract then inject implementation per controller method, expl.: DiscountController@search(SearchRequestContract) -> give(DiscountSearchRequest::class) ...
I'm not going to kibble ideas and bring dirt into the projects by inserting strange methods and workarounds. That will not make your co-workers feel excited about new features, instead, they rather will get a feeling of a new rib growing under the arm. The current implementation is already great and made my life better so I will keep working using it properly and wait for the new exited extensions.

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

Successfully merging this pull request may close these issues.

7 participants