Getting Started with Laravel Service Container, Service Providers and Facades

17 min read

Service Providers, service container and facades, they are definetely not something you need to know about early on in your laravel journey. They come up here and there once you start using packages in laravel and also when you need to update some of the core functionality of laravel like ensuring that your application uses https as the url scheme in production environments.

Stay with me as I take you through them to help you understand how they all come together to help you build your laravel appliations. They are definetely not a must to use but as I always say, it does not hurt to add more tools in your arsenal as this helps you navigate your way around popular/advanced codebases, help you contribute to them and hopefully use it yourself when necessary.

Service Container

The laravel service container plays a major role in providing us with dependency injection. If you have a class** (we’ll call this MyService) and you want to use it in another controller class(we’ll call this MyController), one way will be to simply instantiate the service in the constructor or method of the controller you intend to use it in and then call any accessible public method on it.

declare(strict_types=1);
...

final class MyController {

  public function index(): string {
    $myService = new MyService();

    return $myService->getName();
  }
}


This is perfect to do but with dependency injection, we can make it easier and simpler to use.

Laravel allows you to use the class, MyService in the constructor or a method of the MyController class using dependeny injection which it provides through the service container. It allows you to also use service providers to inject (bind) the dependencies of the class into the container if it has any.

declare(strict_types=1);

...

final class MyController {
  public function __construct(
    private MyService $myService
  ): void {

  }

  public function index(): string {
    return $this->myService->getName();
  }
}


In the example above, MyService is injected in the constructor allowing us to easily call it in the index method. Because it’s injected into the MyController class, this allows us to also mock the service by providing a dummy representation in our tests. So we are able to test our controller by providing a mock representation of the service.

Laravel uses the service container to support injecting the dependencies in a lot of internal implementations like route model bindings, controllers, event listeners, queued jobs and more.

The service container will automatically resolve any classes that only depends on other concrete classes or have no dependencies.

If you want to invoke a method on a class and allow laravel to directly inject the dependencies of the method, you can still take advantage of the service container either through automatic dependency injection or manually bind your dependency in a service provider and then using the service container, Illuminate\Support\Facades\App, class to call that method. Example:


// App\Services\House.php
// this is the class we want to inject

class House {
  public function getName(): string {}
}

// App\Services\Home.php
// this is the class we want to call the method on

use App\Services\House;

class Home {
  public function all(House $house): array {
      return [];
  }
}

// in another place in your application where you want to call method directly
use Illuminate\Support\Facades\App;
...
App::call([new Home, 'all']);
// this will invoke the all method via the container and inject the House class into the method

Automatic Dependency Injection

Laravel allows you to automatically inject a php class into the constructor or method of another class automatically as the service container takes care of injecting those dependencies. An example internal dependency injection is the Illuminate\Http\Request object that can be used in a route handler.

use Illuminate\Http\Request;

Route::get('/hello', function (Request $request) {
    return $request->fullUrl();
});

This allows you to just type hint the request object by providing the Illuminate\Http\Request class as a parameter of your route handler without needing to resolve the class because laravel has automatically resolved it with the service container.

The service container also will automatically allow you to inject any php class except when it implements an interface and you wish to type-hint that interface on a route or class constructor(you can use a service provider to tell laravel how to resolve that interface) and in third party packages where you need to share code with other developers - (paraphrased from laravel documentation).

Binding to Service Containers

You can bind your classes to service container with simple bindings, singletons and more. You can go through the laravel documentation on service container for more information but we’ll only look at some simple bindings and singletons for now. Once you understand the basic bindings and how to create them, you can look into the other options and use them when needed.

Simple bindings can be used to bind classes and their dependencies to the service container. This is typically done in a service provider. Specifically, the register method of a service provider.

In the example below, the PodcastEvent class is a dependency of the SpotifyPodcastEvent class. So to ensure that the PodcastEvent class is automatically resolved anytime a SpotifyPodcastEvent is called, we bind an instance of the PodcastEvent class to it in the service container. This will ensure that we don’t need to pass the PodcastEvent class manually but we can access it directly in the constructor of the SpotifyPodcastEvent anytime we use dependency injection or the service container to resolve the class.

<?php

declare(strict_types=1);

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\PodcastEvent;
use App\Services\SpotifyPodcastEvent;

class MyServiceProvider extends ServiceProvider {
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $podcast = new SpotifyPodcastEvent(new PostcastEvent);
        $this->app->bind(SpotifyPostcastEvent::class, $podcast);
    }
}

You can use singleton bindings if you only want the service container to resolve the class that you bind once and then return the same instance of the class on subsequent calls.


 $this->app->singleton(SpotifyPodcastEvent::class, function () {
      return  new SpotifyPodcastEvent(new PostcastEvent);
 });

Service Providers

Service Providers serve a critical part in laravel’s internal code, laravel third party library codes and end user code (in the case of us building applications with laravel). It’s a topic that at some point in your laravel journey, you will have to learn of it’s capabilities and functionalities.

In the laravel documentation, I quote the definition of service providers:

Service providers are the central place of all laravel application bootstrapping. Your own application, as well as all of Laravel’s core services are bootstrapped via service providers.

In simple terms, service providers provide you with a central place where you can configure the features for your application. It’s ideal for registering service container bindings and updating or extending the core functionalities of laravel’s internal code and third party libraries. It’s used by laravel internally to bootstrap or setup the features of the laravel framework such as caching, filesystem, database and other internal features.

When a laravel application is started, the http kernel layer of laravel which is responsible for accepting a request and returning a response bootstraps(configures) your application by loading internal implementations of laravel features and one of the actions it performs is to load the service providers and go through(call) all register methods of each service provider, both internal (from laravel), and external (from us or third party libraries). This then makes it possible for us to access laravel features (that have been registered) or extend them in the boot method in the service provider which is run after all regsiter methods are called. Visit the laravel documentation for more information on the laravel request cycle.

In a laravel service provider, you have two core methods that you will normally use, boot and register. In the register method, we bind php classes and php objects to the service container. The register method should only be used for this. In the boot method, you can configure your laravel application. The boot method is called after laravel has registered all other services from all service providers so it’s a safe place to then tap into the framework or library to extend it’s functionalities.

We will jump straight into an example to illustrate how to use service providers and service container bindings.

Registering Service Container Bindings to Communicate with an External API

Given that we have a php class, GithubHttpService that is responsible for sending http requests to github, we want to use methods that perform several actions like getting the user profile details and listing all repos. We will also consider using environment variable to store our github personal access token for making the requests.

We don’t want to expose our secure or service related variables because some can be sensitive and can also change in time like the Personal Access Token (PAT) or github the api version in our code so we will configure them in environment variables and use the services config file, config/services.php to provide access to them.

// config/services.php
return [
    ...,

    'github' => [
        'base_uri' => env('GITHUB_BASE_API_URI', 'https://api.github.com/'),
        'api_version' => env('GITHUB_API_VERSION', '2022-11-28'),
        'token' => env('GITHUB_PERSONAL_ACCESS_TOKEN')
    ],
    ...
];

We will then configure our service, App/Services/GithubHttpService.php class to use the environment variables to setup our api calls to github.

We can setup our http client so that it has the base http information we need for all our github api requests by creating a pending request using the laravel http client. A pending request is basically still using laravel’s http client to create an http request but it’s only used to setup or prepare the client with information like headers, url query parameters and any other information that the http request requires without actually sending the request. Thus, you don’t call a get,post,put or any other method on the http client that’s actually responsible for sending the request.


use Illuminate\Support\Http;

$client = Http::baseUrl(config('services.github.base_uri'))
          ->withHeaders([
              'X-GitHub-Api-Version' => config('services.github.api_version')
          ])
          ->withToken(config('services.github.token'));

We can then use this client to make api calls to github api. An example is $client->get('user'), which will return the profile information of the github user (the owner of the PAT).

Our service could end up looking like this:

// App/Services/GithubHttpService.php
<?php

namespace App\Services;

use Illuminate\Support\Http;

final class GithubHttpService {

  public function me(): mixed {
    $client = Http::baseUrl(config('services.github.base_uri'))
          ->withHeaders([
              'X-GitHub-Api-Version' => config('services.github.api_version')
          ])
          ->withToken(config('services.github.token'));

    return $client->get('user');
  }

}

There’s no reason for you to change this piece of code because I mean it’s readable, simple and straight forward to understand right? In the long term, you will find yourself duplicating this code to be able to use the same functionality to make api requests to github like listing the repositories or issues.

Of course, you could also simplify this by moving the client initialization to the __construct__ method of your class which then makes it easy to just call $this->client in any of your methods to access the client.

I am going to talk about a method I consider to be better using service providers. Let’s create our own service provider, GithubServiceProvider.php. We can use the laravel artisan command, php artisan make:provider GithubServiceProvider. After running the command, laravel will create the provider for you in the App\Providers folder/namespace. Laravel will automatically register the service provider for you if you use the command to create a new provider. If you create the provider manually, you will need to register the service provider by adding the class to bootstrap/providers.php (in laravel 11). When laravel is bootstrapping your application, it will look at the bootstrap/providers.php file to any providers it needs to register. If you don’t add your provider to that file, it will not be recognized or called by laravel.

In a service providers’s register method, you can bind classes and their dependencies to the laravel service container. This allows us to bind dependencies our classes rely on to the service container.

We will take advantage of this feature to bind the pending request of our http service to the laravel service container allowing us to use the pending client in any method of our service and also to be able to inject the class in our controllers or other classes without needing to instantiate or create a new pending request since the service provider will have registered our binding already.

We will register our binding to the service container with a singleton which basically ensures that the binding is resolved only once and returns the same object on subsequent calls. This is useful for us since we only need to add configurations such as a base api url and a PAT which should only be resolved once as we don’t need a new one each time we make a github api call.

// App/Providers/GithubServiceProvider.php
<?php
declare(strict_types=1);

namespace App\Providers;

use Domains\External\Github\Services\GithubHttpService;
use Illuminate\Contracts\Foundation\Application;
// use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;

final class GithubServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
        $this->app->singleton(GithubHttpService::class, function (Application $app) {
            $client = Http::baseUrl(config('services.github.base_uri'))
                ->withHeaders([
                    'X-GitHub-Api-Version' => config('services.github.api_version')
                ])
                ->withToken(config('services.github.token'));

            return new GithubHttpService($client);
        });
    }
}

If our service provider only registers service container bindings, we can optimize it by using a deferrable provider which basically tells laravel to not register the service provider till we actually call one of it’s bindings. This is a very useful feature to use because it ensures that laravel does not bind our service provider when we don’t need to access it which will improve the application performance as it has to do less when bootstrapping your application.

Since we are only registering bindings in our service provider, we will switch to implement a Deferrable Provider and provide the bindings that should be deferred in the provides method.

// App/Providers/GithubServiceProvider.php
<?php
declare(strict_types=1);

namespace App\Providers;

use Domains\External\Github\Services\GithubHttpService;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;

final class GithubServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
        $this->app->singleton(GithubHttpService::class, function (Application $app) {
              $client = Http::baseUrl(config('services.github.base_uri'))
                  ->withHeaders([
                      'X-GitHub-Api-Version' => config('services.github.api_version')
                  ])
                  ->withToken(config('services.github.token'));

              return new GithubHttpService($client);
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
          // adding the deffered binding here.
          return [GithubHttpService::class];
    }
}

NOTE: Your service providers will still work even without using the defferable implementation. We only use it here because our provider is only registering bindings and we also don’t need the github api throughout our application.

Once we do this, we can then access the pending request we passed(in the binding, return new GithubHttpService($client)) in the constructor of our service using the Illuminate\Http\Client\PendingRequest class as it’s passed by the service container binding into the class as a dependency.

This will allow us call the client in multiple methods. We can also extend the PendingRequest in any of the methods we call it from before sending the request by adding further headers, or request information since it’s still an http request that has only been configured.

<?php

declare(strict_types=1);

namespace App\Services;

use Illuminate\Http\Client\PendingRequest;

final class GithubHttpService
{
    public function __construct(
        private PendingRequest $client,
    ) {}

    public function me(): mixed
    {
        $request = $this->client->get('user');

        return $request->json();
    }

    public function getPublicRepositories(): mixed
    {
        $request = $this->client->get('repositories');

        return $request
          ->withHeaders([
            'Accept' => 'application/vnd.github+json'
          ])
          ->json();
    }
}

We can then use our service in our controller by using dependency injection to add the service in the __construct__ method of the controller.

// App/Http/Controllers/HomeController.php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Domains\External\Github\Facades\Github;
use Domains\External\Github\Services\GithubHttpService;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;

final class HomeController extends Controller
{
    public function __construct(
      private GithubHttpService $githubHttpService
    ) {}

    //
    public function index(): JsonResponse
    {
        $data = $this->githubHttpService->me();

        return response()->json($data);
    }
}

This works very well allowing us to take advantage of dependency injection.

Sometimes, you might have several services that your controller or the class using them depends upon. You might not want to use dependency injection for each of the classes.

Facades

Facades are another way for us to interact with our service container bindings without using dependency injection. They allow us to provide a static interface to our bindings. Basically, you are able to call all methods on your service container binding but in a more user friendly way by using static methods on the facade without dependency injection.

Internally, laravel uses facades to provide access to a lot of internal implementations such as cache and storage. An example is the cache facade, Illuminate\Support\Facades\Cache which provides us a way to interact with the laravel cache manager and cache factory that provides the caching functionality in laravel.

We will create a facade for our github http service to allow users of the api to easily call the public methods on it statically.

To create a facade, you only need to create a new class that extends the laravel Illuminate\Support\Facades\Facade class and then slightly tweak your binding in the service provider to a facade accessor (basically, a unique string that represents your binding). In our case, we will use a github facade accessor.

Let’s first change our service provider implementation.

...
final class GithubServiceProvider extends ServiceProvider implements DeferrableProvider
{
...
    public function register(): void
    {
        // change here only by changing the class GithubHttpService::class to a string 'github'
        $this->app->singleton('github', function (Application $app) {
            $client = Http::baseUrl(config('services.github.base_uri'))
                ->withHeaders([
                    'X-GitHub-Api-Version' => config('services.github.api_version')
                ])
                ->withToken(config('services.github.token'));

            return new GithubHttpService($client);
        });
    }

    public function provides(): array
    {
      // also replace the provides method with the new string, 'github'
        return ['github'];
    }
}

So we made two changes, first we replaced the binding, GithubHttpService::class with the string github and we also changed the provides method to use the string github , return ['github']; Everything else remains the same in our provider.

We then go ahead to create our facade:

// App/Support/Facades/Github.php
<?php

declare(strict_types=1);

namespace App\Support\Facades;

use Illuminate\Support\Facades\Facade;

final class Github extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'github';
    }
}

In our Github facade, we only needed to extend the laravel Facade class and also add a method for the registered binding, github;

We can now use our new facade to access our api. In our controller, we’ll just replace the call to the api method with the facade and remove the dependency injection of the http service class.

...

use App\Support\Facades\Github;

final class HomeController extends Controller
{
  // you can remove the constructor method if you don't need to use it
    public function __construct(
    ) {}

    //
    public function index(): JsonResponse
    {
      // we replace the call to the service with a static call using the facade
        $data = Github::me();

        return response()->json($data);
    }
}

This now provides us with a lean implementation and interface to interact with our api. Notice that your IDE might not provide you with auto completion of the me method or any other public method on your facade(the methods in your http service). This is because the static methods provided by the facade to access the methods of the http service is generated using laravel’s internal implementation of facades (which we extended with ... extends Facade) so your IDE does not have any idea of the methods that will be called on it. What we can do in this situation is to use php comments to declare the properties,methods and any extra information that will be needed so your IDE can provide autocompletion support for this.

We will use the @see property in the php comments to direct the user to the respective class that implements the functionality of the facade, in our case, \App\Services\GithubHttpService and also the @method property in the php comments to provide support for the methods.

...
/**
 * @method static mixed me()
 * @method static mixed getPublicRepositories()
 *
 * @see \App\Services\GithubHttpService
 */
final class Github extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'github';
    }
}

The format of the method comment (example, @method static mixed getName()) is:

  • @method - this is a function
  • static - the type of the function
  • mixed - the return type of the function
  • getName() - the name of the function

By doing this, we are able to get auto completion support in our IDE. If your container binding’s class has a public method that has arguments, you can also provide the arguments in the comment, example: @method static mixed getOrganisationRepositories(string $orgId).

By taking advantage of dependency injection or facades that laravel provides us with, we are able to use our github http service which is registered by the provider with a dependency on a pending request to make requests to our api.

Of course this is not compulsory to use or required but it can be an arsenal in your toolbox that you can use when you need it. Until the next time, have a fun time writing your laravel applications, laratisans (just a pun, no other meaning, just saying laravel and php artisan :)).

Article is Written By: