Laravel : Binding Interface to Implementations

Amarjot Singh
4 min readOct 1, 2023
Image by Brigitte Werner from Pixabay

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection.

Laravel has in-built zero configuration resolution, which helps in type-hinting dependencies in routes, controller and event listeners. For example, we often use Request $request in controller function to access the current request. Even though we haven’t written any code for that, it’s readily available to us to be used in controller.

If a class has zero dependency or depends only another concreate class. We can use type-hinting to get the class instance inside our function.

<?php

class GoogleMaps {
public function data(){
// generate map
}
}

Route::get('map', function (GoogleMaps $map) {
return $map->data();
});

In above code, we didn’t have to create an instance. Laravel automatically does that for us.

$maps =  new GoogleMaps();

Now, question arises when will we create our own service container ?

According to laravel documentation:

First, if you write a class that implements an interface and you wish to type-hint that interface on a route or class constructor, you must tell the container how to resolve that interface.

In this article, we will understand how it can be accomplished. To better understand, let’s start with an example.

Imagine, we have requirement of creating an API endpoint transaction. This endpoint is suppose to take amount and process with payment gateway. As of now, we allow Stripe payment gateway. In response, we want to know which payment gateway was used and status of transaction.

Let’s begin by creating folders for Interface and classes.

PaymentGateway will look like

<?php

namespace App\Interfaces;

interface PaymentGateway {

function process();
}

StripePayment will look like

<?php

namespace App\Classes;
use App\Interfaces\PaymentGateway;

class StripePayment implements PaymentGateway
{
public function __construct(
private string $name
) {
}

public function process()
{
return [
'gateway' => $this->name,
'message' => 'Transaction processed successfully'
];
}
}

We introduced an interface with process function. Our class is implementing the interface and taking $name as constructor argument which must be provided while instantiating.

Now we have to let laravel know how to resolve PaymentGateway into StripePayment when type hinted. We can accomplish that by creating our own service provider with

php artisan make:provider PaymentGatewayProvider

It will generate a new file. Before, we can use this provider, we have to register it. It can be done inside config/app.php

    'providers' => ServiceProvider::defaultProviders()->merge([
// other service providers
App\Providers\PaymentGatewayProvider::class
])->toArray(),

Now let make changes to our PaymentGatewayProvider

<?php

namespace App\Providers;

use App\Classes\StripePayment;
use App\Interfaces\PaymentGateway;
use Illuminate\Support\ServiceProvider;

class PaymentGatewayProvider extends ServiceProvider
{
// Register bindings, interface => class
public $bindings = [
PaymentGateway::class => StripePayment::class
];

/**
* Register services.
*/
public function register(): void
{
$this->app->when(StripePayment::class)
->needs('$name')
->give('Stripe Payment');
}
}

In $binding we can declare Interface to class associations. In register method, we are telling when StripePayment ask for $name — dependency, provide it with the value of ‘Stripe Payment’. Other dependency like class can also be handled in this way.

Now we want to access this classes into our newly created TransactionController. Our controller will look like

<?php

namespace App\Http\Controllers;

use App\Interfaces\PaymentGateway;
use Illuminate\Http\Request;

class TransactionController extends Controller
{
/**
* Process transaction
* @return Array
*/
public function index(Request $request, PaymentGateway $gateway)
{

$request->validate([
'amount' => 'required'
]);

return $gateway->process();
}
}

and api.php looks like

Route::post('transaction', [TransactionController::class, 'index']);

As you can see, in our controller we are asking for implementation of interface — PaymentGateway $gateway . Laravel will automatically resolve it to StripePayment and our function call $gateway->process() will execute function inside StripePayment class.

So, our POST request

http://localhost/api/transaction

// with payload
{
"amount" : 2500
}

Will result into

{
"gateway": "Stripe Payment",
"message": "Transaction processed successfully"
}

Two months passed by and we got a request to include Moneris as another payment gateway. Since our Project is setup in right way. We can easily compensate for that by creating new class

<?php

namespace App\Classes;
use App\Interfaces\PaymentGateway;

class MonerisPayment implements PaymentGateway
{
public function __construct(
private string $name
) {
}

public function process()
{
return [
'gateway' => $this->name,
'message' => 'Transaction processed successfully'
];
}
}

and make changes to our PaymentGatewayProvider

<?php

namespace App\Providers;

use App\Classes\MonerisPayment;
use App\Classes\StripePayment;
use App\Interfaces\PaymentGateway;
use Illuminate\Support\ServiceProvider;

class PaymentGatewayProvider extends ServiceProvider
{
// Register bindings, interface => class
public $bindings = [
PaymentGateway::class => MonerisPayment::class,
PaymentGateway::class => StripePayment::class
];

/**
* Register services.
*/
public function register(): void
{
$this->app->when(StripePayment::class)
->needs('$name')
->give('Stripe Payment');

$this->app->when(MonerisPayment::class)
->needs('$name')
->give('Moneris payment');
}
}

Now, inside controller we have to type-hint which implementation we want — In real world, we will have settings page to select active payment gateway and associated class which will be passed to index function.

Our controller will look like

<?php

namespace App\Http\Controllers;

use App\Classes\MonerisPayment;
use App\Classes\StripePayment;
use App\Interfaces\PaymentGateway;
use Illuminate\Http\Request;

class TransactionController extends Controller
{
/**
* Process transaction
* @return Array
*/
public function index(Request $request, MonerisPayment $gateway)
{

$request->validate([
'amount' => 'required'
]);

return $gateway->process();
}
}

Our POST call will respond with

{
"gateway": "Moneris payment",
"message": "Transaction processed successfully"
}

There is a lot more that can be done with Service Container. Please refer to documentation. Thank you

--

--