Greetings! I'm Aneesh Sreedharan, CEO of 2Hats Logic Solutions. At 2Hats Logic Solutions, we are dedicated to providing technical expertise and resolving your concerns in the world of technology. Our blog page serves as a resource where we share insights and experiences, offering valuable perspectives on your queries.
Understanding how the service container works is critical in architecting scalable decoupled code using Laravel. In this article, we will explore how the service container helps us to achieve this using some practical examples.
Suppose that you have a reports page where you export users in the system in as an XML. You have a folder AppServicesUserExport where you have a UserExportInterface.
1 2 3 4 5 | namespace AppServicesUserExport; interface UserExportInterface{ public function export(); } |
This is how the XmlUserExport class looks like:
1 2 3 4 5 6 7 8 9 10 | namespace AppServicesUserExport; class XmlUserExport implements UserExportInterface { public function export() { //do the xml user export return "This is the xml user export"; } } |
This is how the controller looks like
1 2 3 4 5 6 7 8 | class UserExportController extends Controller { public function export() { $userExport = new XmlUserExport; return $userExport->export(); } } |
Note 1: UserExportController is creating the object.
After a while, you are asked to change it to a csv export. So now you create this new CsvUserExport class
1 2 3 4 5 6 7 8 9 10 | namespace AppServicesUserExport; class CsvUserExport implements UserExportInterface { public function export() { //do the csv user export return "This is the csv user export"; } } |
You now have to edit the Controller to use this class. This means that the Controler and the implementations are tightly coupled. How can we overcome this shortcoming?
We can bind the interface to implementation to an interface using the Service Container (https://laravel.com/docs/5.8/container#binding-interfaces-to-implementations). Add this to the ProvidersAppServiceProder.
1 | $this->app->bind('AppServicesUserExportUserExportInterface', 'AppServicesUserExportCsvUserExport'); |
Then update our controller like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class UserExportController extends Controller { private $userExport; public function __construct(UserExportInterface $userExport ) { $this->userExport = $userExport; } public function export() { return $this->userExport->export(); } } |
Note 2: The controller is no longer responsible for creating the export class object. It’s the service container that does it.
Whenever we want to switch an implementation, we just need to update the binding in the AppServiceProvider. So if you want to switch to a json export in future, you create the JsonUserExport class and update the AppServiceProvider like this,
1 | $this->app->bind('AppServicesUserExportUserExportInterface', 'AppServicesUserExportJsonUserExport'); |
Now we are adhering to the Open-Closed Principle and Dependency Inversion Principle in the SOLID principles.
But what if you want XmlUserExport on UserExportController and CsvUserExport on ReportController?
This is when we use contextual binding (https://laravel.com/docs/5.8/container#contextual-binding)
Add this to the AppServiceProvider. Ideally, we should be creating a new Service Provider, but it’s ok for our small classes.
1 2 3 4 5 6 7 8 9 10 11 | $this->app->when(UserExportController::class) ->needs(UserExportInterface::class) ->give(function () { return new XmlUserExport; }); $this->app->when(ReportController::class) ->needs(UserExportInterface::class) ->give(function () { return new CsvUserExport; }); |
Now the same interface on both controllers will return the implementations defined in the Service Provider.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use AppServicesUserExportUserExportInterface; class ReportController extends Controller { private $userExport; public function __construct(UserExportInterface $userExport ) { $this->userExport = $userExport; } public function export() { return $this->userExport->export(); } } |
The core idea here is the Inversion Of Control (IoC). Previously (Note 1), the UserExportController is controlling the creation of the XmlUserExport. But in Note 2 and Note 3, we see that the Service Container is controlling the object creation. This way, the controller and the export classes are loosely coupled.