loading...

Laravel Service Container

patelparixit07 profile image Parixit Patel ・4 min read

In this post, we will discuss the most important aspect of Laravel framework Service Container.

Let's understand the definition first...

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are “injected” into the class via the constructor or, in some cases, “setter” methods.

In simple terms, the service container is a container that holds all the bindings that need to run Laravel application smoothly.you can bind almost everything you’d like to instantiate programmatically later in your application when needed. That means Service Container holds a single object of all of our various bindings.

So whenever, you need to use any inbuilt or custom service you can type-hint in a class constructor or method it will automatically be injected from service container as its container that holds all class dependencies.

Let's assume, we have one class with the name MathService which used to do some basic operations like addition, multiplication, etc. Below is the code of this class.

App\Services\MathService.php

<?php

namespace App\Services;
class MathService
{
    public function doAddition($numbers)
    {
        return array_sum($numbers);
    }

    public function doMultiplication($numbers)
    {
        return array_product($numbers);
    }
}

As you can see, there two methods on \App\Services\MathService class doAddition which calculates the sum of numbers provided in array using array_sum function and doMultiplication which multiplies all the numbers provided as an array using array_product function.

To use services provided by \App\Services\MathService class we first need to bind it with service containers. the binding will be like following, we just need to pass the path of MathService class and it will be bind on the container.

<?php

App::bind('MathService',\App\Services\MathService::class);

By doing this, we are telling laravel to store \App\Services\MathService class in the container with label MathService and return it back when we need an instance of a class. This is one time binding of class and can get any number of instances from our application.

Now suppose we require service provided by \App\Services\MathService class in any of our application functionality. To achieve this, we first need to resolve service that we already bound in the container and then call respective methods.

To resolve service from our application can use either of two ways...

<?php

$mathService = app()->make('MathService');

// or

$mathService = resolve('MathService');

The service is resolved now. we can call doAddition & doMultiplication methods from our application.

<?php

$sum = $mathService->doAddition([40, 20, 10]);
$product = $mathService->doMultiplication([4, 2, 3]);

print_r($sum);
print_r($product);

// Result: Sum => 70, Product => 24

Automatic Dependency Injection

Important note from the Laravel documentation:

There is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed on how to build these objects since it can automatically resolve these objects using reflection.

Here, Actually we don't need to bind class on the container. We can just type-hint dependencies directly on the method or constructor of a controller like below.

<?php

function index(MathService $mathService)
{
    $sum = $mathService->doAddition([40, 20, 10]);
    $product = $mathService->doMultiplication([4, 2, 3]);
}

This is just the basic of service container in which we bind service and use it on our app. But remember, All the binding of services like this should be always on register method of Service Providers.

Interface binding

On Service Container we can also bind a specific implementation of an interface to it, in this way whenever we resolve this interface we will end up with the concrete class that is bound to it.

For the above example, We can create Interface and two classes with different implementation and bind any of the implementations to the container.

Let's create MathServiceInterface

App\Services\MathServiceInterface.php

<?php

namespace App\Services;
Interface MathServiceInterface
{
    public function doAddition(array $numbers);

    public function doMultiplication(array $numbers);
}

MathService which implements MathServiceInterface

App\Services\MathService.php

<?php

namespace App\Services;
class MathService implements MathServiceInterface
{
    public function doAddition($numbers)
    {
        return array_sum($numbers);
    }

    public function doMultiplication($numbers)
    {
        return array_product($numbers);
    }
}

CustomMathService which also implements MathServiceInterface

App\Services\CustomMathService.php

<?php

namespace App\Services;
class CustomMathService implements MathServiceInterface
{
    public function doAddition($numbers)
    {
        $sum = 0;
        foreach($numbers as $num)
        {
            $sum = $sum + $num;
        }
        return $sum;
    }

    public function doMultiplication($numbers)
    {
        $product = 1;
        foreach($numbers as $num)
        {
            $product = $product*$num;
        }
        return $product;
    }
}

As you can see, we have two different classes MathService & CustomMathService with a different implementation of doAddition & doMultiplication methods.

Now instead of binding these classes on container lets bind MathServiceInterface with the container.

<?php

App::bind('\App\Services\MathServiceInterface', function () {
    return new \App\Services\MathService();
});

Here, when MathServiceInterface will be resolved, Laravel gives us an instance of MathService class. And by calling methods using this instance will get results using MathService class implementation.

Controller:

<?php

public function index() {
    $mathService = app()->make('\App\Services\MathServiceInterface');
    $sum = $mathService->doAddition([40, 20, 10]);
    $product = $mathService->doMultiplication([4, 2, 3]);   
    /*
    This will calculate results 
    using MathService class implementation
    */
}

Now if we want other implementation called CustomMathService of MathServicesInterface then just need to change in binding.

<?php

App::bind('\App\Services\MathServiceInterface', function () {
    return new \App\Services\CustomMathService();
});

The code will still work the same as earlier but with a different implementation of CustomMathService class. this way we can de-couple any of interface implementation easily by changing just binding.

Another way to resolve is by using reflection.

<?php

App::bind(\App\Services\MathServiceInterface::class, \App\Services\CustomMathService::class);

This will bind class and resolve it using reflection. By doing this, don't need to resolve using resolve() method. We can just type-hint dependency on a constructor.

Controller:

<?php

protected $mathService;

public function __construct(\App\Services\MathServiceInterface $mathService)
{
    $this->mathService = $mathService;
}

public function index() {
    $sum = $this->mathService->doAddition([40, 20, 10]);
    $product = $this->mathService->doMultiplication([4, 2, 3]);
}

Hope this post helps you to get some insights about service container.

For detailed information about Service Containers: https://laravel.com/docs/7.x/container

Thanks for reading.

Posted on by:

patelparixit07 profile

Parixit Patel

@patelparixit07

Software Developer looking to develop my skills further particularly around programming & technologies

Discussion

pic
Editor guide
 

Hi Parixit,

Many thanks for the article, very beautifully explained.

However, I got an error. It reads as


ReflectionException
Class App\Services\MathService does not exist


When I die dump inside the route, I get the binding listed successfully.

"MathService" => array:2 [▼
      "concrete" => Closure($container, $parameters = []) {#140 ▼
        class: "Illuminate\Container\Container"
        this: Illuminate\Foundation\Application {#6}
        use: {▶}
        file: "/home/vagrant/nadias/vendor/laravel/framework/src/Illuminate/Container/Container.php"
        line: "259 to 267"
      }
      "shared" => false
    ]

MathService class


namespace App\Services\MathService;

class MathService
{
    public function doAddition($numbers)
    {
        return array_sum($numbers);
    }

    public function doMultiplication($numbers)
    {
        return array_product($numbers);
    }
}

Controller function


namespace App\Http\Controllers;
use App\Services\MathService;

class MathController extends Controller
{
  public function math(MathService $mathservice)
    {
        $sum = $mathService->doAddition([40, 20, 10]);
        return $sum;
    }
 

I guess there is something wrong with namespace and class name on MathService class. Hope you stored MathService class inside app/Services folder

Try to update this on MathService class :

namespace App\Services\MathService;
// To
namespace App\Services;

class AdhocMath
// To
class MathService
 

Many thanks.

Once I added the binding it worked.

App::bind('MathService',\App\Services\MathService::class);

There must have been some cache refresh issues.