Reputation: 1369
Problem is that i don't know how to bind multitple implementations of one interface.
Example:
// One interface
interface SmsInterface {
...
}
// First implementation using SmsCoin
class SmscoinAPI implements SmsInterface {
...
}
// Second implementation using Fortumo
class FortumoAPI implements SmsInterface {
...
}
// Two controllers:
class SmsCoinController {
public function __construct(SmsInterface $sms) {
$this->sms = $sms
}
}
class FortumoController {
public function __construct(SmsInterface $sms) {
$this->sms = $sms
}
}
Question: How I can bind SmsInterface with implementation FortumoApi for FortumoController, and bind SmsInterface with implementation SmsCoinApi for SmsCoinController ?
I was using ServiceProvider for registering bindings, can I do it there ? If not where should bindings put?
EDIT:
I can't get answer anywhere, read many laravel books, everywhere is said to use multiple implementations, but nowhere is shown how to swap/switch those implementations.
If i have one interface and two implementations, how to bind and swap them in controller. Do i need to do that in that controller constructor method ? or in routes by checking controller's route or in filters.php ? or in Service provider ? and how to technically correclty write that code ?
Upvotes: 6
Views: 5381
Reputation: 3043
I've been trying to do the same, until I found it in the Laravel docs. It's called Contextual Binding, and the way in which you have to implement it is like this:
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
Hope it helps!
Upvotes: 3
Reputation: 1369
This question took for me long time to solve it. After Laravel 5.0 is out i'm posting solution that i have found using Contextual binding
Consider this example, when one controller needs one interface with multiple implementations.
<?php
// We are creating one abstract controller with one logic
abstract class AbstractSearchController
{
protected $searchService;
public function __construct(SearchServiceInterface $searchService)
{
$this->searchService = $searchService;
}
public function getResult($keyword)
{
return $this->searchService->getItems($keyword);
}
}
// In routes.php file we can point url like: http://example.com/search/posts/?keyword=my-search-keyword to use
// something like this: Route::resource('search/posts', 'PostSearchController', ['only' => ['index']]);
class PostSearchController extends AbstractSearchController
{
// No code here as this controller only is needed so that we can point Specific implementation to it
}
// In routes.php file we can point url like: http://example.com/search/members/?keyword=my-search-keyword to use
// something like this: Route::resource('search/members', 'MemberSearchController', ['only' => ['index']]);
class MemberSearchController extends AbstractSearchController
{
//
}
// Our main interface that will have multiple implementations at same time
interface SearchServiceInterface
{
public function getItems($keyword);
}
// Our first implementation of interface
class MemberSearchRepo implements SearchServiceInterface
{
public function getItems($keyword)
{
// Get members by keyword
}
}
// Our second implementation of interface
class PostSearchRepo implements SearchServiceInterface
{
public function getItems($keyword)
{
// Get posts by keyword
}
}
// When PostsSearchController needs Search Logic point IoC to give our first implementation
$this->app->when('PostSearchController')->needs('SearchServiceInterface')->give('PostSearchRepo');
// When MemberSearchController needs Search Logic point IoC to give our seconds implementation
$this->app->when('MemberSearchController')->needs('SearchServiceInterface')->give('MemberSearchRepo');
I hope this extended example will help people to understand how to implement feature that I needed for Laravel 4.x with abillities that Laravel 5 provides
Upvotes: 7
Reputation: 391
Though your answer works, I've run into the same issue in my own projects. The solution I came up with was to decide which implementation of a specific interface to use before execution even reaches the controller.
Many people use the global.php
file to set up their bindings for concrete classes. That's fine, and I tried that for a long time, but the larger your app gets, the more bindings you'll start making, and it gets to be a little long for the file when it's really meant to be a catch all. I started storing all my bindings in a bindings.php
file, in the start
folder and included a require_once
at the end of the global.php
file.
Anyway, in my bindings.php
file I place all my logic to determine which concrete implementations are required for this server request. In my case, my concrete classes are determined by the county the user requests and are specified via either POST or GET variables.
if(Input::has('state') && Input::has('county'))
{
$var_name = strtoupper(Input::get('state')).'.'.ucfirst(strtolower(Input::get('county')));
App::bind('SearchModelInterface', Config::get('counties.'.$var_name.'.searchClass'));
App::bind('InserterModelInterface', Config::get('counties.'.$var_name.'.inserterClass'));
Config::set('propertyClassName', Config::get('counties.'.$var_name.'.propertyClass'));
Config::set('chosenCounty', strtolower(Input::get('county')));
App::bind('Property', function($app)
{
$class = Config::get('propertyClassName');
return new $class();
});
}else{
App::bind('SearchModelInterface', 'Harris_TX_SearchModel');
App::bind('InserterModelInterface', 'Harris_TX_InserterModel');
App::bind('Property', function($app)
{
return new Harris_TX_Property();
});
Config::set('chosenCounty', 'harris');
}
As you can see, there's several interfaces that are set according to config files depending on which county is requested, and defaults provided if none are. It's also helpful to see that all of Laravel's facades are fully available here including the Config facade, which allows me to set values that will be used throughout the request execution.
I've never seen anything like this recommended by Taylor, Jeffrey, or any of the other big names in the community, but this works really well for me.
Upvotes: 2
Reputation: 987
By binding an interface to a class in the IOC (from within your Service Provider), you are performing this binding "globally" so you won't be able to automatically inject different classes using the IOC.
You could manually return an instance of your controller with the correct class injected from your route closure.
Route::get('sms', function()
{
return new SmsCoinController(new SmscoinAPI);
});
Route::get('fortumo', function()
{
return new FortumoController(new FortumoAPI);
});
Either that or don't use interface hints in your constructors at all. Just hint the actual class and the IOC will inject it for you.
Upvotes: 1