Why, When and How Use Service Container in Laravel?

  • Home
  • Laravel
  • Why, When and How Use Service Container in Laravel?
Why, When and How Use Service Container in Laravel?

Laravel is powerful framework and one of the powerful tool is Service Container that help a lot while development any web project. If you use the Zero Configuration Resolution power of Service Container you will feel magic of Laravel.

Why Use Service Container in Laravel?

First of all we will check why need service container. As per last guide of Service Container we checked if we use Dependency Injection we need to provide a instance of our Service in Core PHP. But In Laravel we don’t need to create a instance of service we just create our service and use as type hint Injection or Laravel Automatically resolved this. This is why Laravel use Service Container.

When Use Service Container in Laravel?

we checked that why laravel use service container now lets take a look why we need to use service container. If you ever make project with laravel i’m sure you already used Service Container Feature. For Example if you type-hint Illuminate\Http\Request with $request in your method. Even You not provide Request Class Instance you can use use $request in your method why ? thanks to automatic dependency injection.

Now when we need to interact with service container ? There are two situation where you actually need to interact with service container

  1. When you want to type-hint a interface to class constructor or route
  2. While developing a Laravel package

How Use Service Container in Laravel?

Using Laravel Service Container is very easy you need to know some method that help you to bind your service with service container then service container automatically resolve whenever you use that service.

Binding

To add your service to the container you need to bind your service. For example you have PaymentService interface and you want to bind to service container.

PHPCopy
<?php

namespace App\Providers;

use App\Billings\PaypalAPI;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(PaypalAPI::class, function () {
            return new PaypalAPI(125);
        });
    }

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

we can also use Facades to bind any service to the container

PHPCopy
<?php

namespace App\Providers;

use App\Billings\PaypalAPI;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        App::bind(PaypalAPI::class, function () {
            return new PaypalAPI(125);
        });
    }

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

Singleton Binding

This is called simple binding and whenever we try to access multiple time it resolve multiple times so we can use a singleton method or pass extra argument which is shared parameter in bind().

PHPCopy
<?php

namespace App\Providers;

use App\Billings\PaypalAPI;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(PaypalAPI::class, function () {
            return new PaypalAPI(125);
        });
    }

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

or we can use like –

Plain TextCopy
<?php

namespace App\Providers;

use App\Billings\PaypalAPI;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(PaypalAPI::class, function () {
            return new PaypalAPI(125);
        }, true);
    }

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

Binding a Instance

There may be a situation where we have a instance of any particular service and we want to bind that to service container here is a example –

PHPCopy
<?php

namespace App\Providers;

use App\Billings\PaypalAPI;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $service = new PaypalAPI(125);
        $this->app->instance(PaypalAPI::class, $service);
    }

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

Interface Binding

Laravel Service Container mostly used with interface so whenever we want to change some code and Service that implement a interface we have no worry to change our code to our controller side. Let’s Check below Code –

PHPCopy
<?php

namespace App\Billings;

use Illuminate\Http\Request;

interface PaymentGatewayInterface
{
    public function process(Request $request);
    public function checkout($res);
}

we have a simple interface with two method that is process payment and checkout method.

Now we use this interface to create our main services. We made PaypalGateway and StripeGateway to process our payment. I just make some random code with srmklive/paypal payment package.

PHPCopy
<?php
namespace App\Billings\Gateways;

use App\Billings\PaymentGatewayInterface;
use Illuminate\Http\Request;
use Srmklive\PayPal\Services\ExpressCheckout;

class PaypalGateway implements PaymentGatewayInterface
{

    public function process(Request $request)
    {
        $products = [];
        $products['items'] = [
            ['name'    => 'Laravel Book',
                'price'   => 1200,
                'des'     => "Laravel Book for Advance Learning",
                'qty'     => 1 ]
        ];

        $products['invoice_id'] = uniqid();
        $products['invoice_description'] = "Order #{$products['invoice_id']} Billing";
        $products['return_url'] = route('success.pay');
        $products['cancel_url'] = route('success.pay');
        $products['total'] = 1200;

        $paypal = new ExpressCheckout();

        return $paypal->setExpressCheckout($products);

    }

    public function checkout($res)
    {
        return redirect($res['paypal_link']);
    }
}
PHPCopy
<?php
namespace App\Billings\Gateways;

use App\Billings\PaymentGatewayInterface;
use Illuminate\Http\Request;

class StripeGateway implements PaymentGatewayInterface
{

    public function process(Request $request)
    {
        // TODO: Implement process() method.
    }

    public function checkout($res)
    {
        // TODO: Implement checkout() method.
    }
}

now we have two service where any customer can pay via Paypal or Stripe as he/she wish. Now take a look to our binding section. We directly done this to our AppServiceProvider. You can use your custom Service Provider if you want.

PHPCopy
<?php

namespace App\Providers;

use App\Billings\Gateways\PaypalGateway;
use App\Billings\Gateways\StripeGateway;
use App\Billings\PaymentGatewayInterface;
use Illuminate\Support\ServiceProvider;
use Softon\Indipay\Indipay;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(PaymentGatewayInterface::class, function(){
            $gateway = request()->gateway;
           if ($gateway === "paypal"){
               return new PaypalGateway();
           }elseif ($gateway === "stripe"){
               return new StripeGateway();
           }
        });
    }

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

We can refactor our code but this time its not point to refactor.

Now take a look to our controller side.

PHPCopy
<?php

namespace App\Http\Controllers;

use App\Billings\PaymentGatewayInterface;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
    public function pay(Request $request, PaymentGatewayInterface $module)
    {
        $res = $module->process($request);
        return $module->checkout($res);
    }
}

In the controller how to solve the payment process via paypal or stripe because that’s not the controller job.

Contextual Binding

In some situation you may have two or more class who using same interface and you want to inject different implement depend on your situation. For example if you using Filesystem and want to upload photos in your local server and want to upload Video to your S3 bucket you can use contextual binding to solve this problem.

PHPCopy
<?php

namespace App\Providers;

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;

class PaymentServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->when(PhotoController::class)
            ->needs(Filesystem::class)
            ->give(function () {
                return Storage::disk('local');
            });

        $this->app->when(VideoController::class)
            ->needs(Filesystem::class)
            ->give(function () {
                return Storage::disk('s3');
            });
    }

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

Sometime we need to inject some primitive value such as integer, string values then you also can use Contextual Binding to solve this type of problem.

Let’s check the example – We have a controller that use constructor to use value in index method and return that value to output. I know it’s pretty nonsense to show this example but making a extra service is make you bore to check the guide.

PHPCopy
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', [\App\Http\Controllers\TestController::class, 'index']);

We made a route that call TestController’s index method. Now take a look to TestController Class.

PHPCopy
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestController extends Controller
{
    private $value;

    public function __construct(string $value)
    {
        $this->value = $value;
    }

    public function index(): string
    {
        return $this->value;
    }
}
PHPCopy
<?php

namespace App\Providers;

use App\Http\Controllers\TestController;
use Illuminate\Support\ServiceProvider;

class TestServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $value = "Dump Coder";
        $this->app->when(TestController::class)
            ->needs('$value')
            ->give($value);
    }

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

Now when we run open our project in browser that automatic resolved by the service container.

Tagging

Now we have a term about tagging. Yes you are right tagging is same as tag to multiple things. For Example we have some classes that related to same particular or identical task we give them a tag and we use same tag to get all classes. Let’s check the code –

PHPCopy
<?php

namespace App\Providers;

use App\Reports\Report;
use App\Reports\IncomeReport;
use App\Reports\ExpanceReport;
use App\Http\Controllers\ReportController;
use Illuminate\Support\ServiceProvider;

class ReportServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->tag([IncomeReport::class, ExpanceReport::class], 'reports');
        
        $this->app->when(ReportController::class)
            ->needs(Report::class)
            ->giveTagged('reports');
    }

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

Extending Bindings

Sometime we need to modify our resolved service and want to extend and use. When we use extend method its return a extended or modified service as its only argument.

PHPCopy
<?php

namespace App\Providers;


use Illuminate\Support\ServiceProvider;

class TestServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->extend(TestService::class, function ($service) {
            return new NewExtendedService($service);
        });
    }

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

Resolving

Sometime it’s happen that service container is enable to solve the dependency so we need to use some method that help us to overcome this problem.

We can resolve by some method but they do same work.

PHPCopy
// Using the App Facade
App::make(TestService::class, ['value'=> $value]);

// Using the app helper function
app(TestService::class, ['value'=> $value]);

// Using the resolve helper function
resolve(TestService::class, ['value'=> $value]);

// makeWith()
$this->app->makeWith(TestService::class, ['value'=> $value]);

Conclusion

Uff too much code and a long article. Hope you enjoyed the guide about why when and how use service container in laravel. If you still have any question just comment below and we will catch you.

Leave a Comment