How the service container helps you to architect your Laravel projects better

How the service container helps you to architect your Laravel projects better

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 App\Services\UserExport where you have a UserExportInterface.

namespace App\Services\UserExport;

interface UserExportInterface{
    public function export();
}

This is how the XmlUserExport class looks like:

namespace App\Services\UserExport;

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

 

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

namespace App\Services\UserExport;

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 Providers\AppServiceProder.

   $this->app->bind('App\Services\UserExport\UserExportInterface', 'App\Services\UserExport\CsvUserExport');

Then update our controller like this

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,

 $this->app->bind('App\Services\UserExport\UserExportInterface', 'App\Services\UserExport\JsonUserExport');

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.

$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.

use App\Services\UserExport\UserExportInterface;

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

eight + thirteen =

2hats Logic HelpBot