Introduction
Laravel handles managing class dependencies and dependency injection through its Service Container. Any class can have dependencies resolved using dependency injection in the class constructor. As of Laravel 5.0, this is also possible in class methods.
The service container - leveraging PHP's Reflection API - resolves concrete class implementations for you automatically.
If your class has a method called createUser
, which requires an instance of UserRepository
, type hint it and the service container will inject an instance for you.
What is container binding?
Unlike concrete implementations, interfaces are not instantiable. An interface describes the functionality a concrete class must implement. For example, if you were to have an interface UserRepository
and type hint against this in your class:
1// app/Contracts/UserRepository.php 2 3namespace App\Contracts\Repositories; 4 5interface UserRepository 6{ 7 public function create(array $data); 8} 9 10// app/Http/Controllers/UserController.php11 12namespace App\Http\Controllers;13 14use Illuminate\Http\Request;15use App\Http\Controllers\Controller;16use App\Contracts\Repositories\UserRepository;17 18class UserController extends Controller19{20 protected $repository;21 22 public function __construct(UserRepository $repository)23 {24 $this->repository = $repository;25 }26 27 public function store(Request $request)28 {29 $this->repository->create($request->all());30 }31}
Without binding a concrete implementation to the container you'll encounter a BindingResolutionException
accessing the store
method. The service container will resolve the interface dependency in the __construct
method, but an interface cannot be instantiated.
1Target [App\Contracts\Repositories\UserRepository] is not instantiable.
How does it work?
Using container binding lets you tell the service container that when the application asks for UserRepository
, it should return a specific class, rather than the interface itself. The container is capable of resolving concrete objects using PHP's reflection API, so you should only bind classes that depend on interfaces.
1// app/Providers/AppServiceProvider.php 2 3namespace App\Providers; 4 5use Illuminate\Support\ServiceProvider; 6use App\Repositories\EloquentUserRepository; 7use App\Contracts\Repositories\UserRepository; 8 9class AppServiceProvider10{11 public function register()12 {13 $this->app->bind(UserRepository::class, EloquentUserRepository::class);14 }15}16 17// app/Repositories/UserRepository.php18 19namespace App\Repositories;20 21use App\User;22use App\Contracts\Repositories\UserRepository;23 24class EloquentUserRepository implements UserRepository25{26 public function create(array $data)27 {28 return User::create($data);29 }30}
Now, any time the UserRepository
interface is needed by your application, the service container will inject an instance of EloquentUserRepository
.
This makes swapping the persistence layer in this instance easier1 in future. If you type hint the UserRepository
interface throughout your application, should you ever need a different implementation just update the binding and the container will handle the rest. The application shouldn't care how the UserRepository
is implemented, just that it is.
What about many implementations of an interface?
Container binding works well when you only have one implementation of an interface at any given time. What about when you have many concrete implementations in use simultaneously? Consider working with Socialite to connect with Twitter and Instagram.
A registered user of your application can connect their Twitter or Instagram accounts to their account. You can create an AuthenticateTwitterUser
and AuthenticateInstagramUser
class that each implement an AuthenticateSocialUser
interface. Depending on the provider you need to connect with, you can bind the relevant implementation and inject it at run time.
When you type hint a concrete class to a constructor, this couples your business logic to it. There must be A Better Way ™. Of course there is a better way.
Enter contextual container binding
The service container can resolve a class depending on which context requested it using contextual container binding, decoupling your business logic.
1// app/Providers/AppServiceProvider.php 2 3namespace App\Providers; 4 5use Illuminate\Support\ServiceProvider; 6use App\Social\Connect\AuthenticateTwitterUser; 7use App\Social\Connect\AuthenticateInstagramUser; 8use App\Http\Controllers\Connect\TwitterAuthController; 9use App\Contracts\Social\Connect\AuthenticateSocialUser;10use App\Http\Controllers\Connect\InstagramAuthController;11 12class AppServiceProvider extends ServiceProvider13{14 // ...15 public function register()16 {17 $this->app->when(TwitterAuthController::class)18 ->needs(AuthenticateSocialUser::class)19 ->give(AuthenticateTwitterUser::class);20 21 $this->app->when(InstagramAuthController::class)22 ->needs(AuthenticateSocialUser::class)23 ->give(AuthenticateInstagramUser::class);24 }25}
Now you can keep controllers slim, using inheritance to share common functionality and define only the methods that differ between providers. For example, Twitter and Instagram return different parameters in their callback, so you would extract the callback handling to separate controllers to facilitate this.
Conclusion
Container binding lets you manage dependencies across your entire application from one place. Contextual binding resolves many interface implementations for distinct use cases throughout your application.
Note: Contextual container binding only works for dependencies declared in the __construct
method. It does not work for method injection and it was never the intention to do so. This raises an interesting question; should you have many distinct implementations of an interface within a single class? I would suggest that this might be the point at which you identify whether or not you should be breaking the class down into separate concerns.
1: Switching your persistence layer isn't as simple as changing from EloquentUserRepository
to MongoUserRepository
. You still need to ensure that you're still returning the same data in both instances. This illustrates that switching application bindings is much easier when type hinting an interface.