Right now contextual binding resolution only works at the constructor level but it should also work with method injection. So, for example, if we have:
$app->when('PostsController')
->needs('PostRepositoryInterface')
->give('EloquentPostRepository');
then PostsController::__construct(PostRepositoryInterface $posts)
will resolve the dependency correctly but PostsController::store(PostRepositoryInterface $posts)
will only look for a non-contextual binding and will fail to resolve.
It would be great to have something like this.
$app->when('PostController@store')
->needs('PostRepositoryInterface')
->give('EloquentPostRepository');
Can you please add a real-life example where you need different implementations of the same parent type injected in different methods of the same class? Thanks!
I also posted the same question on Stack Overflow.
Consider the following scenario, which is what I'm currently using for my project's API:
I have a base API controller which implements the CRUD functionalities for various entities (e.g. User, Address, Phone, etc.).
As a simplified example, here is the code for the base controller:
abstract class ApiController {
protected $repo;
public function __construct(RepositoryInterface $repo)
{
$this->repo = $repo;
}
public function index()
{
// return a list of records/models (e.g. $this->repo->getAll())
}
/**
* This is using route model (repository to be precise) binding
*/
public function show($repo)
{
// return a record/model (e.g. $repo->getModel())
}
/**
* This is also using route model (repository to be precise) binding
*/
public function delete($repo)
{
// delete a record/model (e.g. $repo->delete())
}
}
Each entity's controller (e.g. UsersController
) would extend the base controller, inheriting the CRUD functionalities. In the above, I'm using contextual bindings to inject the relevant repository into the __constructor()
method (e.g. when UsersController
needs RepositoryInterface
, give UserRepository
). It works beautifully for the index()
, show()
and delete()
methods.
In order to implement store()
and update()
methods, I would need to inject object for the relevant request class (I have one for create and one for update operation, for each entity).
The problem is, I cannot inject object of the relevant request class into the constructor due to a couple of reasons:
index()
method!). This is clearly wrong, and validation would obviously fail for index()
, show()
and delete()
methods, since no user input is supplied for them. store()
or update()
) being run. This cannot be achieved using constructor injection.If contextual binding also worked for controller methods other than the constructor, I could just inject the relevant request object directly into the method. For example, I could do something like the following:
...
public function store(CreateRequestInterface $request)
{
// create a record (e.g. $this->repo->create($request->all()))
}
public function update($repo, UpdateRequestInterface $request)
{
// create a record (e.g. $repo->update($request->all()))
}
...
Since this doesn't work as of now, I'm using a workaround by declaring a couple of properties $createRequestClass
and $updateRequestClass
, which I override in children controllers to the relevant request classes. Then, within the store()
and update()
methods, I manually create the instance of the relevant request class using something like $request = App::make($this->createRequestClass)
.
If contextual bindings would have worked for methods, even this bit of code would not be been needed and everything would have been beautiful :)
Why was this silently closed without further discussion?
Because Graham forgot to mention contextual binding is now available in
illuminate/container 5.0
On 7 Feb 2015 08:22, "Robin Mitra" [email protected] wrote:
Why was this silently closed without further discussion?
—
Reply to this email directly or view it on GitHub
https://github.com/laravel/framework/issues/6177#issuecomment-73353654.
Thanks for the info @hannesvdvreken
@hannesvdvreken wow that's great! Thanks
@robinmitra Hi! Is it working? Because I have the same issue.
I can't get this to work.
The following works (but isn't useful):
$this->app->bind("API\Http\Requests\RequestInterface", "API\Http\Requests\SquareRequest");
This does not:
$this->app->when("API\Http\Controllers\SquaresController")
->needs("API\Http\Requests\RequestInterface")
->give("API\Http\Requests\SquareRequest");
@smallhadroncollider @hannesvdvreken
I have the same issue.
use bind()
works,
but use when->needs->give
not working
@refear99 I've added a pull request which adds a (currently failing) test for this, so hopefully it might be addressed #8906
+1 I would like to see contextual binding - when->needs->give
working on controllers too. I think it is a valid use case @taylorotwell. Thanks for bringing this up @smallhadroncollider.
+1 Not only in controllers but it would be very useful in some Jobs for example. Jobs rely on the injection only in the handle() method. Sometimes, you might want to give different instances of the interface to different jobs.
+1 I had exact same issue with @robinmitra and would love to see it works
+1 such solution would make a cleaner and accurate binding
+1 I had the same demand to use this feature
@reyezstef I found a work around solution for this, hope this help
$this->app->addContextualBinding(
"API\Http\Controllers\SquaresController"
"API\Http\Requests\RequestInterface"
"API\Http\Requests\SquareRequest"
);
+1 I would love to use contextual binding in a job's handle()
method.
@nguyentienlong the solution doesn't seem to be working. At least, not with 5.1.40
@redgenie for 2 months I did not work with laravel, but if you want some real example about contextual binding in laravel ( >5), you can refer this article http://longkyanh.info/blog/2016/04/29/contextual-binding-in-laravel-and-o-in-solid-principles/
Just encountered this with wanting to create a base controller with request interfaces type-hinted in its methods, and then use contextual binding to inject concrete implementations:
abstract class ReasonController extends Controller
{
public function store(CreateReasonRequest $request)
{
//
}
}
```php
class DiscountReasonController extends ReasonController
{
//
}
```php
$this->app->when(DiscountReasonController::class)
->wants(CreateReasonRequest::class)
->give(function ($app) {
return $app->make(CreateDiscountReasonRequest::class);
});
Was method-based contextual binding ever added to Laravel?
@martinbean as far as was using contextual bindings recently, there is no "official" way to method-based binding. You can do it only in not-exactly-elegant way by creating external resolver class which resolves proper class based on some kind of config file, for eg.
'My\First\Class@method' => 'Class\To\Inject'
and with this override callAction()
from Illuminate\Routing\Controller
, where resolver class can be called and return proper implementation. Of course this method implies, that You cannot use dependency injection in method that have any bindings (in sense for this specific implementation), but work on this internal object returned by resolving class.
Is there a way to make this work with 5.7+? The only way to inject is constructor, but a lot of stuff can be injected directly into methods, which in this case breaks the standardization within the controller. An example:
public function store(Request $request, MyFactoryInterface $myFactory, Filesystem $cloud) {
//
}
In this case, if you want to inject a different filesystem to this controller method you need to inject it through constructor, which means now you have two injections, one in the constructor and one in the method, which you can also standardize by moving method-level injection to the constructor but that dependency is not required for all the methods in the controller, which makes it unnecessarily resolved. This could be easier if we could inject the dependency into the controller method contextually.
You can do it easily with bindMethod
Example:
AppServiceProvider.php
\App::bindMethod('App\Jobs\MyJob@handle', function ($job, $app) {
return $job->handle(
\App::make(Depdency1::class),
\App::make(Depdency2::class),
config('my-config'),
env('SOMETHING')
);
}
You can do it easily with
bindMethod
Example:
AppServiceProvider.php
\App::bindMethod('App\Jobs\MyJob@handle', function ($job, $app) { return $job->handle( \App::make(Depdency1::class), \App::make(Depdency2::class), config('my-config'), env('SOMETHING') ); }
Can this also work with single action controllers (__invoke()
usage) and how would one do this?
It depends on how you call to that controller. To be able to use autobinding, you should call the method using the framework utilities.
I tested and it doesn't seem to work for single action controllers.
I tried to use App::bindMethod(Controller::class)
(as the __invoke()
method won't get called explicitly), but it doesn't work. If I explicitly use Controller@__invoke
it doesn't work either.
If you know a way to make it work that would be amazing, but I think it just doesn't work for invokable controllers?
I can't test it right now, but it is strange that 'controller@__invoke' is
not working because internally the call to the method is the same than a
normal method.
How are you calling the method? Are you using laravel internals?
The method injection is done by this trait, so you can investigate.
\Illuminate\Routing\RouteDependencyResolverTrait::resolveMethodDependencies
Take into account that you can't call directly the method, you must use the trait. It is done automatically when the controller is assigned to a route.
It's a controller and called as a route.
When I have some time I will look into this further. The route only lists the class name as it's a single action controller, that might be the problem. I'm sure it will work if you define the route like this:
Route::post('route', Controller::class . '@__invoke')
instead of (like you usually would with single action controllers):
Route::post('route', Controller::class)
+1 for contextual binding in a job's handle() method.
Here's a case for it:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Repositories\Contracts\GetAllComedians;
use App\Transformers\Contracts\ComedianTransformer;
use App\Transformers\Contracts\ExtractHitsTransformer;
class ComedianController extends Controller
{
private $repository;
private $hitsTransformer;
private $comedianTransformer;
public function __construct(
GetAllComedians $repository,
ExtractHitsTransformer $hitsTransformer,
ComedianTransformer $comedianTransformer
) {
$this->repository = $repository;
$this->hitsTransformer = $hitsTransformer;
$this->comedianTransformer = $comedianTransformer;
}
public function index() {
return array_map(
$this->comedianTransformer,
$this->hitsTransformer->__invoke(
$this->repository->getAllComedians()
)
);
}
}
If I could typehint HitsTransformer in the method it would be much nicer than explicitly calling ->__invoke(..)
TL; DR.
Laravel now is version 7. Can we bind dependencies into methods like this?
$this->app->when(DeleteOverdueFilesJob::class)
->method('handle')
->needs(FilesRepository::class)
->give(EloquentFilesRepository::class);
Hey all. I'm going to lock this thread for now as we've had this request numerous time in the past but we decided that we won't be supporting this long ago. Sorry about that.
Most helpful comment
+1 I would love to use contextual binding in a job's
handle()
method.