Laravel Inversion of Control Implementation using Contextual Binding
Tags: laravel, php, ioc, oop
Published: 2021-06-19 12:13:09

## Introduction

In this post, we will tackle about "Inversion of Control" principle. We will learn when to use it and how we can use it with Laravel using [Contextual Binding](https://laravel.com/docs/8.x/container#contextual-binding) in the [Service Container](https://laravel.com/docs/8.x/container). Although this topic assumes that you have the basic knowledge with Laravel and a general OOP concepts.

At the time of writing this, the current version for Laravel is on version 8.

## What is Inversion of Control?

If you know what is [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) then it's basically just a reverse of it. For Dependency Injection the code typically depends directly on the [class](https://www.w3schools.com/php/php_oop_classes_objects.asp). With [Inversion of Control (IoC)](https://en.wikipedia.org/wiki/Inversion_of_control) we invert it, the code does not depend on the class directly but only to the interface and we bind it in the [service container](https://laravel.com/docs/8.x/container). So when we Inject dependency to a certain class or [Controller](https://laravel.com/docs/8.x/controllers) we call the [Interface](https://www.w3schools.com/php/php_oop_interfaces.asp) and not the class. 

## When to use Inversion of Control?

One must be aware and has to fully understand the scenario or what problem or certain feature to be implemented. There are a lot of problems and are a lot of ways to address it. But to choose the proper method of addressing the problem is a good way to approach it. Things does not have to be complex and should just be simple as possible. 

Other than that, say for a given scenario, your client wants to support multiple payment providers for the project that you are building. At your first glance you might think that you'll have to create a lot of classes for it and have different implementation details on each one of them, but the problem arises that you might have to inject a lot these in your Controllers or that you might have to put up some conditional logics just to use the correct payment provider.

That can work but it may not the ideal implementation. Then that's the time we will make use of abstraction using Inversion of Control, we can then only inject a single dependency to a Controller or whatever class it requires and that leaves us lesser code to write. It keeps it simple, and that also means it'll be easier to maintain it in the long term.

## Creating a PaymentInterface

Just for a quick, simple, and straight-forward example let's have a `PaymentInterface` that requires 1 method to implement whichever a class implements this interface or abstract.

So let's just create a directory under `app` directory of a fresh Laravel project, and call this directory as `Interfaces` and have a file created named as `PaymentInterface.php` and for its content, we have this

```php
<?php

namespace App\Interfaces;

interface PaymentInterface
{
  /**
   * @param float $amount 
   * @return mixed 
   */
  public function pay(float $amount): string;
} 
```

We only require classes to implement `pay` method that takes an argument `$amount` type hinted with `float` and it returns a `string` 

## Creating payment services that implements PaymentInterface

Let's say the client wants to have at least 3 payment providers, let's just call it whatever we want in this case.

- Paypal
- SquarePay
- Stripe

We have at least 3 payment providers but with different implementation details because we might have to setup a few configuration for each of these third party APIs. Typically we want these configuration setup private and should not be exposed publicly, we only expose what is defined in the `PaymentInterface`

So let's define these services, we'll start off with `Paypal`

```php
<?php

namespace App\Services;

use App\Interfaces\PaymentInterface;

class PaypalService implements PaymentInterface
{
	public function pay(float $amount): string
	{
		return "From PaypalService $amount";
	}
}
```

The `PaypalService` implements the `PaymentInterface` and the `pay` method as well, and as defined from the abstract class or interface that `pay` should return a `string` then we'll return it with a type of `string` so we basically just know right away what we should be returning.

For the `SquarePayService`

```php
<?php

namespace App\Services;

use App\Interfaces\PaymentInterface;

class SquarePayService implements PaymentInterface
{
	public function pay(float $amount): string
	{
		return "From SquarePayService $amount";
	}
}
```

For the `StripeService`

```php
<?php

namespace App\Services;

use App\Interfaces\PaymentInterface;

class StripeService implements PaymentInterface
{
	public function pay(float $amount): string
	{
		return "From StripeService $amount";
	}
}
```

Now we have those defined and implemented the `PaymentInterface` we can move on to having to dynamically bind the interface with the corresponding class or payment provider.

## Exposing the payment service providers to REST API

Now let's go over and create the controllers for each of these payment service providers that we defined. If you are coding along then open up your terminal and let's create these controllers using the artisan commands.

```bash
# Will create a directory called "PaymentProvider" and have 
# the controller named as defined "PaypalController" for example

# Paypal
php artisan make:controller PaymentProvider/PaypalController

# Stripe
php artisan make:controller PaymentProvider/StripeController

# SquarePay
php artisan make:controller PaymentProvider/SquarePayController
```

Then we'll go over each of these controllers and we will inject `PaymentInterface` into the constructor and pass it down into a private field.

`PaypalController`

```php
<?php

namespace App\Http\Controllers\PaymentProvider;

use App\Http\Controllers\Controller;
use App\Contracts\PaymentInterface;
use Illuminate\Http\Request;

class PaypalController extends Controller
{
    private $paymentService;
    
    public function __construct(PaymentInterface $paymentService)
    {
        $this->paymentService = $paymentService;
    }

    public function index()
    {
        return response()->json([
            'data' => $this->paymentService->pay(250.0),
        ]);
    }
}
```

`StripeController`

```php
<?php

namespace App\Http\Controllers\PaymentProvider;

use App\Http\Controllers\Controller;
use App\Contracts\PaymentInterface;
use Illuminate\Http\Request;

class StripeController extends Controller
{
    private $paymentService;
    
    public function __construct(PaymentInterface $paymentService)
    {
        $this->paymentService = $paymentService;
    }

    public function index()
    {
        return response()->json([
            'data' => $this->paymentService->pay(10.0),
        ]);
    }
}
```

`SquarePayController`

```php
<?php

namespace App\Http\Controllers\PaymentProvider;

use App\Http\Controllers\Controller;
use App\Contracts\PaymentInterface;
use Illuminate\Http\Request;

class SquarePayController extends Controller
{
    private $paymentService;
    
    public function __construct(PaymentInterface $paymentService)
    {
        $this->paymentService = $paymentService;
    }

    public function index()
    {
        return response()->json([
            'data' => $this->paymentService->pay(5.0),
        ]);
    }
}
```

Once that's done, we can then expose these controllers as an endpoint to the REST API. 

We define it in `api.php`

```php
<?php

use App\Http\Controllers\PaymentProvider\PaypalController;
use App\Http\Controllers\PaymentProvider\SquarePayController;
use App\Http\Controllers\PaymentProvider\StripeController;
use Illuminate\Support\Facades\Route;

Route::get('pay-with-paypal', [PaypalController::class, 'index']);
Route::get('pay-with-stripe', [StripeController::class, 'index']);
Route::get('pay-with-squarepay', [SquarePayController::class, 'index']);
```

I just defined it with `GET` request HTTP method just for simplicity of the tutorial. But for actual payment implementations, prefer to use `POST` instead that it contains a payload data of payment information containing the amount, the account ID and any other sensitive information.

So there we defined it, you might be tempted to test it out with your HTTP client to see if it works. But actually it won't work yet as we didn't define it to act that way. So let's proceed to using Contextual Binding implementation.

## Contextual Binding

We will be defining these bindings in the `AppServiceProvider` or you can create a different service provider that is relevant to its implementation, it can be `PaymentServiceProvider` (or any other name as you prefer) and have it registered in `AppServiceProvider`. But just for the sake of simplicity for the tutorial I will just bind the interface and their corresponding services directly into the `AppServiceProvider`.

```php
<?php

namespace App\Providers;

use App\Http\Controllers\PaymentProvider\PaypalController;
use App\Http\Controllers\PaymentProvider\SquarePayController;
use App\Http\Controllers\PaymentProvider\StripeController;
use App\Interfaces\PaymentInterface;
use App\Services\PaypalService;
use App\Services\SquarePayService;
use App\Services\StripeService;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->when(PaypalController::class)
            ->needs(PaymentInterface::class)
            ->give(PaypalService::class);

        $this->app->when(StripeController::class)
            ->needs(PaymentInterface::class)
            ->give(StripeService::class);

        $this->app->when(SquarePayController::class)
            ->needs(PaymentInterface::class)
            ->give(SquarePayService::class);
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
} 
```

On the `register` method of the `AppServiceProvider` is where we define the Contextual Binding, as you can see it checks on the `Controller` using the `when` method then the `needs` is referring to the dependency of that particular controller, and the last method chaining is `give` which is what we want to bind it to, these are the service classes that we defined `PaypalService`, `StripeService`, and `SquarePayService`.

In other words, if the `PaypalController` injects the `PaymentInterface` the service container would know that its corresponding binding will result to `PaypalService` and the same goes for `StripeController` and `SquarePayController`.

Now that we defined that in the service container, we can proceed to testing it out manually using our HTTP or just the browser to see if it worked.

## Manually Testing in browser

It's just a simple test. So make sure you have an active server running via `php artisan serve` and then just put up the endpoints that we defined in `api.php`; We have the following:

- `/api/pay-with-paypal`
- `/api/pay-with-stripe`
- `/api/pay-with-squarepay`

Now let's see each of these if it returns the actual implementation from each services that we defined above when creating them.

`/api/pay-with-paypal`

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s7efoxj18glnbabpi3nv.png) 

That is exactly what we wrote in `PaypalService` to return a string from the `pay` method and we even specified it with "PaypalService" just for us to indicate where the implementation is coming from. So using Contextual Binding works and it solves our problem!

`/api/pay-with-stripe`

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpymv4072dydw0wma3bg.png) 

And this is for Stripe.

`/api/pay-with-squarepay`

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irqy89t91rz22z0jjams.png) 

And our last payment provider that our client wants. We got it.

## Conclusion

Now that we learned how to tackle multiple payment providers using the **Inversion of Control (IoC)** principle, we learned how to implement it with Laravel's service container using Contextual Binding, and we understand that we'll always go with the best approach to address a problem. Don't use Inversion of Control when it's not really relevant solution at all, no need to add complexity. Only use it when it seems the best solution. 

I hope this was useful and that you have learned something new. Thanks for taking the time to read and have a good day!

[Full Source Code](https://github.com/carlomigueldy/laravel-inversion-of-control-tutorial)